libkcal

incidenceformatter.cpp

00001 /*
00002     This file is part of libkcal.
00003 
00004     Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
00005     Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
00006     Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
00007 
00008     This library is free software; you can redistribute it and/or
00009     modify it under the terms of the GNU Library General Public
00010     License as published by the Free Software Foundation; either
00011     version 2 of the License, or (at your option) any later version.
00012 
00013     This library is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016     Library General Public License for more details.
00017 
00018     You should have received a copy of the GNU Library General Public License
00019     along with this library; see the file COPYING.LIB.  If not, write to
00020     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00021     Boston, MA 02110-1301, USA.
00022 */
00023 
00024 #include "incidenceformatter.h"
00025 
00026 #include <libkcal/attachment.h>
00027 #include <libkcal/calhelper.h>
00028 #include <libkcal/event.h>
00029 #include <libkcal/todo.h>
00030 #include <libkcal/journal.h>
00031 #include <libkcal/calendar.h>
00032 #include <libkcal/calendarlocal.h>
00033 #include <libkcal/icalformat.h>
00034 #include <libkcal/freebusy.h>
00035 #include <libkcal/calendarresources.h>
00036 
00037 #include <libemailfunctions/email.h>
00038 
00039 #include <ktnef/ktnefparser.h>
00040 #include <ktnef/ktnefmessage.h>
00041 #include <ktnef/ktnefdefs.h>
00042 #include <kabc/phonenumber.h>
00043 #include <kabc/vcardconverter.h>
00044 #include <kabc/stdaddressbook.h>
00045 
00046 #include <kapplication.h>
00047 #include <kemailsettings.h>
00048 
00049 #include <klocale.h>
00050 #include <kglobal.h>
00051 #include <kiconloader.h>
00052 #include <kcalendarsystem.h>
00053 #include <kmdcodec.h>
00054 #include <kmimetype.h>
00055 
00056 #include <qbuffer.h>
00057 #include <qstylesheet.h>
00058 #include <qdatetime.h>
00059 #include <qregexp.h>
00060 
00061 #include <time.h>
00062 
00063 using namespace KCal;
00064 
00065 //#define KORG_DEBUG_SCHEDULING_IDS
00066 
00067 /*******************
00068  *  General helpers
00069  *******************/
00070 
00071 static QString htmlAddLink( const QString &ref, const QString &text,
00072                              bool newline = true )
00073 {
00074   QString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" );
00075   if ( newline ) tmpStr += "\n";
00076   return tmpStr;
00077 }
00078 
00079 static QString htmlAddMailtoLink( const QString &email, const QString &name )
00080 {
00081   QString str;
00082 
00083   if ( !email.isEmpty() ) {
00084     KCal::Person person( name, email );
00085     KURL mailto;
00086     mailto.setProtocol( "mailto" );
00087     mailto.setPath( person.fullName() );
00088     const QString iconPath = KGlobal::iconLoader()->iconPath( "mail_new", KIcon::Small );
00089     str = htmlAddLink( mailto.url(), "<img valign=\"top\" src=\"" + iconPath + "\">" );
00090   }
00091   return str;
00092 }
00093 
00094 static QString htmlAddUidLink( const QString &email, const QString &name, const QString &uid )
00095 {
00096   QString str;
00097 
00098   if ( !uid.isEmpty() ) {
00099     // There is a UID, so make a link to the addressbook
00100     if ( name.isEmpty() ) {
00101       // Use the email address for text
00102       str += htmlAddLink( "uid:" + uid, email );
00103     } else {
00104       str += htmlAddLink( "uid:" + uid, name );
00105     }
00106   }
00107   return str;
00108 }
00109 
00110 static QString htmlAddTag( const QString & tag, const QString & text )
00111 {
00112   int numLineBreaks = text.contains( "\n" );
00113   QString str = "<" + tag + ">";
00114   QString tmpText = text;
00115   QString tmpStr = str;
00116   if( numLineBreaks >= 0 ) {
00117     if ( numLineBreaks > 0) {
00118       int pos = 0;
00119       QString tmp;
00120       for( int i = 0; i <= numLineBreaks; i++ ) {
00121         pos = tmpText.find( "\n" );
00122         tmp = tmpText.left( pos );
00123         tmpText = tmpText.right( tmpText.length() - pos - 1 );
00124         tmpStr += tmp + "<br>";
00125       }
00126     } else {
00127       tmpStr += tmpText;
00128     }
00129   }
00130   tmpStr += "</" + tag + ">";
00131   return tmpStr;
00132 }
00133 
00134 static QPair<QString, QString> searchNameAndUid( const QString &email, const QString &name,
00135                                                  const QString &uid )
00136 {
00137   QString sName = name;
00138   QString sUid = uid;
00139   // Make the search, if there is an email address to search on,
00140   // and either name or uid is missing
00141   if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
00142     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
00143     KABC::Addressee::List addressList = add_book->findByEmail( email );
00144     if ( !addressList.isEmpty() ) {
00145       KABC::Addressee o = addressList.first();
00146       if ( !o.isEmpty() && addressList.size() < 2 ) {
00147         if ( name.isEmpty() ) {
00148           // No name set, so use the one from the addressbook
00149           sName = o.formattedName();
00150         }
00151         sUid = o.uid();
00152       } else {
00153         // Email not found in the addressbook
00154         sUid = QString::null;
00155       }
00156     }
00157   }
00158   QPair<QString, QString>s;
00159   s.first = sName;
00160   s.second = sUid;
00161   return s;
00162 }
00163 
00164 static QString searchName( const QString &email, const QString &name )
00165 {
00166   QString printName = name;
00167   // Search, if there is an email address to search on
00168   if ( name.isEmpty() && !email.isEmpty() ) {
00169     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
00170     KABC::Addressee::List addressList = add_book->findByEmail( email );
00171     if ( !addressList.isEmpty() ) {
00172       KABC::Addressee o = addressList.first();
00173       if ( !o.isEmpty() && addressList.size() < 2 ) {
00174         // No name set, so use the one from the addressbook
00175         printName = o.formattedName();
00176       }
00177     }
00178   }
00179   return printName;
00180 }
00181 
00182 static bool iamAttendee( Attendee *attendee )
00183 {
00184   // Check if I'm this attendee
00185 
00186   bool iam = false;
00187   KEMailSettings settings;
00188   QStringList profiles = settings.profiles();
00189   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
00190     settings.setProfile( *it );
00191     if ( settings.getSetting( KEMailSettings::EmailAddress ) == attendee->email() ) {
00192       iam = true;
00193       break;
00194     }
00195   }
00196   return iam;
00197 }
00198 
00199 static bool iamOrganizer( Incidence *incidence )
00200 {
00201   // Check if I'm the organizer for this incidence
00202 
00203   if ( !incidence ) {
00204     return false;
00205   }
00206 
00207   bool iam = false;
00208   KEMailSettings settings;
00209   QStringList profiles = settings.profiles();
00210   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
00211     settings.setProfile( *it );
00212     if ( settings.getSetting( KEMailSettings::EmailAddress ) == incidence->organizer().email() ) {
00213       iam = true;
00214       break;
00215     }
00216   }
00217   return iam;
00218 }
00219 
00220 static bool senderIsOrganizer( Incidence *incidence, const QString &sender )
00221 {
00222   // Check if the specified sender is the organizer
00223 
00224   if ( !incidence || sender.isEmpty() ) {
00225     return true;
00226   }
00227   bool isorg = true;
00228   QString senderName, senderEmail;
00229   if ( KPIM::getNameAndMail( sender, senderName, senderEmail ) ) {
00230     // for this heuristic, we say the sender is the organizer if either the name or the email match.
00231     if ( incidence->organizer().email() != senderEmail &&
00232          incidence->organizer().name() != senderName ) {
00233       isorg = false;
00234     }
00235   }
00236   return isorg;
00237 }
00238 
00239 static bool attendeeIsOrganizer( Incidence *incidence, Attendee *attendee )
00240 {
00241   if ( incidence && attendee &&
00242        ( incidence->organizer().email() == attendee->email() ) ) {
00243     return true;
00244   } else {
00245     return false;
00246   }
00247 }
00248 
00249 static QString organizerName( Incidence *incidence, const QString &defName )
00250 {
00251   QString tName;
00252   if ( !defName.isEmpty() ) {
00253     tName = defName;
00254   } else {
00255     tName = i18n( "<Organizer Unknown>" );
00256   }
00257 
00258   QString name;
00259   if ( incidence ) {
00260     name = incidence->organizer().name();
00261     if ( name.isEmpty() ) {
00262       name = incidence->organizer().email();
00263     }
00264   }
00265   if ( name.isEmpty() ) {
00266     name = tName;
00267   }
00268   return name;
00269 }
00270 
00271 static QString firstAttendeeName( Incidence *incidence, const QString &defName )
00272 {
00273   QString tName;
00274   if ( !defName.isEmpty() ) {
00275     tName = defName;
00276   } else {
00277     tName = i18n( "Sender" );
00278   }
00279 
00280   QString name;
00281   if ( incidence ) {
00282     Attendee::List attendees = incidence->attendees();
00283     if ( attendees.count() > 0 ) {
00284       Attendee *attendee = *attendees.begin();
00285       name = attendee->name();
00286       if ( name.isEmpty() ) {
00287         name = attendee->email();
00288       }
00289     }
00290   }
00291   if ( name.isEmpty() ) {
00292     name = tName;
00293   }
00294   return name;
00295 }
00296 
00297 static QString rsvpStatusIconPath( Attendee::PartStat status )
00298 {
00299   QString iconPath;
00300   switch ( status ) {
00301   case Attendee::Accepted:
00302     iconPath = KGlobal::iconLoader()->iconPath( "ok", KIcon::Small );
00303     break;
00304   case Attendee::Declined:
00305     iconPath = KGlobal::iconLoader()->iconPath( "no", KIcon::Small );
00306     break;
00307   case Attendee::NeedsAction:
00308   case Attendee::InProcess:
00309     iconPath = KGlobal::iconLoader()->iconPath( "help", KIcon::Small );
00310     break;
00311   case Attendee::Tentative:
00312     iconPath = KGlobal::iconLoader()->iconPath( "apply", KIcon::Small );
00313     break;
00314   case Attendee::Delegated:
00315     iconPath = KGlobal::iconLoader()->iconPath( "mail_forward", KIcon::Small );
00316     break;
00317   default:
00318     break;
00319   }
00320   return iconPath;
00321 }
00322 
00323 /*******************************************************************
00324  *  Helper functions for the extensive display (display viewer)
00325  *******************************************************************/
00326 
00327 static QString displayViewFormatPerson( const QString &email, const QString &name,
00328                                         const QString &uid, const QString &iconPath )
00329 {
00330   // Search for new print name or uid, if needed.
00331   QPair<QString, QString> s = searchNameAndUid( email, name, uid );
00332   const QString printName = s.first;
00333   const QString printUid = s.second;
00334 
00335   QString personString;
00336   if ( !iconPath.isEmpty() ) {
00337     personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
00338   }
00339 
00340   // Make the uid link
00341   if ( !printUid.isEmpty() ) {
00342     personString += htmlAddUidLink( email, printName, printUid );
00343   } else {
00344     // No UID, just show some text
00345     personString += ( printName.isEmpty() ? email : printName );
00346   }
00347 
00348   // Make the mailto link
00349   if ( !email.isEmpty() ) {
00350     personString += "&nbsp;" + htmlAddMailtoLink( email, printName );
00351   }
00352 
00353   return personString;
00354 }
00355 
00356 static QString displayViewFormatPerson( const QString &email, const QString &name,
00357                                         const QString &uid, Attendee::PartStat status )
00358 {
00359   return displayViewFormatPerson( email, name, uid, rsvpStatusIconPath( status ) );
00360 }
00361 
00362 static QString displayViewFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role,
00363                                                   bool showStatus )
00364 {
00365   QString tmpStr;
00366   Attendee::List::ConstIterator it;
00367   Attendee::List attendees = incidence->attendees();
00368 
00369   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00370     Attendee *a = *it;
00371     if ( a->role() != role ) {
00372       // skip this role
00373       continue;
00374     }
00375     if ( attendeeIsOrganizer( incidence, a ) ) {
00376       // skip attendee that is also the organizer
00377       continue;
00378     }
00379     tmpStr += displayViewFormatPerson( a->email(), a->name(), a->uid(),
00380                                        showStatus ? a->status() : Attendee::None );
00381     if ( !a->delegator().isEmpty() ) {
00382       tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
00383     }
00384     if ( !a->delegate().isEmpty() ) {
00385       tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
00386     }
00387     tmpStr += "<br>";
00388   }
00389   if ( tmpStr.endsWith( "<br>" ) ) {
00390     tmpStr.truncate( tmpStr.length() - 4 );
00391   }
00392   return tmpStr;
00393 }
00394 
00395 static QString displayViewFormatAttendees( Calendar *calendar, Incidence *incidence )
00396 {
00397   QString tmpStr, str;
00398 
00399   // Add organizer link
00400   int attendeeCount = incidence->attendees().count();
00401   if ( attendeeCount > 1 ||
00402        ( attendeeCount == 1 &&
00403          !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) {
00404 
00405     QPair<QString, QString> s = searchNameAndUid( incidence->organizer().email(),
00406                                                   incidence->organizer().name(),
00407                                                   QString() );
00408     tmpStr += "<tr>";
00409     tmpStr += "<td><b>" + i18n( "Organizer:" ) + "</b></td>";
00410     QString iconPath = KGlobal::iconLoader()->iconPath( "organizer", KIcon::Small );
00411     tmpStr += "<td>" + displayViewFormatPerson( incidence->organizer().email(),
00412                                                 s.first, s.second, iconPath ) +
00413               "</td>";
00414     tmpStr += "</tr>";
00415   }
00416 
00417   // Show the attendee status if the incidence's organizer owns the resource calendar,
00418   // which means they are running the show and have all the up-to-date response info.
00419   bool showStatus = CalHelper::incOrganizerOwnsCalendar( calendar, incidence );
00420 
00421   // Add "chair"
00422   str = displayViewFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus );
00423   if ( !str.isEmpty() ) {
00424     tmpStr += "<tr>";
00425     tmpStr += "<td><b>" + i18n( "Chair:" ) + "</b></td>";
00426     tmpStr += "<td>" + str + "</td>";
00427     tmpStr += "</tr>";
00428   }
00429 
00430   // Add required participants
00431   str = displayViewFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus );
00432   if ( !str.isEmpty() ) {
00433     tmpStr += "<tr>";
00434     tmpStr += "<td><b>" + i18n( "Required Participants:" ) + "</b></td>";
00435     tmpStr += "<td>" + str + "</td>";
00436     tmpStr += "</tr>";
00437   }
00438 
00439   // Add optional participants
00440   str = displayViewFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus );
00441   if ( !str.isEmpty() ) {
00442     tmpStr += "<tr>";
00443     tmpStr += "<td><b>" + i18n( "Optional Participants:" ) + "</b></td>";
00444     tmpStr += "<td>" + str + "</td>";
00445     tmpStr += "</tr>";
00446   }
00447 
00448   // Add observers
00449   str = displayViewFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus );
00450   if ( !str.isEmpty() ) {
00451     tmpStr += "<tr>";
00452     tmpStr += "<td><b>" + i18n( "Observers:" ) + "</b></td>";
00453     tmpStr += "<td>" + str + "</td>";
00454     tmpStr += "</tr>";
00455   }
00456 
00457   return tmpStr;
00458 }
00459 
00460 static QString displayViewFormatAttachments( Incidence *incidence )
00461 {
00462   QString tmpStr;
00463   Attachment::List as = incidence->attachments();
00464   Attachment::List::ConstIterator it;
00465   uint count = 0;
00466   for( it = as.begin(); it != as.end(); ++it ) {
00467     count++;
00468     if ( (*it)->isUri() ) {
00469       QString name;
00470       if ( (*it)->uri().startsWith( "kmail:" ) ) {
00471         name = i18n( "Show mail" );
00472       } else {
00473         if ( (*it)->label().isEmpty() ) {
00474           name = (*it)->uri();
00475         } else {
00476           name = (*it)->label();
00477         }
00478       }
00479       tmpStr += htmlAddLink( (*it)->uri(), name );
00480     } else {
00481       const QCString encodedLabel = KCodecs::base64Encode((*it)->label().utf8());
00482       tmpStr += htmlAddLink( "ATTACH:" + incidence->uid() + ':' + QString::fromUtf8(encodedLabel.data(), encodedLabel.length()),
00483                              (*it)->label(), false );
00484     }
00485     if ( count < as.count() ) {
00486       tmpStr += "<br>";
00487     }
00488   }
00489   return tmpStr;
00490 }
00491 
00492 static QString displayViewFormatCategories( Incidence *incidence )
00493 {
00494   // We do not use Incidence::categoriesStr() since it does not have whitespace
00495   return incidence->categories().join( ", " );
00496 }
00497 
00498 static QString displayViewFormatCreationDate( Incidence *incidence )
00499 {
00500   return i18n( "Creation date: %1" ).
00501     arg( IncidenceFormatter::dateTimeToString( incidence->created(), false, true ) );
00502 }
00503 
00504 static QString displayViewFormatBirthday( Event *event )
00505 {
00506   if ( !event ) {
00507     return  QString::null;
00508   }
00509   if ( event->customProperty("KABC","BIRTHDAY") != "YES" ) {
00510     return QString::null;
00511   }
00512 
00513   QString uid = event->customProperty("KABC","UID-1");
00514   QString name = event->customProperty("KABC","NAME-1");
00515   QString email= event->customProperty("KABC","EMAIL-1");
00516   QString tmpStr = displayViewFormatPerson( email, name, uid, QString() );
00517 
00518   if ( event->customProperty( "KABC", "ANNIVERSARY") == "YES" ) {
00519     uid = event->customProperty("KABC","UID-2");
00520     name = event->customProperty("KABC","NAME-2");
00521     email= event->customProperty("KABC","EMAIL-2");
00522     tmpStr += "<br>" + displayViewFormatPerson( email, name, uid, QString() );
00523   }
00524 
00525   return tmpStr;
00526 }
00527 
00528 static QString displayViewFormatHeader( Incidence *incidence )
00529 {
00530   QString tmpStr = "<table><tr>";
00531 
00532   // show icons
00533   {
00534     tmpStr += "<td>";
00535 
00536     if ( incidence->type() == "Event" ) {
00537       QString iconPath;
00538       if ( incidence->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) {
00539         if ( incidence->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00540           iconPath =
00541             KGlobal::iconLoader()->iconPath( "calendaranniversary", KIcon::Small );
00542         } else {
00543           iconPath = KGlobal::iconLoader()->iconPath( "calendarbirthday", KIcon::Small );
00544         }
00545       } else {
00546         iconPath = KGlobal::iconLoader()->iconPath( "appointment", KIcon::Small );
00547       }
00548       tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
00549     }
00550     if ( incidence->type() == "Todo" ) {
00551       tmpStr += "<img valign=\"top\" src=\"" +
00552                 KGlobal::iconLoader()->iconPath( "todo", KIcon::Small ) +
00553                 "\">";
00554     }
00555     if ( incidence->type() == "Journal" ) {
00556       tmpStr += "<img valign=\"top\" src=\"" +
00557                 KGlobal::iconLoader()->iconPath( "journal", KIcon::Small ) +
00558                 "\">";
00559     }
00560     if ( incidence->isAlarmEnabled() ) {
00561       tmpStr += "<img valign=\"top\" src=\"" +
00562                 KGlobal::iconLoader()->iconPath( "bell", KIcon::Small ) +
00563                 "\">";
00564     }
00565     if ( incidence->doesRecur() ) {
00566       tmpStr += "<img valign=\"top\" src=\"" +
00567                 KGlobal::iconLoader()->iconPath( "recur", KIcon::Small ) +
00568                 "\">";
00569     }
00570     if ( incidence->isReadOnly() ) {
00571       tmpStr += "<img valign=\"top\" src=\"" +
00572                 KGlobal::iconLoader()->iconPath( "readonlyevent", KIcon::Small ) +
00573                 "\">";
00574     }
00575 
00576     tmpStr += "</td>";
00577   }
00578 
00579   tmpStr += "<td>";
00580   tmpStr += "<b><u>" + incidence->summary() + "</u></b>";
00581   tmpStr += "</td>";
00582 
00583   tmpStr += "</tr></table>";
00584 
00585   return tmpStr;
00586 }
00587 
00588 static QString displayViewFormatEvent( Calendar *calendar, Event *event, const QDate &date )
00589 {
00590   if ( !event ) {
00591     return QString::null;
00592   }
00593 
00594   QString tmpStr = displayViewFormatHeader( event );
00595 
00596   tmpStr += "<table>";
00597   tmpStr += "<col width=\"25%\"/>";
00598   tmpStr += "<col width=\"75%\"/>";
00599 
00600   if ( calendar ) {
00601     QString calStr = IncidenceFormatter::resourceString( calendar, event );
00602     if ( !calStr.isEmpty() ) {
00603       tmpStr += "<tr>";
00604       tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00605       tmpStr += "<td>" + calStr + "</td>";
00606       tmpStr += "</tr>";
00607     }
00608   }
00609 
00610   if ( !event->location().isEmpty() ) {
00611     tmpStr += "<tr>";
00612     tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
00613     tmpStr += "<td>" + event->location() + "</td>";
00614     tmpStr += "</tr>";
00615   }
00616 
00617   QDateTime startDt = event->dtStart();
00618   QDateTime endDt = event->dtEnd();
00619   if ( event->doesRecur() ) {
00620     if ( date.isValid() ) {
00621       QDateTime dt( date, QTime( 0, 0, 0 ) );
00622       int diffDays = startDt.daysTo( dt );
00623       dt = dt.addSecs( -1 );
00624       startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() );
00625       if ( event->hasEndDate() ) {
00626         endDt = endDt.addDays( diffDays );
00627         if ( startDt > endDt ) {
00628           startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() );
00629           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
00630         }
00631       }
00632     }
00633   }
00634 
00635   tmpStr += "<tr>";
00636   if ( event->doesFloat() ) {
00637     if ( event->isMultiDay() ) {
00638       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00639       tmpStr += "<td>" +
00640                 i18n("<beginDate> - <endDate>","%1 - %2").
00641                 arg( IncidenceFormatter::dateToString( startDt, false ) ).
00642                 arg( IncidenceFormatter::dateToString( endDt, false ) ) +
00643                 "</td>";
00644     } else {
00645       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00646       tmpStr += "<td>" +
00647                 i18n("date as string","%1").
00648                 arg( IncidenceFormatter::dateToString( startDt, false ) ) +
00649                 "</td>";
00650     }
00651   } else {
00652     if ( event->isMultiDay() ) {
00653       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00654       tmpStr += "<td>" +
00655                 i18n("<beginDate> - <endDate>","%1 - %2").
00656                 arg( IncidenceFormatter::dateToString( startDt, false ) ).
00657                 arg( IncidenceFormatter::dateToString( endDt, false ) ) +
00658                 "</td>";
00659     } else {
00660       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00661       tmpStr += "<td>" +
00662                 i18n("date as string","%1").
00663                 arg( IncidenceFormatter::dateToString( startDt, false ) ) +
00664                 "</td>";
00665 
00666       tmpStr += "</tr><tr>";
00667       tmpStr += "<td><b>" + i18n( "Time:" ) + "</b></td>";
00668       if ( event->hasEndDate() && startDt != endDt ) {
00669         tmpStr += "<td>" +
00670                   i18n("<beginTime> - <endTime>","%1 - %2").
00671                   arg( IncidenceFormatter::timeToString( startDt, true ) ).
00672                   arg( IncidenceFormatter::timeToString( endDt, true ) ) +
00673                   "</td>";
00674       } else {
00675         tmpStr += "<td>" +
00676                   IncidenceFormatter::timeToString( startDt, true ) +
00677                   "</td>";
00678       }
00679     }
00680   }
00681   tmpStr += "</tr>";
00682 
00683   const QString durStr = IncidenceFormatter::durationString( event );
00684   if ( !durStr.isEmpty() ) {
00685     tmpStr += "<tr>";
00686     tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>";
00687     tmpStr += "<td>" + durStr + "</td>";
00688     tmpStr += "</tr>";
00689   }
00690 
00691   if ( event->doesRecur() ) {
00692     tmpStr += "<tr>";
00693     tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>";
00694     tmpStr += "<td>" +
00695               IncidenceFormatter::recurrenceString( event ) +
00696               "</td>";
00697     tmpStr += "</tr>";
00698   }
00699 
00700   if ( event->customProperty("KABC","BIRTHDAY")== "YES" ) {
00701     tmpStr += "<tr>";
00702     if ( event->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00703       tmpStr += "<td><b>" + i18n( "Anniversary:" ) + "</b></td>";
00704     } else {
00705       tmpStr += "<td><b>" + i18n( "Birthday:" ) + "</b></td>";
00706     }
00707     tmpStr += "<td>" + displayViewFormatBirthday( event ) + "</td>";
00708     tmpStr += "</tr>";
00709     tmpStr += "</table>";
00710     return tmpStr;
00711   }
00712 
00713   if ( !event->description().isEmpty() ) {
00714     tmpStr += "<tr>";
00715     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00716     tmpStr += "<td>" + event->description() + "</td>";
00717     tmpStr += "</tr>";
00718   }
00719 
00720   // TODO: print comments?
00721 
00722   int reminderCount = event->alarms().count();
00723   if ( reminderCount > 0 && event->isAlarmEnabled() ) {
00724     tmpStr += "<tr>";
00725     tmpStr += "<td><b>" +
00726               i18n( "Reminder:", "%n Reminders:", reminderCount ) +
00727               "</b></td>";
00728     tmpStr += "<td>" + IncidenceFormatter::reminderStringList( event ).join( "<br>" ) + "</td>";
00729     tmpStr += "</tr>";
00730   }
00731 
00732   tmpStr += displayViewFormatAttendees( calendar, event );
00733 
00734   int categoryCount = event->categories().count();
00735   if ( categoryCount > 0 ) {
00736     tmpStr += "<tr>";
00737     tmpStr += "<td><b>" +
00738               i18n( "Category:", "%n Categories:", categoryCount ) +
00739               "</b></td>";
00740     tmpStr += "<td>" + displayViewFormatCategories( event ) + "</td>";
00741     tmpStr += "</tr>";
00742   }
00743 
00744   int attachmentCount = event->attachments().count();
00745   if ( attachmentCount > 0 ) {
00746     tmpStr += "<tr>";
00747     tmpStr += "<td><b>" +
00748               i18n( "Attachment:", "%n Attachments:", attachmentCount ) +
00749               "</b></td>";
00750     tmpStr += "<td>" + displayViewFormatAttachments( event ) + "</td>";
00751     tmpStr += "</tr>";
00752   }
00753   tmpStr += "</table>";
00754 
00755   tmpStr += "<em>" + displayViewFormatCreationDate( event ) + "</em>";
00756 
00757   return tmpStr;
00758 }
00759 
00760 static QString displayViewFormatTodo( Calendar *calendar, Todo *todo,
00761                                       const QDate &date )
00762 {
00763   if ( !todo ) {
00764     return QString::null;
00765   }
00766 
00767   QString tmpStr = displayViewFormatHeader( todo );
00768 
00769   tmpStr += "<table>";
00770   tmpStr += "<col width=\"25%\"/>";
00771   tmpStr += "<col width=\"75%\"/>";
00772 
00773   if ( calendar ) {
00774     const QString calStr = IncidenceFormatter::resourceString( calendar, todo );
00775     if ( !calStr.isEmpty() ) {
00776       tmpStr += "<tr>";
00777       tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00778       tmpStr += "<td>" + calStr + "</td>";
00779       tmpStr += "</tr>";
00780     }
00781   }
00782 
00783   if ( !todo->location().isEmpty() ) {
00784     tmpStr += "<tr>";
00785     tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
00786     tmpStr += "<td>" + todo->location() + "</td>";
00787     tmpStr += "</tr>";
00788   }
00789 
00790   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
00791     QDateTime startDt = todo->dtStart();
00792     if ( todo->doesRecur() ) {
00793       if ( date.isValid() ) {
00794         startDt.setDate( date );
00795       }
00796     }
00797     tmpStr += "<tr>";
00798     tmpStr += "<td><b>" + i18n( "Start:" ) + "</b></td>";
00799     tmpStr += "<td>" +
00800               IncidenceFormatter::dateTimeToString( startDt,
00801                                                     todo->doesFloat(), false ) +
00802               "</td>";
00803     tmpStr += "</tr>";
00804   }
00805 
00806   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
00807     QDateTime dueDt = todo->dtDue();
00808     if ( todo->doesRecur() ) {
00809       if ( date.isValid() ) {
00810         QDateTime dt( date, QTime( 0, 0, 0 ) );
00811         dt = dt.addSecs( -1 );
00812         dueDt.setDate( todo->recurrence()->getNextDateTime( dt ).date() );
00813       }
00814     }
00815     tmpStr += "<tr>";
00816     tmpStr += "<td><b>" + i18n( "Due:" ) + "</b></td>";
00817     tmpStr += "<td>" +
00818               IncidenceFormatter::dateTimeToString( dueDt,
00819                                                     todo->doesFloat(), false ) +
00820               "</td>";
00821     tmpStr += "</tr>";
00822   }
00823 
00824   const QString durStr = IncidenceFormatter::durationString( todo );
00825   if ( !durStr.isEmpty() ) {
00826     tmpStr += "<tr>";
00827     tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>";
00828     tmpStr += "<td>" + durStr + "</td>";
00829     tmpStr += "</tr>";
00830   }
00831 
00832   if ( todo->doesRecur() ) {
00833     tmpStr += "<tr>";
00834     tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>";
00835     tmpStr += "<td>" +
00836               IncidenceFormatter::recurrenceString( todo ) +
00837               "</td>";
00838     tmpStr += "</tr>";
00839   }
00840 
00841   if ( !todo->description().isEmpty() ) {
00842     tmpStr += "<tr>";
00843     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00844     tmpStr += "<td>" + todo->description() + "</td>";
00845     tmpStr += "</tr>";
00846   }
00847 
00848   // TODO: print comments?
00849 
00850   int reminderCount = todo->alarms().count();
00851   if ( reminderCount > 0 && todo->isAlarmEnabled() ) {
00852     tmpStr += "<tr>";
00853     tmpStr += "<td><b>" +
00854               i18n( "Reminder:", "%n Reminders:", reminderCount ) +
00855               "</b></td>";
00856     tmpStr += "<td>" + IncidenceFormatter::reminderStringList( todo ).join( "<br>" ) + "</td>";
00857     tmpStr += "</tr>";
00858   }
00859 
00860   tmpStr += displayViewFormatAttendees( calendar, todo );
00861 
00862   int categoryCount = todo->categories().count();
00863   if ( categoryCount > 0 ) {
00864     tmpStr += "<tr>";
00865     tmpStr += "<td><b>" +
00866               i18n( "Category:", "%n Categories:", categoryCount ) +
00867               "</b></td>";
00868     tmpStr += "<td>" + displayViewFormatCategories( todo ) + "</td>";
00869     tmpStr += "</tr>";
00870   }
00871 
00872   if ( todo->priority() > 0 ) {
00873     tmpStr += "<tr>";
00874     tmpStr += "<td><b>" + i18n( "Priority:" ) + "</b></td>";
00875     tmpStr += "<td>";
00876     tmpStr += QString::number( todo->priority() );
00877     tmpStr += "</td>";
00878     tmpStr += "</tr>";
00879   }
00880 
00881   tmpStr += "<tr>";
00882   if ( todo->isCompleted() ) {
00883     tmpStr += "<td><b>" + i18n( "Completed:" ) + "</b></td>";
00884     tmpStr += "<td>";
00885     tmpStr += todo->completedStr();
00886   } else {
00887     tmpStr += "<td><b>" + i18n( "Percent Done:" ) + "</b></td>";
00888     tmpStr += "<td>";
00889     tmpStr += i18n( "%1%" ).arg( todo->percentComplete() );
00890   }
00891   tmpStr += "</td>";
00892   tmpStr += "</tr>";
00893 
00894   int attachmentCount = todo->attachments().count();
00895   if ( attachmentCount > 0 ) {
00896     tmpStr += "<tr>";
00897     tmpStr += "<td><b>" +
00898               i18n( "Attachment:", "Attachments:", attachmentCount ) +
00899               "</b></td>";
00900     tmpStr += "<td>" + displayViewFormatAttachments( todo ) + "</td>";
00901     tmpStr += "</tr>";
00902   }
00903 
00904   tmpStr += "</table>";
00905 
00906   tmpStr += "<em>" + displayViewFormatCreationDate( todo ) + "</em>";
00907 
00908   return tmpStr;
00909 }
00910 
00911 static QString displayViewFormatJournal( Calendar *calendar, Journal *journal )
00912 {
00913   if ( !journal ) {
00914     return QString::null;
00915   }
00916 
00917   QString tmpStr = displayViewFormatHeader( journal );
00918 
00919   tmpStr += "<table>";
00920   tmpStr += "<col width=\"25%\"/>";
00921   tmpStr += "<col width=\"75%\"/>";
00922 
00923   if ( calendar ) {
00924     const QString calStr = IncidenceFormatter::resourceString( calendar, journal );
00925     if ( !calStr.isEmpty() ) {
00926       tmpStr += "<tr>";
00927       tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00928       tmpStr += "<td>" + calStr + "</td>";
00929       tmpStr += "</tr>";
00930     }
00931   }
00932 
00933   tmpStr += "<tr>";
00934   tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00935   tmpStr += "<td>" +
00936             IncidenceFormatter::dateToString( journal->dtStart(), false ) +
00937             "</td>";
00938   tmpStr += "</tr>";
00939 
00940   if ( !journal->description().isEmpty() ) {
00941     tmpStr += "<tr>";
00942     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00943     tmpStr += "<td>" + journal->description() + "</td>";
00944     tmpStr += "</tr>";
00945   }
00946 
00947   int categoryCount = journal->categories().count();
00948   if ( categoryCount > 0 ) {
00949     tmpStr += "<tr>";
00950     tmpStr += "<td><b>" +
00951               i18n( "Category:", "%n Categories:", categoryCount ) +
00952               "</b></td>";
00953     tmpStr += "<td>" + displayViewFormatCategories( journal ) + "</td>";
00954     tmpStr += "</tr>";
00955   }
00956   tmpStr += "</table>";
00957 
00958   tmpStr += "<em>" + displayViewFormatCreationDate( journal ) + "</em>";
00959 
00960   return tmpStr;
00961 }
00962 
00963 static QString displayViewFormatFreeBusy( Calendar * /*calendar*/, FreeBusy *fb )
00964 {
00965   if ( !fb ) {
00966     return QString::null;
00967   }
00968 
00969   QString tmpStr = htmlAddTag( "h2",
00970                                htmlAddTag( "b",
00971                                            i18n("Free/Busy information for %1").
00972                                            arg( fb->organizer().fullName() ) ) );
00973 
00974   tmpStr += htmlAddTag( "h4", i18n("Busy times in date range %1 - %2:").
00975                         arg( IncidenceFormatter::dateToString( fb->dtStart(), true ) ).
00976                         arg( IncidenceFormatter::dateToString( fb->dtEnd(), true ) ) );
00977 
00978   QValueList<Period> periods = fb->busyPeriods();
00979 
00980   QString text = htmlAddTag( "em", htmlAddTag( "b", i18n("Busy:") ) );
00981   QValueList<Period>::iterator it;
00982   for ( it = periods.begin(); it != periods.end(); ++it ) {
00983     Period per = *it;
00984     if ( per.hasDuration() ) {
00985       int dur = per.duration().asSeconds();
00986       QString cont;
00987       if ( dur >= 3600 ) {
00988         cont += i18n("1 hour ", "%n hours ", dur / 3600 );
00989         dur %= 3600;
00990       }
00991       if ( dur >= 60 ) {
00992         cont += i18n("1 minute ", "%n minutes ", dur / 60);
00993         dur %= 60;
00994       }
00995       if ( dur > 0 ) {
00996         cont += i18n("1 second", "%n seconds", dur);
00997       }
00998       text += i18n("startDate for duration", "%1 for %2").
00999               arg( IncidenceFormatter::dateTimeToString( per.start(), false, true ) ).
01000               arg( cont );
01001       text += "<br>";
01002     } else {
01003       if ( per.start().date() == per.end().date() ) {
01004         text += i18n("date, fromTime - toTime ", "%1, %2 - %3").
01005                 arg( IncidenceFormatter::dateToString( per.start().date(), true ) ).
01006                 arg( IncidenceFormatter::timeToString( per.start(), true ) ).
01007                 arg( IncidenceFormatter::timeToString( per.end(), true ) );
01008       } else {
01009         text += i18n("fromDateTime - toDateTime", "%1 - %2").
01010                 arg( IncidenceFormatter::dateTimeToString( per.start(), false, true ) ).
01011                 arg( IncidenceFormatter::dateTimeToString( per.end(), false, true ) );
01012       }
01013       text += "<br>";
01014     }
01015   }
01016   tmpStr += htmlAddTag( "p", text );
01017   return tmpStr;
01018 }
01019 
01020 class IncidenceFormatter::EventViewerVisitor : public IncidenceBase::Visitor
01021 {
01022   public:
01023     EventViewerVisitor()
01024       : mCalendar( 0 ), mResult( "" ) {}
01025 
01026     bool act( Calendar *calendar, IncidenceBase *incidence, const QDate &date )
01027     {
01028       mCalendar = calendar;
01029       mDate = date;
01030       mResult = "";
01031       return incidence->accept( *this );
01032     }
01033     QString result() const { return mResult; }
01034 
01035   protected:
01036     bool visit( Event *event )
01037     {
01038       mResult = displayViewFormatEvent( mCalendar, event, mDate );
01039       return !mResult.isEmpty();
01040     }
01041     bool visit( Todo *todo )
01042     {
01043       mResult = displayViewFormatTodo( mCalendar, todo, mDate );
01044       return !mResult.isEmpty();
01045     }
01046     bool visit( Journal *journal )
01047     {
01048       mResult = displayViewFormatJournal( mCalendar, journal );
01049       return !mResult.isEmpty();
01050     }
01051     bool visit( FreeBusy *fb )
01052     {
01053       mResult = displayViewFormatFreeBusy( mCalendar, fb );
01054       return !mResult.isEmpty();
01055     }
01056 
01057   protected:
01058     Calendar *mCalendar;
01059     QDate mDate;
01060     QString mResult;
01061 };
01062 
01063 QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence )
01064 {
01065   return extensiveDisplayStr( 0, incidence, QDate() );
01066 }
01067 
01068 QString IncidenceFormatter::extensiveDisplayStr( Calendar *calendar,
01069                                                  IncidenceBase *incidence,
01070                                                  const QDate &date )
01071 {
01072   if ( !incidence ) {
01073     return QString::null;
01074   }
01075 
01076   EventViewerVisitor v;
01077   if ( v.act( calendar, incidence, date ) ) {
01078     return v.result();
01079   } else {
01080     return QString::null;
01081   }
01082 }
01083 
01084 /***********************************************************************
01085  *  Helper functions for the body part formatter of kmail (Invitations)
01086  ***********************************************************************/
01087 
01088 static QString string2HTML( const QString& str )
01089 {
01090   return QStyleSheet::convertFromPlainText(str, QStyleSheetItem::WhiteSpaceNormal);
01091 }
01092 
01093 static QString cleanHtml( const QString &html )
01094 {
01095   QRegExp rx( "<body[^>]*>(.*)</body>" );
01096   rx.setCaseSensitive( false );
01097   rx.search( html );
01098   QString body = rx.cap( 1 );
01099 
01100   return QStyleSheet::escape( body.remove( QRegExp( "<[^>]*>" ) ).stripWhiteSpace() );
01101 }
01102 
01103 static QString invitationSummary( Incidence *incidence, bool noHtmlMode )
01104 {
01105   QString summaryStr = i18n( "Summary unspecified" );
01106   if ( !incidence->summary().isEmpty() ) {
01107     if ( !QStyleSheet::mightBeRichText( incidence->summary() ) ) {
01108       summaryStr = QStyleSheet::escape( incidence->summary() );
01109     } else {
01110       summaryStr = incidence->summary();
01111       if ( noHtmlMode ) {
01112         summaryStr = cleanHtml( summaryStr );
01113       }
01114     }
01115   }
01116   return summaryStr;
01117 }
01118 
01119 static QString invitationLocation( Incidence *incidence, bool noHtmlMode )
01120 {
01121   QString locationStr = i18n( "Location unspecified" );
01122   if ( !incidence->location().isEmpty() ) {
01123     if ( !QStyleSheet::mightBeRichText( incidence->location() ) ) {
01124       locationStr = QStyleSheet::escape( incidence->location() );
01125     } else {
01126       locationStr = incidence->location();
01127       if ( noHtmlMode ) {
01128         locationStr = cleanHtml( locationStr );
01129       }
01130     }
01131   }
01132   return locationStr;
01133 }
01134 
01135 static QString eventStartTimeStr( Event *event )
01136 {
01137   QString tmp;
01138   if ( !event->doesFloat() ) {
01139     tmp = i18n( "%1: Start Date, %2: Start Time", "%1 %2" ).
01140           arg( IncidenceFormatter::dateToString( event->dtStart(), true ),
01141                IncidenceFormatter::timeToString( event->dtStart(), true ) );
01142   } else {
01143     tmp = i18n( "%1: Start Date", "%1 (all day)" ).
01144           arg( IncidenceFormatter::dateToString( event->dtStart(), true ) );
01145   }
01146   return tmp;
01147 }
01148 
01149 static QString eventEndTimeStr( Event *event )
01150 {
01151   QString tmp;
01152   if ( event->hasEndDate() && event->dtEnd().isValid() ) {
01153     if ( !event->doesFloat() ) {
01154       tmp = i18n( "%1: End Date, %2: End Time", "%1 %2" ).
01155             arg( IncidenceFormatter::dateToString( event->dtEnd(), true ),
01156                  IncidenceFormatter::timeToString( event->dtEnd(), true ) );
01157     } else {
01158       tmp = i18n( "%1: End Date", "%1 (all day)" ).
01159             arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) );
01160     }
01161   }
01162   return tmp;
01163 }
01164 
01165 static QString htmlInvitationDetailsBegin()
01166 {
01167   QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
01168   return QString( "<div dir=\"%1\">\n" ).arg( dir );
01169 }
01170 
01171 static QString htmlInvitationDetailsEnd()
01172 {
01173   return "</div>\n";
01174 }
01175 
01176 static QString htmlInvitationDetailsTableBegin()
01177 {
01178   return "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n";
01179 }
01180 
01181 static QString htmlInvitationDetailsTableEnd()
01182 {
01183   return "</table>\n";
01184 }
01185 
01186 static QString diffColor()
01187 {
01188   // Color for printing comparison differences inside invitations.
01189 
01190 //  return  "#DE8519"; // hard-coded color from Outlook2007
01191   return QColor( Qt::red ).name();
01192 }
01193 
01194 static QString noteColor()
01195 {
01196   // Color for printing notes inside invitations.
01197 
01198 //  return qApp->palette().color( QPalette::Active, QPalette::Highlight ).name();
01199   return QColor( Qt::red ).name();
01200 }
01201 
01202 static QString htmlRow( const QString &title, const QString &value )
01203 {
01204   if ( !value.isEmpty() ) {
01205     return "<tr><td>" + title + "</td><td>" + value + "</td></tr>\n";
01206   } else {
01207     return QString::null;
01208   }
01209 }
01210 
01211 static QString htmlRow( const QString &title, const QString &value, const QString &oldvalue )
01212 {
01213   // if 'value' is empty, then print nothing
01214   if ( value.isEmpty() ) {
01215     return QString::null;
01216   }
01217 
01218   // if 'value' is new or unchanged, then print normally
01219   if ( oldvalue.isEmpty() || value == oldvalue ) {
01220     return htmlRow( title, value );
01221   }
01222 
01223   // if 'value' has changed, then make a special print
01224   QString color = diffColor();
01225   QString newtitle = "<font color=\"" + color + "\">" + title + "</font>";
01226   QString newvalue = "<font color=\"" + color + "\">" + value + "</font>" +
01227                      "&nbsp;" +
01228                      "(<strike>" + oldvalue + "</strike>)";
01229   return htmlRow( newtitle, newvalue );
01230 }
01231 
01232 static Attendee *findDelegatedFromMyAttendee( Incidence *incidence )
01233 {
01234   // Return the first attendee that was delegated-from me
01235 
01236   Attendee *attendee = 0;
01237   if ( !incidence ) {
01238     return attendee;
01239   }
01240 
01241   KEMailSettings settings;
01242   QStringList profiles = settings.profiles();
01243   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
01244     settings.setProfile( *it );
01245 
01246     QString delegatorName, delegatorEmail;
01247     Attendee::List attendees = incidence->attendees();
01248     Attendee::List::ConstIterator it2;
01249     for ( it2 = attendees.begin(); it2 != attendees.end(); ++it2 ) {
01250       Attendee *a = *it2;
01251       KPIM::getNameAndMail( a->delegator(), delegatorName, delegatorEmail );
01252       if ( settings.getSetting( KEMailSettings::EmailAddress ) == delegatorEmail ) {
01253         attendee = a;
01254         break;
01255       }
01256     }
01257   }
01258   return attendee;
01259 }
01260 
01261 static Attendee *findMyAttendee( Incidence *incidence )
01262 {
01263   // Return the attendee for the incidence that is probably me
01264 
01265   Attendee *attendee = 0;
01266   if ( !incidence ) {
01267     return attendee;
01268   }
01269 
01270   KEMailSettings settings;
01271   QStringList profiles = settings.profiles();
01272   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
01273     settings.setProfile( *it );
01274 
01275     Attendee::List attendees = incidence->attendees();
01276     Attendee::List::ConstIterator it2;
01277     for ( it2 = attendees.begin(); it2 != attendees.end(); ++it2 ) {
01278       Attendee *a = *it2;
01279       if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) {
01280         attendee = a;
01281         break;
01282       }
01283     }
01284   }
01285   return attendee;
01286 }
01287 
01288 static Attendee *findAttendee( Incidence *incidence, const QString &email )
01289 {
01290   // Search for an attendee by email address
01291 
01292   Attendee *attendee = 0;
01293   if ( !incidence ) {
01294     return attendee;
01295   }
01296 
01297   Attendee::List attendees = incidence->attendees();
01298   Attendee::List::ConstIterator it;
01299   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
01300     Attendee *a = *it;
01301     if ( email == a->email() ) {
01302       attendee = a;
01303       break;
01304     }
01305   }
01306   return attendee;
01307 }
01308 
01309 static bool rsvpRequested( Incidence *incidence )
01310 {
01311   if ( !incidence ) {
01312     return false;
01313   }
01314 
01315   //use a heuristic to determine if a response is requested.
01316 
01317   bool rsvp = true; // better send superfluously than not at all
01318   Attendee::List attendees = incidence->attendees();
01319   Attendee::List::ConstIterator it;
01320   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
01321     if ( it == attendees.begin() ) {
01322       rsvp = (*it)->RSVP(); // use what the first one has
01323     } else {
01324       if ( (*it)->RSVP() != rsvp ) {
01325         rsvp = true; // they differ, default
01326         break;
01327       }
01328     }
01329   }
01330   return rsvp;
01331 }
01332 
01333 static QString rsvpRequestedStr( bool rsvpRequested, const QString &role )
01334 {
01335   if ( rsvpRequested ) {
01336     if ( role.isEmpty() ) {
01337       return i18n( "Your response is requested" );
01338     } else {
01339       return i18n( "Your response as <b>%1</b> is requested" ).arg( role );
01340     }
01341   } else {
01342     if ( role.isEmpty() ) {
01343       return i18n( "No response is necessary" );
01344     } else {
01345       return i18n( "No response as <b>%1</b> is necessary" ).arg( role );
01346     }
01347   }
01348 }
01349 
01350 static QString myStatusStr( Incidence *incidence )
01351 {
01352   QString ret;
01353   Attendee *a = findMyAttendee( incidence );
01354   if ( a &&
01355        a->status() != Attendee::NeedsAction && a->status() != Attendee::Delegated ) {
01356     ret = i18n( "(<b>Note</b>: the Organizer preset your response to <b>%1</b>)" ).
01357           arg( Attendee::statusName( a->status() ) );
01358   }
01359   return ret;
01360 }
01361 
01362 static QString invitationNote( const QString &title, const QString &note,
01363                                const QString &tag, const QString &color )
01364 {
01365   QString noteStr;
01366   if ( !note.isEmpty() ) {
01367     noteStr += "<table border=\"0\" style=\"margin-top:4px;\">";
01368     noteStr += "<tr><center><td>";
01369     if ( !color.isEmpty() ) {
01370       noteStr += "<font color=\"" + color + "\">";
01371     }
01372     if ( !title.isEmpty() ) {
01373       if ( !tag.isEmpty() ) {
01374         noteStr += htmlAddTag( tag, title );
01375       } else {
01376         noteStr += title;
01377       }
01378     }
01379     noteStr += "&nbsp;" + note;
01380     if ( !color.isEmpty() ) {
01381       noteStr += "</font>";
01382     }
01383     noteStr += "</td></center></tr>";
01384     noteStr += "</table>";
01385   }
01386   return noteStr;
01387 }
01388 
01389 static QString invitationPerson( const QString &email, const QString &name, const QString &uid,
01390                                  const QString &comment )
01391 {
01392   QPair<QString, QString> s = searchNameAndUid( email, name, uid );
01393   const QString printName = s.first;
01394   const QString printUid = s.second;
01395 
01396   QString personString;
01397   // Make the uid link
01398   if ( !printUid.isEmpty() ) {
01399     personString = htmlAddUidLink( email, printName, printUid );
01400   } else {
01401     // No UID, just show some text
01402     personString = ( printName.isEmpty() ? email : printName );
01403   }
01404   if ( !comment.isEmpty() ) {
01405     personString = i18n( "name (comment)", "%1 (%2)" ).arg( personString ).arg( comment );
01406   }
01407   personString += '\n';
01408 
01409   // Make the mailto link
01410   if ( !email.isEmpty() ) {
01411     personString += "&nbsp;" + htmlAddMailtoLink( email, printName );
01412   }
01413   personString += "\n";
01414 
01415   return personString;
01416 }
01417 
01418 static QString invitationDetailsIncidence( Incidence *incidence, bool noHtmlMode )
01419 {
01420   // if description and comment -> use both
01421   // if description, but no comment -> use the desc as the comment (and no desc)
01422   // if comment, but no description -> use the comment and no description
01423 
01424   QString html;
01425   QString descr;
01426   QStringList comments;
01427 
01428   if ( incidence->comments().isEmpty() ) {
01429     if ( !incidence->description().isEmpty() ) {
01430       // use description as comments
01431       if ( !QStyleSheet::mightBeRichText( incidence->description() ) ) {
01432         comments << string2HTML( incidence->description() );
01433       } else {
01434         comments << incidence->description();
01435         if ( noHtmlMode ) {
01436           comments[0] = cleanHtml( comments[0] );
01437         }
01438         comments[0] = htmlAddTag( "p", comments[0] );
01439       }
01440     }
01441     //else desc and comments are empty
01442   } else {
01443     // non-empty comments
01444     QStringList cl = incidence->comments();
01445     uint i = 0;
01446     for( QStringList::Iterator it=cl.begin(); it!=cl.end(); ++it ) {
01447       if ( !QStyleSheet::mightBeRichText( *it ) ) {
01448         comments.append( string2HTML( *it ) );
01449       } else {
01450         if ( noHtmlMode ) {
01451           comments.append( cleanHtml( "<body>" + (*it) + "</body>" ) );
01452         } else {
01453           comments.append( *it );
01454         }
01455       }
01456       i++;
01457     }
01458     if ( !incidence->description().isEmpty() ) {
01459       // use description too
01460       if ( !QStyleSheet::mightBeRichText( incidence->description() ) ) {
01461         descr = string2HTML( incidence->description() );
01462       } else {
01463         descr = incidence->description();
01464         if ( noHtmlMode ) {
01465           descr = cleanHtml( descr );
01466         }
01467         descr = htmlAddTag( "p", descr );
01468       }
01469     }
01470   }
01471 
01472   if( !descr.isEmpty() ) {
01473     html += "<p>";
01474     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01475     html += "<tr><td><center>" +
01476             htmlAddTag( "u", i18n( "Description:" ) ) +
01477             "</center></td></tr>";
01478     html += "<tr><td>" + descr + "</td></tr>";
01479     html += "</table>";
01480   }
01481 
01482   if ( !comments.isEmpty() ) {
01483     html += "<p>";
01484     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01485     html += "<tr><td><center>" +
01486             htmlAddTag( "u", i18n( "Comments:" ) ) +
01487             "</center></td></tr>";
01488     html += "<tr><td>";
01489     if ( comments.count() > 1 ) {
01490       html += "<ul>";
01491       for ( uint i=0; i < comments.count(); ++i ) {
01492         html += "<li>" + comments[i] + "</li>";
01493       }
01494       html += "</ul>";
01495     } else {
01496       html += comments[0];
01497     }
01498     html += "</td></tr>";
01499     html += "</table>";
01500   }
01501   return html;
01502 }
01503 
01504 static QString invitationDetailsEvent( Event* event, bool noHtmlMode )
01505 {
01506   // Invitation details are formatted into an HTML table
01507   if ( !event ) {
01508     return QString::null;
01509   }
01510 
01511   QString html = htmlInvitationDetailsBegin();
01512   html += htmlInvitationDetailsTableBegin();
01513 
01514   // Invitation summary & location rows
01515   html += htmlRow( i18n( "What:" ), invitationSummary( event, noHtmlMode ) );
01516   html += htmlRow( i18n( "Where:" ), invitationLocation( event, noHtmlMode ) );
01517 
01518   // If a 1 day event
01519   if ( event->dtStart().date() == event->dtEnd().date() ) {
01520     html += htmlRow( i18n( "Date:" ),
01521                      IncidenceFormatter::dateToString( event->dtStart(), false ) );
01522     if ( !event->doesFloat() ) {
01523       html += htmlRow( i18n( "Time:" ),
01524                        IncidenceFormatter::timeToString( event->dtStart(), true ) +
01525                        " - " +
01526                        IncidenceFormatter::timeToString( event->dtEnd(), true ) );
01527     }
01528   } else {
01529     html += htmlRow( i18n( "Starting date of an event", "From:" ),
01530                      IncidenceFormatter::dateToString( event->dtStart(), false ) );
01531     if ( !event->doesFloat() ) {
01532       html += htmlRow( i18n( "Starting time of an event", "At:" ),
01533                        IncidenceFormatter::timeToString( event->dtStart(), true ) );
01534     }
01535     if ( event->hasEndDate() ) {
01536       html += htmlRow( i18n( "Ending date of an event", "To:" ),
01537                        IncidenceFormatter::dateToString( event->dtEnd(), false ) );
01538       if ( !event->doesFloat() ) {
01539         html += htmlRow( i18n( "Starting time of an event", "At:" ),
01540                          IncidenceFormatter::timeToString( event->dtEnd(), true ) );
01541       }
01542     } else {
01543       html += htmlRow( i18n( "Ending date of an event", "To:" ),
01544                        i18n( "no end date specified" ) );
01545     }
01546   }
01547 
01548   // Invitation Duration Row
01549   html += htmlRow( i18n( "Duration:" ), IncidenceFormatter::durationString( event ) );
01550 
01551   // Invitation Recurrence Row
01552   if ( event->doesRecur() ) {
01553     html += htmlRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( event ) );
01554   }
01555 
01556   html += htmlInvitationDetailsTableEnd();
01557   html += invitationDetailsIncidence( event, noHtmlMode );
01558   html += htmlInvitationDetailsEnd();
01559 
01560   return html;
01561 }
01562 
01563 static QString invitationDetailsEvent( Event *event, Event *oldevent, ScheduleMessage *message,
01564                                        bool noHtmlMode )
01565 {
01566   if ( !oldevent ) {
01567     return invitationDetailsEvent( event, noHtmlMode );
01568   }
01569 
01570   QString html;
01571 
01572   // Print extra info typically dependent on the iTIP
01573   if ( message->method() == Scheduler::Declinecounter ) {
01574     html += "<br>";
01575     html += invitationNote( QString::null,
01576                             i18n( "Please respond again to the original proposal." ),
01577                             QString::null, noteColor() );
01578   }
01579 
01580   html += htmlInvitationDetailsBegin();
01581   html += htmlInvitationDetailsTableBegin();
01582 
01583   html += htmlRow( i18n( "What:" ),
01584                    invitationSummary( event, noHtmlMode ),
01585                    invitationSummary( oldevent, noHtmlMode ) );
01586 
01587   html += htmlRow( i18n( "Where:" ),
01588                    invitationLocation( event, noHtmlMode ),
01589                    invitationLocation( oldevent, noHtmlMode ) );
01590 
01591 
01592   // If a 1 day event
01593   if ( event->dtStart().date() == event->dtEnd().date() ) {
01594     QDateTime newDateToUse = event->dtStart();
01595     QDateTime oldDateToUse = oldevent->dtStart();
01596     const int exDatesCount = event->recurrence()->exDates().count();
01597     if ( event->doesRecur() && oldevent->doesRecur() &&
01598          exDatesCount == oldevent->recurrence()->exDates().count() + 1 &&
01599          event->dtStart() == oldevent->dtStart() && event->dtEnd() == oldevent->dtEnd() )
01600     {
01601       // kolab/issue4735 - When you delete an occurrence of a recurring event, the date
01602       // of the occurrence should be used. This is a bit of an hack because we don't support
01603       // recurrence-id yet
01604       newDateToUse = QDateTime( event->recurrence()->exDates()[exDatesCount-1], QTime( -1, -1 ) );
01605       oldDateToUse = newDateToUse;
01606     }
01607 
01608     html += htmlRow( i18n( "Date:" ),
01609                      IncidenceFormatter::dateToString( newDateToUse, false ),
01610                      IncidenceFormatter::dateToString( oldDateToUse, false ) );
01611     QString spanStr , oldspanStr;
01612     if ( !event->doesFloat() ) {
01613       spanStr = IncidenceFormatter::timeToString( event->dtStart(), true ) +
01614                 " - " +
01615                 IncidenceFormatter::timeToString( event->dtEnd(), true );
01616     }
01617     if ( !oldevent->doesFloat() ) {
01618       oldspanStr = IncidenceFormatter::timeToString( oldevent->dtStart(), true ) +
01619                    " - " +
01620                    IncidenceFormatter::timeToString( oldevent->dtEnd(), true );
01621     }
01622     html += htmlRow( i18n( "Time:" ), spanStr, oldspanStr );
01623   } else {
01624     html += htmlRow( i18n( "Starting date of an event", "From:" ),
01625                      IncidenceFormatter::dateToString( event->dtStart(), false ),
01626                      IncidenceFormatter::dateToString( oldevent->dtStart(), false ) );
01627     QString startStr, oldstartStr;
01628     if ( !event->doesFloat() ) {
01629       startStr = IncidenceFormatter::timeToString( event->dtStart(), true );
01630     }
01631     if ( !oldevent->doesFloat() ) {
01632       oldstartStr = IncidenceFormatter::timeToString( oldevent->dtStart(), true );
01633     }
01634     html += htmlRow( i18n( "Starting time of an event", "At:" ), startStr, oldstartStr );
01635 
01636     if ( event->hasEndDate() ) {
01637       html += htmlRow( i18n( "Ending date of an event", "To:" ),
01638                        IncidenceFormatter::dateToString( event->dtEnd(), false ),
01639                        IncidenceFormatter::dateToString( oldevent->dtEnd(), false ) );
01640       QString endStr, oldendStr;
01641       if ( !event->doesFloat() ) {
01642         endStr = IncidenceFormatter::timeToString( event->dtEnd(), true );
01643       }
01644       if ( !oldevent->doesFloat() ) {
01645         oldendStr = IncidenceFormatter::timeToString( oldevent->dtEnd(), true );
01646       }
01647       html += htmlRow( i18n( "Starting time of an event", "At:" ), endStr, oldendStr );
01648     } else {
01649       QString endStr = i18n( "no end date specified" );
01650       QString oldendStr;
01651       if ( !oldevent->hasEndDate() ) {
01652         oldendStr = i18n( "no end date specified" );
01653       } else {
01654         oldendStr = IncidenceFormatter::dateTimeToString( oldevent->dtEnd(),
01655                                                           oldevent->doesFloat(), false );
01656       }
01657       html += htmlRow( i18n( "Ending date of an event", "To:" ), endStr, oldendStr );
01658     }
01659   }
01660 
01661   html += htmlRow( i18n( "Duration:" ),
01662                    IncidenceFormatter::durationString( event ),
01663                    IncidenceFormatter::durationString( oldevent ) );
01664 
01665   QString recurStr, oldrecurStr;
01666   if ( event->doesRecur() || oldevent->doesRecur() ) {
01667     recurStr = IncidenceFormatter::recurrenceString( event );
01668     oldrecurStr = IncidenceFormatter::recurrenceString( oldevent );
01669   }
01670   html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr );
01671 
01672   html += htmlInvitationDetailsTableEnd();
01673   html += invitationDetailsIncidence( event, noHtmlMode );
01674   html += htmlInvitationDetailsEnd();
01675 
01676   return html;
01677 }
01678 
01679 static QString invitationDetailsTodo( Todo *todo, bool noHtmlMode )
01680 {
01681   // Task details are formatted into an HTML table
01682   if ( !todo ) {
01683     return QString::null;
01684   }
01685 
01686   QString html = htmlInvitationDetailsBegin();
01687   html += htmlInvitationDetailsTableBegin();
01688 
01689   // Invitation summary & location rows
01690   html += htmlRow( i18n( "What:" ), invitationSummary( todo, noHtmlMode ) );
01691   html += htmlRow( i18n( "Where:" ), invitationLocation( todo, noHtmlMode ) );
01692 
01693   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01694     html += htmlRow( i18n( "Start Date:" ),
01695                      IncidenceFormatter::dateToString( todo->dtStart(), false ) );
01696     if ( !todo->doesFloat() ) {
01697       html += htmlRow( i18n( "Start Time:" ),
01698                        IncidenceFormatter::timeToString( todo->dtStart(), false ) );
01699     }
01700   }
01701   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01702     html += htmlRow( i18n( "Due Date:" ),
01703                      IncidenceFormatter::dateToString( todo->dtDue(), false ) );
01704     if ( !todo->doesFloat() ) {
01705       html += htmlRow( i18n( "Due Time:" ),
01706                        IncidenceFormatter::timeToString( todo->dtDue(), false ) );
01707     }
01708   } else {
01709     html += htmlRow( i18n( "Due Date:" ), i18n( "Due Date: None", "None" ) );
01710   }
01711 
01712   // Invitation Duration Row
01713   html += htmlRow( i18n( "Duration:" ), IncidenceFormatter::durationString( todo ) );
01714 
01715   // Completeness
01716   if ( todo->percentComplete() > 0 ) {
01717     html += htmlRow( i18n( "Percent Done:" ), i18n( "%1%" ).arg( todo->percentComplete() ) );
01718   }
01719 
01720   // Invitation Recurrence Row
01721   if ( todo->doesRecur() ) {
01722     html += htmlRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( todo ) );
01723   }
01724 
01725   html += htmlInvitationDetailsTableEnd();
01726   html += invitationDetailsIncidence( todo, noHtmlMode );
01727   html += htmlInvitationDetailsEnd();
01728 
01729   return html;
01730 }
01731 
01732 static QString invitationDetailsTodo( Todo *todo, Todo *oldtodo, ScheduleMessage *message,
01733                                       bool noHtmlMode )
01734 {
01735   if ( !oldtodo ) {
01736     return invitationDetailsTodo( todo, noHtmlMode );
01737   }
01738 
01739   QString html;
01740 
01741   // Print extra info typically dependent on the iTIP
01742   if ( message->method() == Scheduler::Declinecounter ) {
01743     html += "<br>";
01744     html += invitationNote( QString::null,
01745                             i18n( "Please respond again to the original proposal." ),
01746                             QString::null, noteColor() );
01747   }
01748 
01749   html += htmlInvitationDetailsBegin();
01750   html += htmlInvitationDetailsTableBegin();
01751 
01752   html += htmlRow( i18n( "What:" ),
01753                    invitationSummary( todo, noHtmlMode ),
01754                    invitationSummary( todo, noHtmlMode ) );
01755 
01756   html += htmlRow( i18n( "Where:" ),
01757                    invitationLocation( todo, noHtmlMode ),
01758                    invitationLocation( oldtodo, noHtmlMode ) );
01759 
01760   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01761     html += htmlRow( i18n( "Start Date:" ),
01762                      IncidenceFormatter::dateToString( todo->dtStart(), false ),
01763                      IncidenceFormatter::dateToString( oldtodo->dtStart(), false ) );
01764     QString startTimeStr, oldstartTimeStr;
01765     if ( !todo->doesFloat() || !oldtodo->doesFloat() ) {
01766       startTimeStr = todo->doesFloat() ?
01767                      i18n( "All day" ) :
01768                      IncidenceFormatter::timeToString( todo->dtStart(), false );
01769       oldstartTimeStr = oldtodo->doesFloat() ?
01770                         i18n( "All day" ) :
01771                         IncidenceFormatter::timeToString( oldtodo->dtStart(), false );
01772     }
01773     html += htmlRow( i18n( "Start Time:" ), startTimeStr, oldstartTimeStr );
01774   }
01775 
01776   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01777     html += htmlRow( i18n( "Due Date:" ),
01778                      IncidenceFormatter::dateToString( todo->dtDue(), false ),
01779                      IncidenceFormatter::dateToString( oldtodo->dtDue(), false ) );
01780     QString endTimeStr, oldendTimeStr;
01781     if ( !todo->doesFloat() || !oldtodo->doesFloat() ) {
01782       endTimeStr = todo->doesFloat() ?
01783                    i18n( "All day" ) :
01784                    IncidenceFormatter::timeToString( todo->dtDue(), false );
01785       oldendTimeStr = oldtodo->doesFloat() ?
01786                       i18n( "All day" ) :
01787                       IncidenceFormatter::timeToString( oldtodo->dtDue(), false );
01788     }
01789     html += htmlRow( i18n( "Due Time:" ), endTimeStr, oldendTimeStr );
01790   } else {
01791     QString dueStr = i18n( "Due Date: None", "None" );
01792     QString olddueStr;
01793     if ( !oldtodo->hasDueDate() || !oldtodo->dtDue().isValid() ) {
01794       olddueStr = i18n( "Due Date: None", "None" );
01795     } else {
01796       olddueStr = IncidenceFormatter::dateTimeToString( oldtodo->dtDue(),
01797                                                         oldtodo->doesFloat(), false );
01798     }
01799     html += htmlRow( i18n( "Due Date:" ), dueStr, olddueStr );
01800   }
01801 
01802   html += htmlRow( i18n( "Duration:" ),
01803                    IncidenceFormatter::durationString( todo ),
01804                    IncidenceFormatter::durationString( oldtodo ) );
01805 
01806   QString completionStr, oldcompletionStr;
01807   if ( todo->percentComplete() > 0 || oldtodo->percentComplete() > 0 ) {
01808     completionStr = i18n( "%1%" ).arg( todo->percentComplete() );
01809     oldcompletionStr = i18n( "%1%" ).arg( oldtodo->percentComplete() );
01810   }
01811   html += htmlRow( i18n( "Percent Done:" ), completionStr, oldcompletionStr );
01812 
01813   QString recurStr, oldrecurStr;
01814   if ( todo->doesRecur() || oldtodo->doesRecur() ) {
01815     recurStr = IncidenceFormatter::recurrenceString( todo );
01816     oldrecurStr = IncidenceFormatter::recurrenceString( oldtodo );
01817   }
01818   html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr );
01819 
01820   html += htmlInvitationDetailsTableEnd();
01821   html += invitationDetailsIncidence( todo, noHtmlMode );
01822   html += htmlInvitationDetailsEnd();
01823 
01824   return html;
01825 }
01826 
01827 static QString invitationDetailsJournal( Journal *journal, bool noHtmlMode )
01828 {
01829   if ( !journal ) {
01830     return QString::null;
01831   }
01832 
01833   QString html = htmlInvitationDetailsBegin();
01834   html += htmlInvitationDetailsTableBegin();
01835 
01836   html += htmlRow( i18n( "Summary:" ), invitationSummary( journal, noHtmlMode ) );
01837   html += htmlRow( i18n( "Date:" ), IncidenceFormatter::dateToString( journal->dtStart(), false ) );
01838 
01839   html += htmlInvitationDetailsTableEnd();
01840   html += invitationDetailsIncidence( journal, noHtmlMode );
01841   html += htmlInvitationDetailsEnd();
01842 
01843   return html;
01844 }
01845 
01846 static QString invitationDetailsJournal( Journal *journal, Journal *oldjournal, bool noHtmlMode )
01847 {
01848   if ( !oldjournal ) {
01849     return invitationDetailsJournal( journal, noHtmlMode );
01850   }
01851 
01852   QString html = htmlInvitationDetailsBegin();
01853   html += htmlInvitationDetailsTableBegin();
01854 
01855   html += htmlRow( i18n( "What:" ),
01856                    invitationSummary( journal, noHtmlMode ),
01857                    invitationSummary( oldjournal, noHtmlMode ) );
01858 
01859   html += htmlRow( i18n( "Date:" ),
01860                    IncidenceFormatter::dateToString( journal->dtStart(), false ),
01861                    IncidenceFormatter::dateToString( oldjournal->dtStart(), false ) );
01862 
01863   html += htmlInvitationDetailsTableEnd();
01864   html += invitationDetailsIncidence( journal, noHtmlMode );
01865   html += htmlInvitationDetailsEnd();
01866 
01867   return html;
01868 }
01869 
01870 static QString invitationDetailsFreeBusy( FreeBusy *fb, bool /*noHtmlMode*/ )
01871 {
01872   if ( !fb ) {
01873     return QString::null;
01874   }
01875 
01876   QString html = htmlInvitationDetailsTableBegin();
01877 
01878   html += htmlRow( i18n("Person:"), fb->organizer().fullName() );
01879   html += htmlRow( i18n("Start date:"),
01880                    IncidenceFormatter::dateToString( fb->dtStart(), true ) );
01881   html += htmlRow( i18n("End date:"),
01882                    KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) );
01883   html += "<tr><td colspan=2><hr></td></tr>\n";
01884   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
01885 
01886   QValueList<Period> periods = fb->busyPeriods();
01887 
01888   QValueList<Period>::iterator it;
01889   for ( it = periods.begin(); it != periods.end(); ++it ) {
01890     Period per = *it;
01891     if ( per.hasDuration() ) {
01892       int dur = per.duration().asSeconds();
01893       QString cont;
01894       if ( dur >= 3600 ) {
01895         cont += i18n("1 hour ", "%n hours ", dur / 3600);
01896         dur %= 3600;
01897       }
01898       if ( dur >= 60 ) {
01899         cont += i18n("1 minute", "%n minutes ", dur / 60);
01900         dur %= 60;
01901       }
01902       if ( dur > 0 ) {
01903         cont += i18n("1 second", "%n seconds", dur);
01904       }
01905       html += htmlRow( QString::null, i18n("startDate for duration", "%1 for %2").
01906                        arg( KGlobal::locale()->formatDateTime( per.start(), false ) ).
01907                        arg(cont) );
01908     } else {
01909       QString cont;
01910       if ( per.start().date() == per.end().date() ) {
01911         cont = i18n("date, fromTime - toTime ", "%1, %2 - %3")
01912             .arg( KGlobal::locale()->formatDate( per.start().date() ) )
01913             .arg( KGlobal::locale()->formatTime( per.start().time() ) )
01914             .arg( KGlobal::locale()->formatTime( per.end().time() ) );
01915       } else {
01916         cont = i18n("fromDateTime - toDateTime", "%1 - %2")
01917           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
01918           .arg( KGlobal::locale()->formatDateTime( per.end(), false ) );
01919       }
01920 
01921       html += htmlRow( QString::null, cont );
01922     }
01923   }
01924 
01925   html += htmlInvitationDetailsTableEnd();
01926   return html;
01927 }
01928 
01929 static QString invitationDetailsFreeBusy( FreeBusy *freebusy, FreeBusy *oldfreebusy, bool noHtmlMode )
01930 {
01931   Q_UNUSED( oldfreebusy );
01932   return invitationDetailsFreeBusy( freebusy, noHtmlMode );
01933 }
01934 
01935 static bool replyMeansCounter( Incidence */*incidence*/ )
01936 {
01937   return false;
01952 }
01953 
01954 static QString invitationHeaderEvent( Event *event, Incidence *existingIncidence,
01955                                       ScheduleMessage *msg, const QString &sender )
01956 {
01957   if ( !msg || !event )
01958     return QString::null;
01959 
01960   switch ( msg->method() ) {
01961   case Scheduler::Publish:
01962     return i18n( "This invitation has been published" );
01963   case Scheduler::Request:
01964     if ( existingIncidence && event->revision() > 0 ) {
01965       QString orgStr = organizerName( event, sender );
01966       if ( senderIsOrganizer( event, sender ) ) {
01967         return i18n( "This invitation has been updated by the organizer %1" ).arg( orgStr );
01968       } else {
01969         return i18n( "This invitation has been updated by %1 as a representative of %2" ).
01970           arg( sender, orgStr );
01971       }
01972     }
01973     if ( iamOrganizer( event ) ) {
01974       return i18n( "I created this invitation" );
01975     } else {
01976       QString orgStr = organizerName( event, sender );
01977       if ( senderIsOrganizer( event, sender ) ) {
01978         return i18n( "You received an invitation from %1" ).arg( orgStr );
01979       } else {
01980         return i18n( "You received an invitation from %1 as a representative of %2" ).
01981           arg( sender, orgStr );
01982       }
01983     }
01984   case Scheduler::Refresh:
01985     return i18n( "This invitation was refreshed" );
01986   case Scheduler::Cancel:
01987     if ( iamOrganizer( event ) ) {
01988       return i18n( "This invitation has been canceled" );
01989     } else {
01990       const Event *existingEvent = dynamic_cast<Event*>( existingIncidence );
01991       if ( existingEvent ) {
01992         // We check if the CANCEL message contains all attendees from the original
01993         // message, in this case we assume that the event has been canceled, otherwise
01994         // only a subset of the attendees (including us) has been disinvited.
01995         const Attendee::List oldAttendees = existingEvent->attendees();
01996         const Attendee::List newAttendees = event->attendees();
01997 
01998         bool hasAllAttendees = true;
01999         for ( unsigned int i = 0; i < oldAttendees.count(); ++i ) {
02000           bool containsAttendee = false;
02001           for ( unsigned int j = 0; j < newAttendees.count(); j++ ) {
02002             if ( oldAttendees[i]->email() == newAttendees[j]->email() ) {
02003               containsAttendee = true;
02004               break;
02005             }
02006           }
02007 
02008           if ( !containsAttendee ) {
02009             hasAllAttendees = false;
02010             break;
02011           }
02012         }
02013 
02014         if ( oldAttendees.count() != newAttendees.count() )
02015           hasAllAttendees = false;
02016 
02017         if ( hasAllAttendees )
02018           return i18n( "The organizer has canceled the event" );
02019       }
02020 
02021       return i18n( "The organizer has removed you from the invitation" );
02022     }
02023   case Scheduler::Add:
02024     return i18n( "Addition to the invitation" );
02025   case Scheduler::Reply:
02026   {
02027     if ( replyMeansCounter( event ) ) {
02028       return i18n( "%1 makes this counter proposal" ).arg( firstAttendeeName( event, sender ) );
02029     }
02030 
02031     Attendee::List attendees = event->attendees();
02032     if( attendees.count() == 0 ) {
02033       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
02034       return QString::null;
02035     }
02036     if( attendees.count() != 1 ) {
02037       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
02038                     << "but is " << attendees.count() << endl;
02039     }
02040     QString attendeeName = firstAttendeeName( event, sender );
02041 
02042     QString delegatorName, dummy;
02043     Attendee* attendee = *attendees.begin();
02044     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
02045     if ( delegatorName.isEmpty() ) {
02046       delegatorName = attendee->delegator();
02047     }
02048 
02049     switch( attendee->status() ) {
02050     case Attendee::NeedsAction:
02051       return i18n( "%1 indicates this invitation still needs some action" ).arg( attendeeName );
02052     case Attendee::Accepted:
02053       if ( event->revision() > 0 ) {
02054         if ( !sender.isEmpty() ) {
02055           return i18n( "This invitation has been updated by attendee %1" ).arg( sender );
02056         } else {
02057           return i18n( "This invitation has been updated by an attendee" );
02058         }
02059       } else {
02060         if ( delegatorName.isEmpty() ) {
02061           return i18n( "%1 accepts this invitation" ).arg( attendeeName );
02062         } else {
02063           return i18n( "%1 accepts this invitation on behalf of %2" ).
02064             arg( attendeeName ).arg( delegatorName );
02065         }
02066       }
02067     case Attendee::Tentative:
02068       if ( delegatorName.isEmpty() ) {
02069         return i18n( "%1 tentatively accepts this invitation" ).
02070           arg( attendeeName );
02071       } else {
02072         return i18n( "%1 tentatively accepts this invitation on behalf of %2" ).
02073           arg( attendeeName ).arg( delegatorName );
02074       }
02075     case Attendee::Declined:
02076       if ( delegatorName.isEmpty() ) {
02077         return i18n( "%1 declines this invitation" ).arg( attendeeName );
02078       } else {
02079         return i18n( "%1 declines this invitation on behalf of %2" ).
02080           arg( attendeeName ).arg( delegatorName );
02081       }
02082     case Attendee::Delegated: {
02083       QString delegate, dummy;
02084       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
02085       if ( delegate.isEmpty() ) {
02086         delegate = attendee->delegate();
02087       }
02088       if ( !delegate.isEmpty() ) {
02089         return i18n( "%1 has delegated this invitation to %2" ).
02090           arg( attendeeName ) .arg( delegate );
02091       } else {
02092         return i18n( "%1 has delegated this invitation" ).arg( attendeeName );
02093       }
02094     }
02095     case Attendee::Completed:
02096       return i18n( "This invitation is now completed" );
02097     case Attendee::InProcess:
02098       return i18n( "%1 is still processing the invitation" ).
02099         arg( attendeeName );
02100     default:
02101       return i18n( "Unknown response to this invitation" );
02102     }
02103     break;
02104   }
02105 
02106   case Scheduler::Counter:
02107     return i18n( "%1 makes this counter proposal" ).arg( firstAttendeeName( event, sender ) );
02108 
02109   case Scheduler::Declinecounter:
02110   {
02111     QString orgStr = organizerName( event, sender );
02112     if ( senderIsOrganizer( event, sender ) ) {
02113       return i18n( "%1 declines your counter proposal" ).arg( orgStr );
02114     } else {
02115       return i18n( "%1 declines your counter proposal on behalf of %2" ).arg( sender, orgStr );
02116     }
02117   }
02118 
02119   case Scheduler::NoMethod:
02120     return i18n("Error: iTIP message with unknown method: '%1'").
02121       arg( msg->method() );
02122   }
02123   return QString::null;
02124 }
02125 
02126 static QString invitationHeaderTodo( Todo *todo, Incidence *existingIncidence,
02127                                      ScheduleMessage *msg, const QString &sender )
02128 {
02129   if ( !msg || !todo ) {
02130     return QString::null;
02131   }
02132 
02133   switch ( msg->method() ) {
02134   case Scheduler::Publish:
02135     return i18n("This task has been published");
02136   case Scheduler::Request:
02137     if ( existingIncidence && todo->revision() > 0 ) {
02138       QString orgStr = organizerName( todo, sender );
02139       if ( senderIsOrganizer( todo, sender ) ) {
02140         return i18n( "This task has been updated by the organizer %1" ).arg( orgStr );
02141       } else {
02142         return i18n( "This task has been updated by %1 as a representative of %2" ).
02143           arg( sender, orgStr );
02144       }
02145     } else {
02146       if ( iamOrganizer( todo ) ) {
02147         return i18n( "I created this task" );
02148       } else {
02149         QString orgStr = organizerName( todo, sender );
02150         if ( senderIsOrganizer( todo, sender ) ) {
02151           return i18n( "You have been assigned this task by %1" ).arg( orgStr );
02152         } else {
02153           return i18n( "You have been assigned this task by %1 as a representative of %2" ).
02154             arg( sender, orgStr );
02155         }
02156       }
02157     }
02158   case Scheduler::Refresh:
02159     return i18n( "This task was refreshed" );
02160   case Scheduler::Cancel:
02161     if ( iamOrganizer( todo ) ) {
02162       return i18n( "This task was canceled" );
02163     } else {
02164       return i18n( "The organizer has removed you from this task" );
02165     }
02166   case Scheduler::Add:
02167     return i18n( "Addition to the task" );
02168   case Scheduler::Reply:
02169   {
02170     if ( replyMeansCounter( todo ) ) {
02171       return i18n( "%1 makes this counter proposal" ).arg( firstAttendeeName( todo, sender ) );
02172     }
02173 
02174     Attendee::List attendees = todo->attendees();
02175     if( attendees.count() == 0 ) {
02176       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
02177       return QString::null;
02178     }
02179     if( attendees.count() != 1 ) {
02180       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
02181                     << "but is " << attendees.count() << endl;
02182     }
02183     QString attendeeName = firstAttendeeName( todo, sender );
02184 
02185     QString delegatorName, dummy;
02186     Attendee* attendee = *attendees.begin();
02187     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
02188     if ( delegatorName.isEmpty() ) {
02189       delegatorName = attendee->delegator();
02190     }
02191 
02192     switch( attendee->status() ) {
02193     case Attendee::NeedsAction:
02194       return i18n( "%1 indicates this task assignment still needs some action" ).arg( attendeeName );
02195     case Attendee::Accepted:
02196       if ( todo->revision() > 0 ) {
02197         if ( !sender.isEmpty() ) {
02198           if ( todo->isCompleted() ) {
02199             return i18n( "This task has been completed by assignee %1" ).arg( sender );
02200           } else {
02201             return i18n( "This task has been updated by assignee %1" ).arg( sender );
02202           }
02203         } else {
02204           if ( todo->isCompleted() ) {
02205             return i18n( "This task has been completed by an assignee" );
02206           } else {
02207             return i18n( "This task has been updated by an assignee" );
02208           }
02209         }
02210       } else {
02211         if ( delegatorName.isEmpty() ) {
02212           return i18n( "%1 accepts this task" ).arg( attendeeName );
02213         } else {
02214           return i18n( "%1 accepts this task on behalf of %2" ).
02215             arg( attendeeName ).arg( delegatorName );
02216         }
02217       }
02218     case Attendee::Tentative:
02219       if ( delegatorName.isEmpty() ) {
02220         return i18n( "%1 tentatively accepts this task" ).
02221           arg( attendeeName );
02222       } else {
02223         return i18n( "%1 tentatively accepts this task on behalf of %2" ).
02224           arg( attendeeName ).arg( delegatorName );
02225       }
02226     case Attendee::Declined:
02227       if ( delegatorName.isEmpty() ) {
02228         return i18n( "%1 declines this task" ).arg( attendeeName );
02229       } else {
02230         return i18n( "%1 declines this task on behalf of %2" ).
02231           arg( attendeeName ).arg( delegatorName );
02232       }
02233     case Attendee::Delegated: {
02234       QString delegate, dummy;
02235       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
02236       if ( delegate.isEmpty() ) {
02237         delegate = attendee->delegate();
02238       }
02239       if ( !delegate.isEmpty() ) {
02240         return i18n( "%1 has delegated this request for the task to %2" ).
02241           arg( attendeeName ).arg( delegate );
02242       } else {
02243         return i18n( "%1 has delegated this request for the task" ).
02244           arg( attendeeName );
02245       }
02246     }
02247     case Attendee::Completed:
02248       return i18n( "The request for this task is now completed" );
02249     case Attendee::InProcess:
02250       return i18n( "%1 is still processing the task" ).
02251         arg( attendeeName );
02252     default:
02253       return i18n( "Unknown response to this task" );
02254     }
02255     break;
02256   }
02257 
02258   case Scheduler::Counter:
02259     return i18n( "%1 makes this counter proposal" ).arg( firstAttendeeName( todo, sender ) );
02260 
02261   case Scheduler::Declinecounter:
02262   {
02263     QString orgStr = organizerName( todo, sender );
02264     if ( senderIsOrganizer( todo, sender ) ) {
02265       return i18n( "%1 declines the counter proposal" ).arg( orgStr );
02266     } else {
02267       return i18n( "%1 declines the counter proposal on behalf of %2" ).arg( sender, orgStr );
02268     }
02269   }
02270 
02271   case Scheduler::NoMethod:
02272     return i18n( "Error: iTIP message with unknown method: '%1'" ).
02273       arg( msg->method() );
02274   }
02275   return QString::null;
02276 }
02277 
02278 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
02279 {
02280   if ( !msg || !journal ) {
02281     return QString::null;
02282   }
02283 
02284   switch ( msg->method() ) {
02285   case Scheduler::Publish:
02286     return i18n("This journal has been published");
02287   case Scheduler::Request:
02288     return i18n( "You have been assigned this journal" );
02289   case Scheduler::Refresh:
02290     return i18n( "This journal was refreshed" );
02291   case Scheduler::Cancel:
02292     return i18n( "This journal was canceled" );
02293   case Scheduler::Add:
02294     return i18n( "Addition to the journal" );
02295   case Scheduler::Reply:
02296   {
02297     if ( replyMeansCounter( journal ) ) {
02298       return i18n( "Sender makes this counter proposal" );
02299     }
02300 
02301     Attendee::List attendees = journal->attendees();
02302     if( attendees.count() == 0 ) {
02303       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
02304       return QString::null;
02305     }
02306     if( attendees.count() != 1 ) {
02307       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
02308                     << "but is " << attendees.count() << endl;
02309     }
02310     Attendee* attendee = *attendees.begin();
02311 
02312     switch( attendee->status() ) {
02313     case Attendee::NeedsAction:
02314       return i18n( "Sender indicates this journal assignment still needs some action" );
02315     case Attendee::Accepted:
02316       return i18n( "Sender accepts this journal" );
02317     case Attendee::Tentative:
02318       return i18n( "Sender tentatively accepts this journal" );
02319     case Attendee::Declined:
02320       return i18n( "Sender declines this journal" );
02321     case Attendee::Delegated:
02322       return i18n( "Sender has delegated this request for the journal" );
02323     case Attendee::Completed:
02324       return i18n( "The request for this journal is now completed" );
02325     case Attendee::InProcess:
02326       return i18n( "Sender is still processing the invitation" );
02327     default:
02328       return i18n( "Unknown response to this journal" );
02329     }
02330     break;
02331   }
02332   case Scheduler::Counter:
02333     return i18n( "Sender makes this counter proposal" );
02334   case Scheduler::Declinecounter:
02335     return i18n( "Sender declines the counter proposal" );
02336   case Scheduler::NoMethod:
02337     return i18n("Error: iTIP message with unknown method: '%1'").
02338       arg( msg->method() );
02339   }
02340   return QString::null;
02341 }
02342 
02343 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
02344 {
02345   if ( !msg || !fb ) {
02346     return QString::null;
02347   }
02348 
02349   switch ( msg->method() ) {
02350   case Scheduler::Publish:
02351     return i18n("This free/busy list has been published");
02352   case Scheduler::Request:
02353     return i18n( "The free/busy list has been requested" );
02354   case Scheduler::Refresh:
02355     return i18n( "This free/busy list was refreshed" );
02356   case Scheduler::Cancel:
02357     return i18n( "This free/busy list was canceled" );
02358   case Scheduler::Add:
02359     return i18n( "Addition to the free/busy list" );
02360   case Scheduler::NoMethod:
02361   default:
02362     return i18n("Error: Free/Busy iTIP message with unknown method: '%1'").
02363       arg( msg->method() );
02364   }
02365 }
02366 
02367 static QString invitationAttendeeList( Incidence *incidence )
02368 {
02369   QString tmpStr;
02370   if ( !incidence ) {
02371     return tmpStr;
02372   }
02373 
02374   if ( incidence->type() == "Todo" ) {
02375     tmpStr += i18n( "Assignees" );
02376   } else {
02377     tmpStr += i18n( "Invitation List" );
02378   }
02379   tmpStr += "<br/>";
02380 
02381   int count=0;
02382   Attendee::List attendees = incidence->attendees();
02383   if ( !attendees.isEmpty() ) {
02384     QStringList comments;
02385     Attendee::List::ConstIterator it;
02386     for( it = attendees.begin(); it != attendees.end(); ++it ) {
02387       Attendee *a = *it;
02388       if ( !iamAttendee( a ) ) {
02389         count++;
02390         if ( count == 1 ) {
02391           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\" columns=\"1\">";
02392         }
02393         tmpStr += "<tr>";
02394         tmpStr += "<td>";
02395         comments.clear();
02396         if ( attendeeIsOrganizer( incidence, a ) ) {
02397           comments << i18n( "organizer" );
02398         }
02399         if ( !a->delegator().isEmpty() ) {
02400           comments << i18n( "delegated by %1" ).arg( a->delegator() );
02401         }
02402         if ( !a->delegate().isEmpty() ) {
02403           comments << i18n( "delegated to %1" ).arg( a->delegate() );
02404         }
02405         tmpStr += invitationPerson( a->email(), a->name(), QString::null, comments.join( "," ) );
02406         tmpStr += "</td>";
02407         tmpStr += "</tr>";
02408       }
02409     }
02410   }
02411   if ( count ) {
02412     tmpStr += "</table>";
02413   } else {
02414     tmpStr = QString::null;
02415   }
02416 
02417   return tmpStr;
02418 }
02419 
02420 static QString invitationRsvpList( Incidence *incidence, Attendee *sender )
02421 {
02422   QString tmpStr;
02423   if ( !incidence ) {
02424     return tmpStr;
02425   }
02426 
02427   if ( incidence->type() == "Todo" ) {
02428     tmpStr += i18n( "Assignees" );
02429   } else {
02430     tmpStr += i18n( "Invitation List" );
02431   }
02432   tmpStr += "<br/>";
02433 
02434   int count=0;
02435   Attendee::List attendees = incidence->attendees();
02436   if ( !attendees.isEmpty() ) {
02437     QStringList comments;
02438     Attendee::List::ConstIterator it;
02439     for( it = attendees.begin(); it != attendees.end(); ++it ) {
02440       Attendee *a = *it;
02441       if ( !attendeeIsOrganizer( incidence, a ) ) {
02442         QString statusStr = a->statusStr();
02443         if ( sender && ( a->email() == sender->email() ) ) {
02444           // use the attendee taken from the response incidence,
02445           // rather than the attendee from the calendar incidence.
02446           if ( a->status() != sender->status() ) {
02447             statusStr = i18n( "%1 (<i>unrecorded</i>)" ).arg( sender->statusStr() );
02448           }
02449           a = sender;
02450         }
02451         count++;
02452         if ( count == 1 ) {
02453           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\" columns=\"2\">";
02454         }
02455         tmpStr += "<tr>";
02456         tmpStr += "<td>";
02457         comments.clear();
02458         if ( iamAttendee( a ) ) {
02459           comments << i18n( "myself" );
02460         }
02461         if ( !a->delegator().isEmpty() ) {
02462           comments << i18n( "delegated by %1" ).arg( a->delegator() );
02463         }
02464         if ( !a->delegate().isEmpty() ) {
02465           comments << i18n( "delegated to %1" ).arg( a->delegate() );
02466         }
02467         tmpStr += invitationPerson( a->email(), a->name(), QString::null, comments.join( "," ) );
02468         tmpStr += "</td>";
02469         tmpStr += "<td>" + statusStr + "</td>";
02470         tmpStr += "</tr>";
02471       }
02472     }
02473   }
02474   if ( count ) {
02475     tmpStr += "</table>";
02476   } else {
02477     tmpStr += "<i>" + i18n( "No attendee", "None" ) + "</i>";
02478   }
02479 
02480   return tmpStr;
02481 }
02482 
02483 static QString invitationAttachments( InvitationFormatterHelper *helper, Incidence *incidence )
02484 {
02485   QString tmpStr;
02486   if ( !incidence ) {
02487     return tmpStr;
02488   }
02489 
02490   Attachment::List attachments = incidence->attachments();
02491   if ( !attachments.isEmpty() ) {
02492     tmpStr += i18n( "Attached Documents:" ) + "<ol>";
02493 
02494     Attachment::List::ConstIterator it;
02495     for( it = attachments.begin(); it != attachments.end(); ++it ) {
02496       Attachment *a = *it;
02497       tmpStr += "<li>";
02498       // Attachment icon
02499       KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
02500       const QString iconStr = mimeType ? mimeType->icon( a->uri(), false ) : QString( "application-octet-stream" );
02501       const QString iconPath = KGlobal::iconLoader()->iconPath( iconStr, KIcon::Small );
02502       if ( !iconPath.isEmpty() ) {
02503         tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
02504       }
02505       const QCString encodedLabel = KCodecs::base64Encode(a->label().utf8());
02506       tmpStr += helper->makeLink( "ATTACH:" + QString::fromUtf8(encodedLabel.data(), encodedLabel.length()), a->label() );
02507       tmpStr += "</li>";
02508     }
02509     tmpStr += "</ol>";
02510   }
02511 
02512   return tmpStr;
02513 }
02514 
02515 class IncidenceFormatter::ScheduleMessageVisitor
02516   : public IncidenceBase::Visitor
02517 {
02518   public:
02519     ScheduleMessageVisitor() : mExistingIncidence( 0 ), mMessage( 0 ) { mResult = ""; }
02520     bool act( IncidenceBase *incidence, Incidence *existingIncidence, ScheduleMessage *msg,
02521               const QString &sender )
02522     {
02523       mExistingIncidence = existingIncidence;
02524       mMessage = msg;
02525       mSender = sender;
02526       return incidence->accept( *this );
02527     }
02528     QString result() const { return mResult; }
02529 
02530   protected:
02531     QString mResult;
02532     Incidence *mExistingIncidence;
02533     ScheduleMessage *mMessage;
02534     QString mSender;
02535 };
02536 
02537 class IncidenceFormatter::InvitationHeaderVisitor
02538   : public IncidenceFormatter::ScheduleMessageVisitor
02539 {
02540   protected:
02541     bool visit( Event *event )
02542     {
02543       mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender );
02544       return !mResult.isEmpty();
02545     }
02546     bool visit( Todo *todo )
02547     {
02548       mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender );
02549       return !mResult.isEmpty();
02550     }
02551     bool visit( Journal *journal )
02552     {
02553       mResult = invitationHeaderJournal( journal, mMessage );
02554       return !mResult.isEmpty();
02555     }
02556     bool visit( FreeBusy *fb )
02557     {
02558       mResult = invitationHeaderFreeBusy( fb, mMessage );
02559       return !mResult.isEmpty();
02560     }
02561 };
02562 
02563 class IncidenceFormatter::InvitationBodyVisitor
02564   : public IncidenceFormatter::ScheduleMessageVisitor
02565 {
02566   public:
02567     InvitationBodyVisitor( bool noHtmlMode )
02568       : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ) {}
02569 
02570   protected:
02571     bool visit( Event *event )
02572     {
02573       Event *oldevent = dynamic_cast<Event *>( mExistingIncidence );
02574       mResult = invitationDetailsEvent( event, oldevent, mMessage, mNoHtmlMode );
02575       return !mResult.isEmpty();
02576     }
02577     bool visit( Todo *todo )
02578     {
02579       Todo *oldtodo = dynamic_cast<Todo *>( mExistingIncidence );
02580       mResult = invitationDetailsTodo( todo, oldtodo, mMessage, mNoHtmlMode );
02581       return !mResult.isEmpty();
02582     }
02583     bool visit( Journal *journal )
02584     {
02585       Journal *oldjournal = dynamic_cast<Journal *>( mExistingIncidence );
02586       mResult = invitationDetailsJournal( journal, oldjournal, mNoHtmlMode );
02587       return !mResult.isEmpty();
02588     }
02589     bool visit( FreeBusy *fb )
02590     {
02591       mResult = invitationDetailsFreeBusy( fb, 0, mNoHtmlMode );
02592       return !mResult.isEmpty();
02593     }
02594 
02595   private:
02596     bool mNoHtmlMode;
02597 };
02598 
02599 class IncidenceFormatter::IncidenceCompareVisitor
02600   : public IncidenceBase::Visitor
02601 {
02602   public:
02603     IncidenceCompareVisitor() : mExistingIncidence(0) {}
02604     bool act( IncidenceBase *incidence, Incidence *existingIncidence, int method )
02605     {
02606       Incidence *inc = dynamic_cast<Incidence*>( incidence );
02607       if ( !inc || !existingIncidence  || inc->revision() <= existingIncidence->revision() )
02608         return false;
02609       mExistingIncidence = existingIncidence;
02610       mMethod = method;
02611       return incidence->accept( *this );
02612     }
02613 
02614     QString result() const
02615     {
02616       if ( mChanges.isEmpty() ) {
02617         return QString::null;
02618       }
02619       QString html = "<div align=\"left\"><ul><li>";
02620       html += mChanges.join( "</li><li>" );
02621       html += "</li><ul></div>";
02622       return html;
02623     }
02624 
02625   protected:
02626     bool visit( Event *event )
02627     {
02628       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
02629       compareIncidences( event, mExistingIncidence, mMethod );
02630       return !mChanges.isEmpty();
02631     }
02632     bool visit( Todo *todo )
02633     {
02634       compareTodos( todo, dynamic_cast<Todo*>( mExistingIncidence ) );
02635       compareIncidences( todo, mExistingIncidence, mMethod );
02636       return !mChanges.isEmpty();
02637     }
02638     bool visit( Journal *journal )
02639     {
02640       compareIncidences( journal, mExistingIncidence, mMethod );
02641       return !mChanges.isEmpty();
02642     }
02643     bool visit( FreeBusy *fb )
02644     {
02645       Q_UNUSED( fb );
02646       return !mChanges.isEmpty();
02647     }
02648 
02649   private:
02650     void compareEvents( Event *newEvent, Event *oldEvent )
02651     {
02652       if ( !oldEvent || !newEvent )
02653         return;
02654       if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->doesFloat() != newEvent->doesFloat() )
02655         mChanges += i18n( "The invitation starting time has been changed from %1 to %2" )
02656                     .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) );
02657       if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->doesFloat() != newEvent->doesFloat() )
02658         mChanges += i18n( "The invitation ending time has been changed from %1 to %2" )
02659                     .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) );
02660     }
02661 
02662     void compareTodos( Todo *newTodo, Todo *oldTodo )
02663     {
02664       if ( !oldTodo || !newTodo ) {
02665         return;
02666       }
02667 
02668       if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) {
02669         mChanges += i18n( "The task has been completed" );
02670       }
02671       if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) {
02672         mChanges += i18n( "The task is no longer completed" );
02673       }
02674       if ( oldTodo->percentComplete() != newTodo->percentComplete() ) {
02675         const QString oldPer = i18n( "%1%" ).arg( oldTodo->percentComplete() );
02676         const QString newPer = i18n( "%1%" ).arg( newTodo->percentComplete() );
02677         mChanges += i18n( "The task completed percentage has changed from %1 to %2" ).
02678                     arg( oldPer ).arg( newPer );
02679       }
02680 
02681       if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) {
02682         mChanges += i18n( "A task starting time has been added" );
02683       }
02684       if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) {
02685         mChanges += i18n( "The task starting time has been removed" );
02686       }
02687       if ( oldTodo->hasStartDate() && newTodo->hasStartDate() &&
02688            oldTodo->dtStart() != newTodo->dtStart() ) {
02689         mChanges += i18n( "The task starting time has been changed from %1 to %2" ).
02690                     arg( dateTimeToString( oldTodo->dtStart(), oldTodo->doesFloat(), false ) ).
02691                     arg( dateTimeToString( newTodo->dtStart(), newTodo->doesFloat(), false ) );
02692       }
02693 
02694       if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) {
02695         mChanges += i18n( "A task due time has been added" );
02696       }
02697       if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) {
02698         mChanges += i18n( "The task due time has been removed" );
02699       }
02700       if ( ( oldTodo->hasDueDate() && newTodo->hasDueDate() ) &&
02701            ( oldTodo->dtDue() != newTodo->dtDue() ) ) {
02702         mChanges += i18n( "The task due time has been changed from %1 to %2" ).
02703                     arg( dateTimeToString( oldTodo->dtDue(), oldTodo->doesFloat(), false ) ).
02704                     arg( dateTimeToString( newTodo->dtDue(), newTodo->doesFloat(), false ) );
02705       }
02706     }
02707 
02708     void compareIncidences( Incidence *newInc, Incidence *oldInc, int method )
02709     {
02710       if ( !oldInc || !newInc )
02711         return;
02712       if ( oldInc->summary() != newInc->summary() )
02713         mChanges += i18n( "The summary has been changed to: \"%1\"" ).arg( newInc->summary() );
02714       if ( oldInc->location() != newInc->location() )
02715         mChanges += i18n( "The location has been changed to: \"%1\"" ).arg( newInc->location() );
02716       if ( oldInc->description() != newInc->description() )
02717         mChanges += i18n( "The description has been changed to: \"%1\"" ).arg( newInc->description() );
02718       Attendee::List oldAttendees = oldInc->attendees();
02719       Attendee::List newAttendees = newInc->attendees();
02720       for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
02721             it != newAttendees.constEnd(); ++it ) {
02722         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
02723         if ( !oldAtt ) {
02724           mChanges += i18n( "Attendee %1 has been added" ).arg( (*it)->fullName() );
02725         } else {
02726           if ( oldAtt->status() != (*it)->status() )
02727             mChanges += i18n( "The status of attendee %1 has been changed to: %2" ).
02728                         arg( (*it)->fullName() ).arg( (*it)->statusStr() );
02729         }
02730       }
02731       if ( method == Scheduler::Request ) {
02732         for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
02733               it != oldAttendees.constEnd(); ++it ) {
02734           if ( !attendeeIsOrganizer( oldInc, (*it) ) ) {
02735             Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
02736             if ( !newAtt ) {
02737               mChanges += i18n( "Attendee %1 has been removed" ).arg( (*it)->fullName() );
02738             }
02739           }
02740         }
02741       }
02742     }
02743 
02744   private:
02745     Incidence *mExistingIncidence;
02746     int mMethod;
02747     QStringList mChanges;
02748 };
02749 
02750 
02751 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
02752 {
02753   if ( !id.startsWith( "ATTACH:" ) ) {
02754     QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
02755                   arg( generateLinkURL( id ), text );
02756     return res;
02757   } else {
02758     // draw the attachment links in non-bold face
02759     QString res = QString( "<a href=\"%1\">%2</a>" ).
02760                   arg( generateLinkURL( id ), text );
02761     return res;
02762   }
02763 }
02764 
02765 static QString responseButtons( Incidence *inc, bool rsvpReq, bool rsvpRec,
02766                                 InvitationFormatterHelper *helper )
02767 {
02768   // The spacer for the invitation buttons
02769   const QString spacer = "<td> &nbsp; </td>";
02770   // The open & close table cell tags for the invitation buttons
02771   const QString tdOpen = "<td>";
02772   const QString tdClose = "</td>" + spacer;
02773 
02774   QString html;
02775   if ( !helper ) {
02776     return html;
02777   }
02778 
02779   if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) {
02780     // Record only
02781     html += tdOpen;
02782     html += helper->makeLink( "record", i18n( "[Record]" ) );
02783     html += tdClose;
02784 
02785     // Move to trash
02786     html += tdOpen;
02787     html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) );
02788     html += tdClose;
02789 
02790   } else {
02791 
02792     // Accept
02793     html += tdOpen;
02794     html += helper->makeLink( "accept", i18n( "[Accept]" ) );
02795     html += tdClose;
02796 
02797     // Tentative
02798     html += tdOpen;
02799     html += helper->makeLink( "accept_conditionally",
02800                               i18n( "Accept conditionally", "[Accept cond.]" ) );
02801     html += tdClose;
02802 
02803     // Counter proposal
02804     html += tdOpen;
02805     html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
02806     html += tdClose;
02807 
02808     // Decline
02809     html += tdOpen;
02810     html += helper->makeLink( "decline", i18n( "[Decline]" ) );
02811     html += tdClose;
02812   }
02813 
02814   if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
02815     // Delegate
02816     html += tdOpen;
02817     html += helper->makeLink( "delegate", i18n( "[Delegate]" ) );
02818     html += tdClose;
02819 
02820     // Forward
02821     html += tdOpen;
02822     html += helper->makeLink( "forward", i18n( "[Forward]" ) );
02823     html += tdClose;
02824 
02825     // Check calendar
02826     if ( inc && inc->type() == "Event" ) {
02827       html += tdOpen;
02828       html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
02829       html += tdClose;
02830     }
02831   }
02832   return html;
02833 }
02834 
02835 static QString counterButtons( Incidence *incidence,
02836                                InvitationFormatterHelper *helper )
02837 {
02838   // The spacer for the invitation buttons
02839   const QString spacer = "<td> &nbsp; </td>";
02840   // The open & close table cell tags for the invitation buttons
02841   const QString tdOpen = "<td>";
02842   const QString tdClose = "</td>" + spacer;
02843 
02844   QString html;
02845   if ( !helper ) {
02846     return html;
02847   }
02848 
02849   // Accept proposal
02850   html += tdOpen;
02851   html += helper->makeLink( "accept_counter", i18n("[Accept]") );
02852   html += tdClose;
02853 
02854   // Decline proposal
02855   html += tdOpen;
02856   html += helper->makeLink( "decline_counter", i18n("[Decline]") );
02857   html += tdClose;
02858 
02859   // Check calendar
02860   if ( incidence && incidence->type() == "Event" ) {
02861     html += tdOpen;
02862     html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
02863     html += tdClose;
02864   }
02865   return html;
02866 }
02867 
02868 QString IncidenceFormatter::formatICalInvitationHelper( QString invitation,
02869                                                         Calendar *mCalendar,
02870                                                         InvitationFormatterHelper *helper,
02871                                                         bool noHtmlMode,
02872                                                         const QString &sender,
02873                                                         bool outlookCompareStyle )
02874 {
02875   if ( invitation.isEmpty() ) {
02876     return QString::null;
02877   }
02878 
02879   ICalFormat format;
02880   // parseScheduleMessage takes the tz from the calendar, no need to set it manually here for the format!
02881   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
02882 
02883   if( !msg ) {
02884     kdDebug( 5850 ) << "Failed to parse the scheduling message" << endl;
02885     Q_ASSERT( format.exception() );
02886     kdDebug( 5850 ) << format.exception()->message() << endl;
02887     return QString::null;
02888   }
02889 
02890   IncidenceBase *incBase = msg->event();
02891 
02892   // Determine if this incidence is in my calendar (and owned by me)
02893   Incidence *existingIncidence = 0;
02894   if ( incBase && helper->calendar() ) {
02895     existingIncidence = helper->calendar()->incidence( incBase->uid() );
02896     if ( !CalHelper::isMyCalendarIncidence( helper->calendar(), existingIncidence ) ) {
02897       //sergio, 03/2011: This never happens I think. If the incidence is in a shared calendar
02898       //then it's uid will be a new one, and won't match incBase->uid().
02899       existingIncidence = 0;
02900     }
02901     if ( !existingIncidence ) {
02902       const Incidence::List list = helper->calendar()->incidences();
02903       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
02904         if ( (*it)->schedulingID() == incBase->uid() &&
02905              CalHelper::isMyCalendarIncidence( helper->calendar(), *it ) ) {
02906           existingIncidence = *it;
02907           break;
02908         }
02909       }
02910     }
02911   }
02912 
02913   Incidence *inc = dynamic_cast<Incidence*>( incBase ); // the incidence in the invitation email
02914 
02915   // First make the text of the message
02916   QString html;
02917 
02918   QString tableStyle = QString::fromLatin1(
02919     "style=\"border: solid 1px; margin: 0em;\"" );
02920   QString tableHead = QString::fromLatin1(
02921     "<div align=\"center\">"
02922     "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
02923     "<tr><td>").arg(tableStyle);
02924 
02925   html += tableHead;
02926   InvitationHeaderVisitor headerVisitor;
02927   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
02928   if ( !headerVisitor.act( inc, existingIncidence, msg, sender ) ) {
02929     return QString::null;
02930   }
02931   html += "<b>" + headerVisitor.result() + "</b>";
02932 
02933   if ( outlookCompareStyle ||
02934        msg->method() == Scheduler::Declinecounter ) { //use Outlook style for decline counters
02935     // use the Outlook 2007 Comparison Style
02936     InvitationBodyVisitor bodyVisitor( noHtmlMode );
02937     bool bodyOk;
02938     if ( msg->method() == Scheduler::Request || msg->method() == Scheduler::Reply ||
02939          msg->method() == Scheduler::Declinecounter ) {
02940       if ( inc && existingIncidence &&
02941            inc->lastModified() < existingIncidence->lastModified() ) {
02942         bodyOk = bodyVisitor.act( existingIncidence, inc, msg, sender );
02943       } else {
02944         bodyOk = bodyVisitor.act( inc, existingIncidence, msg, sender );
02945       }
02946     } else {
02947       bodyOk = bodyVisitor.act( inc, 0, msg, sender );
02948     }
02949     if ( bodyOk ) {
02950       html += bodyVisitor.result();
02951     } else {
02952       return QString::null;
02953     }
02954   } else {
02955     // use our "Classic: Comparison Style
02956     InvitationBodyVisitor bodyVisitor( noHtmlMode );
02957     if ( !bodyVisitor.act( inc, 0, msg, sender ) )
02958       return QString::null;
02959     html += bodyVisitor.result();
02960 
02961     if ( msg->method() == Scheduler::Request ) {
02962       IncidenceCompareVisitor compareVisitor;
02963       if ( compareVisitor.act( inc, existingIncidence, msg->method() ) ) {
02964         html += "<p align=\"left\">";
02965         if ( senderIsOrganizer( inc, sender ) ) {
02966           html += i18n( "The following changes have been made by the organizer:" );
02967         } else if ( !sender.isEmpty() ) {
02968           html += i18n( "The following changes have been made by %1:" ).arg( sender );
02969         } else {
02970           html += i18n( "The following changes have been made:" );
02971         }
02972         html += "</p>";
02973         html += compareVisitor.result();
02974       }
02975     }
02976     if ( msg->method() == Scheduler::Reply ) {
02977       IncidenceCompareVisitor compareVisitor;
02978       if ( compareVisitor.act( inc, existingIncidence, msg->method() ) ) {
02979         html += "<p align=\"left\">";
02980         if ( !sender.isEmpty() ) {
02981           html += i18n( "The following changes have been made by %1:" ).arg( sender );
02982         } else {
02983           html += i18n( "The following changes have been made by an attendee:" );
02984         }
02985         html += "</p>";
02986         html += compareVisitor.result();
02987       }
02988     }
02989   }
02990 
02991   // determine if I am the organizer for this invitation
02992   bool myInc = iamOrganizer( inc );
02993 
02994   // determine if the invitation response has already been recorded
02995   bool rsvpRec = false;
02996   Attendee *ea = 0;
02997   if ( !myInc ) {
02998     Incidence *rsvpIncidence = existingIncidence;
02999     if ( !rsvpIncidence && inc && inc->revision() > 0 ) {
03000       rsvpIncidence = inc;
03001     }
03002     if ( rsvpIncidence ) {
03003       ea = findMyAttendee( rsvpIncidence );
03004     }
03005     if ( ea &&
03006          ( ea->status() == Attendee::Accepted ||
03007            ea->status() == Attendee::Declined ||
03008            ea->status() == Attendee::Tentative ) ) {
03009       rsvpRec = true;
03010     }
03011   }
03012 
03013   // determine invitation role
03014   QString role;
03015   bool isDelegated = false;
03016   Attendee *a = findMyAttendee( inc );
03017   if ( !a && inc ) {
03018     if ( !inc->attendees().isEmpty() ) {
03019       a = inc->attendees().first();
03020     }
03021   }
03022   if ( a ) {
03023     isDelegated = ( a->status() == Attendee::Delegated );
03024     role = Attendee::roleName( a->role() );
03025   }
03026 
03027   // determine if RSVP needed, not-needed, or response already recorded
03028   bool rsvpReq = rsvpRequested( inc );
03029   if ( !myInc && a ) {
03030     html += "<br/>";
03031     html += "<i><u>";
03032     if ( rsvpRec && inc ) {
03033       if ( inc->revision() == 0 ) {
03034         html += i18n( "Your <b>%1</b> response has been recorded" ).
03035                 arg( ea->statusStr() );
03036       } else {
03037         html += i18n( "Your status for this invitation is <b>%1</b>" ).
03038                 arg( ea->statusStr() );
03039       }
03040       rsvpReq = false;
03041     } else if ( msg->method() == Scheduler::Cancel ) {
03042       html += i18n( "This invitation was canceled" );
03043     } else if ( msg->method() == Scheduler::Add ) {
03044       html += i18n( "This invitation was accepted" );
03045     } else if ( msg->method() == Scheduler::Declinecounter ) {
03046       rsvpReq = true;
03047       html += rsvpRequestedStr( rsvpReq, role );
03048     } else {
03049       if ( !isDelegated ) {
03050         html += rsvpRequestedStr( rsvpReq, role );
03051       } else {
03052         html += i18n( "Awaiting delegation response" );
03053       }
03054     }
03055     html += "</u></i>";
03056   }
03057 
03058   // Print if the organizer gave you a preset status
03059   if ( !myInc ) {
03060     if ( inc && inc->revision() == 0 ) {
03061       QString statStr = myStatusStr( inc );
03062       if ( !statStr.isEmpty() ) {
03063         html += "<br/>";
03064         html += "<i>";
03065         html += statStr;
03066         html += "</i>";
03067       }
03068     }
03069   }
03070 
03071   // Add groupware links
03072 
03073   html += "<br><table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td></tr>";
03074 
03075   switch ( msg->method() ) {
03076     case Scheduler::Publish:
03077     case Scheduler::Request:
03078     case Scheduler::Refresh:
03079     case Scheduler::Add:
03080     {
03081       if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
03082         html += "<tr><td colspan=\"13\">";
03083         if ( inc->type() == "Todo" ) {
03084           html += helper->makeLink( "reply", i18n( "[Record in my task list]" ) );
03085         } else {
03086           html += helper->makeLink( "reply", i18n( "[Record in my calendar]" ) );
03087         }
03088         html += "</td></tr>";
03089       }
03090 
03091       if ( !myInc && a ) {
03092         html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
03093       }
03094       break;
03095     }
03096 
03097     case Scheduler::Cancel:
03098       // Remove invitation
03099       if ( inc ) {
03100         html += "<tr><td colspan=\"13\">";
03101         if ( inc->type() == "Todo" ) {
03102           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my task list]" ) );
03103         } else {
03104           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my calendar]" ) );
03105         }
03106         html += "</td></tr>";
03107       }
03108       break;
03109 
03110     case Scheduler::Reply:
03111     {
03112       // Record invitation response
03113       Attendee *a = 0;
03114       Attendee *ea = 0;
03115       if ( inc ) {
03116         // First, determine if this reply is really a counter in disguise.
03117         if ( replyMeansCounter( inc ) ) {
03118           html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
03119           break;
03120         }
03121 
03122         // Next, maybe this is a declined reply that was delegated from me?
03123         // find first attendee who is delegated-from me
03124         // look a their PARTSTAT response, if the response is declined,
03125         // then we need to start over which means putting all the action
03126         // buttons and NOT putting on the [Record response..] button
03127         a = findDelegatedFromMyAttendee( inc );
03128         if ( a ) {
03129           if ( a->status() != Attendee::Accepted ||
03130                a->status() != Attendee::Tentative ) {
03131             html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
03132             break;
03133           }
03134         }
03135 
03136         // Finally, simply allow a Record of the reply
03137         if ( !inc->attendees().isEmpty() ) {
03138           a = inc->attendees().first();
03139         }
03140         if ( a ) {
03141           ea = findAttendee( existingIncidence, a->email() );
03142         }
03143       }
03144       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
03145         // we have seen this invitation and recorded a response
03146         if ( inc->revision() > 0 &&
03147              ( inc->lastModified() > existingIncidence->lastModified() ) ) {
03148           // newer than we have recorded, so an update
03149           html += "<tr><td>";
03150           if ( inc->type() == "Todo" ) {
03151             html += helper->makeLink( "reply", i18n( "[Record update in my task list]" ) );
03152           } else {
03153             html += helper->makeLink( "reply", i18n( "[Record update in my calendar]" ) );
03154           }
03155           html += "</td></tr>";
03156         } else {
03157           // not newer than we have recorded
03158           html += "<br><u><i>";
03159           if ( inc->revision() > 0 ) {
03160             // an update we already have recorded
03161             html += i18n( "This update has been recorded" );
03162           } else {
03163             // not an update
03164             html += i18n( "This <b>%1</b> response has been recorded" ).arg( ea->statusStr() );
03165           }
03166           html += "</i></u>";
03167         }
03168       } else {
03169         // Not seen or recorded with a response yet
03170         if ( inc ) {
03171           html += "<tr><td>";
03172           if ( inc->type() == "Todo" ) {
03173             html += helper->makeLink( "reply", i18n( "[Record response in my task list]" ) );
03174           } else {
03175             html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
03176           }
03177           html += "</td></tr>";
03178         }
03179       }
03180       break;
03181     }
03182 
03183     case Scheduler::Counter:
03184       // Counter proposal
03185       html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
03186       break;
03187 
03188     case Scheduler::Declinecounter:
03189       html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
03190       break;
03191 
03192     case Scheduler::NoMethod:
03193       break;
03194   }
03195 
03196   // close the groupware table
03197   html += "</td></tr></table>";
03198 
03199   // Add the attendee list
03200   if ( myInc ) {
03201     html += invitationRsvpList( existingIncidence, a );
03202   } else {
03203     html += invitationAttendeeList( inc );
03204   }
03205 
03206   // close the top-level table
03207   html += "</td></tr></table><br></div>";
03208 
03209   // Add the attachment list
03210   html += invitationAttachments( helper, inc );
03211 
03212   return html;
03213 }
03214 
03215 QString IncidenceFormatter::formatICalInvitation( QString invitation,
03216                                                   Calendar *mCalendar,
03217                                                   InvitationFormatterHelper *helper )
03218 {
03219   return formatICalInvitationHelper( invitation, mCalendar, helper, false, QString(), true );
03220 }
03221 
03222 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
03223                                                         Calendar *mCalendar,
03224                                                         InvitationFormatterHelper *helper )
03225 {
03226   return formatICalInvitationHelper( invitation, mCalendar, helper, true, QString(), true );
03227 }
03228 
03229 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
03230                                                         Calendar *mCalendar,
03231                                                         InvitationFormatterHelper *helper,
03232                                                         const QString &sender )
03233 {
03234   return formatICalInvitationHelper( invitation, mCalendar, helper, true, sender, true );
03235 }
03236 
03237 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
03238                                                         Calendar *mCalendar,
03239                                                         InvitationFormatterHelper *helper,
03240                                                         const QString &sender,
03241                                                         bool outlookCompareStyle )
03242 {
03243   return formatICalInvitationHelper( invitation, mCalendar, helper, true, sender, outlookCompareStyle );
03244 }
03245 
03246 /*******************************************************************
03247  *  Helper functions for the msTNEF -> VPart converter
03248  *******************************************************************/
03249 
03250 
03251 //-----------------------------------------------------------------------------
03252 
03253 static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
03254                            const QString& fallback = QString::null)
03255 {
03256   return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
03257                             fallback );
03258 }
03259 
03260 static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
03261                            const QString& fallback = QString::null )
03262 {
03263   return tnefMsg->findNamedProp( name, fallback );
03264 }
03265 
03266 struct save_tz { char* old_tz; char* tz_env_str; };
03267 
03268 /* temporarily go to a different timezone */
03269 static struct save_tz set_tz( const char* _tc )
03270 {
03271   const char *tc = _tc?_tc:"UTC";
03272 
03273   struct save_tz rv;
03274 
03275   rv.old_tz = 0;
03276   rv.tz_env_str = 0;
03277 
03278   //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;
03279 
03280   char* tz_env = 0;
03281   if( getenv( "TZ" ) ) {
03282     tz_env = strdup( getenv( "TZ" ) );
03283     rv.old_tz = tz_env;
03284   }
03285   char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
03286   strcpy( tmp_env, "TZ=" );
03287   strcpy( tmp_env+3, tc );
03288   putenv( tmp_env );
03289 
03290   rv.tz_env_str = tmp_env;
03291 
03292   /* tmp_env is not free'ed -- it is part of the environment */
03293 
03294   tzset();
03295   //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;
03296 
03297   return rv;
03298 }
03299 
03300 /* restore previous timezone */
03301 static void unset_tz( struct save_tz old_tz )
03302 {
03303   if( old_tz.old_tz ) {
03304     char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
03305     strcpy( tmp_env, "TZ=" );
03306     strcpy( tmp_env+3, old_tz.old_tz );
03307     putenv( tmp_env );
03308     /* tmp_env is not free'ed -- it is part of the environment */
03309     free( old_tz.old_tz );
03310   } else {
03311     /* clear TZ from env */
03312     putenv( strdup("TZ") );
03313   }
03314   tzset();
03315 
03316   /* is this OK? */
03317   if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
03318 }
03319 
03320 static QDateTime utc2Local( const QDateTime& utcdt )
03321 {
03322   struct tm tmL;
03323 
03324   save_tz tmp_tz = set_tz("UTC");
03325   time_t utc = utcdt.toTime_t();
03326   unset_tz( tmp_tz );
03327 
03328   localtime_r( &utc, &tmL );
03329   return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
03330                     QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
03331 }
03332 
03333 
03334 static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
03335                                           bool bDateOnly = false )
03336 {
03337   QDate tmpDate;
03338   QTime tmpTime;
03339   int year, month, day, hour, minute, second;
03340 
03341   if( bDateOnly ) {
03342     year = dtStr.left( 4 ).toInt();
03343     month = dtStr.mid( 4, 2 ).toInt();
03344     day = dtStr.mid( 6, 2 ).toInt();
03345     hour = 0;
03346     minute = 0;
03347     second = 0;
03348   } else {
03349     year = dtStr.left( 4 ).toInt();
03350     month = dtStr.mid( 4, 2 ).toInt();
03351     day = dtStr.mid( 6, 2 ).toInt();
03352     hour = dtStr.mid( 9, 2 ).toInt();
03353     minute = dtStr.mid( 11, 2 ).toInt();
03354     second = dtStr.mid( 13, 2 ).toInt();
03355   }
03356   tmpDate.setYMD( year, month, day );
03357   tmpTime.setHMS( hour, minute, second );
03358 
03359   if( tmpDate.isValid() && tmpTime.isValid() ) {
03360     QDateTime dT = QDateTime( tmpDate, tmpTime );
03361 
03362     if( !bDateOnly ) {
03363       // correct for GMT ( == Zulu time == UTC )
03364       if (dtStr.at(dtStr.length()-1) == 'Z') {
03365         //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
03366         //localUTCOffset( dT ) );
03367         dT = utc2Local( dT );
03368       }
03369     }
03370     return dT;
03371   } else
03372     return QDateTime();
03373 }
03374 
03375 
03376 
03377 QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef )
03378 {
03379   KTNEFParser parser;
03380   QBuffer buf( tnef );
03381   CalendarLocal cal ( QString::fromLatin1( "UTC" ) );
03382   KABC::Addressee addressee;
03383   KABC::VCardConverter cardConv;
03384   ICalFormat calFormat;
03385   Event* event = new Event();
03386 
03387   if( parser.openDevice( &buf ) ) {
03388     KTNEFMessage* tnefMsg = parser.message();
03389     //QMap<int,KTNEFProperty*> props = parser.message()->properties();
03390 
03391     // Everything depends from property PR_MESSAGE_CLASS
03392     // (this is added by KTNEFParser):
03393     QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
03394       .upper();
03395     if( !msgClass.isEmpty() ) {
03396       // Match the old class names that might be used by Outlook for
03397       // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
03398       bool bCompatClassAppointment = false;
03399       bool bCompatMethodRequest = false;
03400       bool bCompatMethodCancled = false;
03401       bool bCompatMethodAccepted = false;
03402       bool bCompatMethodAcceptedCond = false;
03403       bool bCompatMethodDeclined = false;
03404       if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
03405         bCompatClassAppointment = true;
03406         if( msgClass.endsWith( ".MTGREQ" ) )
03407           bCompatMethodRequest = true;
03408         if( msgClass.endsWith( ".MTGCNCL" ) )
03409           bCompatMethodCancled = true;
03410         if( msgClass.endsWith( ".MTGRESPP" ) )
03411           bCompatMethodAccepted = true;
03412         if( msgClass.endsWith( ".MTGRESPA" ) )
03413           bCompatMethodAcceptedCond = true;
03414         if( msgClass.endsWith( ".MTGRESPN" ) )
03415           bCompatMethodDeclined = true;
03416       }
03417       bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );
03418 
03419       if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
03420         // Compose a vCal
03421         bool bIsReply = false;
03422         QString prodID = "-//Microsoft Corporation//Outlook ";
03423         prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
03424         prodID += "MIMEDIR/EN\n";
03425         prodID += "VERSION:2.0\n";
03426         calFormat.setApplication( "Outlook", prodID );
03427 
03428         Scheduler::Method method;
03429         if( bCompatMethodRequest )
03430           method = Scheduler::Request;
03431         else if( bCompatMethodCancled )
03432           method = Scheduler::Cancel;
03433         else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
03434                  bCompatMethodDeclined ) {
03435           method = Scheduler::Reply;
03436           bIsReply = true;
03437         } else {
03438           // pending(khz): verify whether "0x0c17" is the right tag ???
03439           //
03440           // at the moment we think there are REQUESTS and UPDATES
03441           //
03442           // but WHAT ABOUT REPLIES ???
03443           //
03444           //
03445 
03446           if( tnefMsg->findProp(0x0c17) == "1" )
03447             bIsReply = true;
03448           method = Scheduler::Request;
03449         }
03450 
03452         ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );
03453 
03454         QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );
03455 
03456         if( !sSenderSearchKeyEmail.isEmpty() ) {
03457           int colon = sSenderSearchKeyEmail.find( ':' );
03458           // May be e.g. "SMTP:KHZ@KDE.ORG"
03459           if( sSenderSearchKeyEmail.find( ':' ) == -1 )
03460             sSenderSearchKeyEmail.remove( 0, colon+1 );
03461         }
03462 
03463         QString s( tnefMsg->findProp( 0x0e04 ) );
03464         QStringList attendees = QStringList::split( ';', s );
03465         if( attendees.count() ) {
03466           for( QStringList::Iterator it = attendees.begin();
03467                it != attendees.end(); ++it ) {
03468             // Skip all entries that have no '@' since these are
03469             // no mail addresses
03470             if( (*it).find('@') == -1 ) {
03471               s = (*it).stripWhiteSpace();
03472 
03473               Attendee *attendee = new Attendee( s, s, true );
03474               if( bIsReply ) {
03475                 if( bCompatMethodAccepted )
03476                   attendee->setStatus( Attendee::Accepted );
03477                 if( bCompatMethodDeclined )
03478                   attendee->setStatus( Attendee::Declined );
03479                 if( bCompatMethodAcceptedCond )
03480                   attendee->setStatus(Attendee::Tentative);
03481               } else {
03482                 attendee->setStatus( Attendee::NeedsAction );
03483                 attendee->setRole( Attendee::ReqParticipant );
03484               }
03485               event->addAttendee(attendee);
03486             }
03487           }
03488         } else {
03489           // Oops, no attendees?
03490           // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
03491           s = sSenderSearchKeyEmail;
03492           if( !s.isEmpty() ) {
03493             Attendee *attendee = new Attendee( QString::null, QString::null,
03494                                                true );
03495             if( bIsReply ) {
03496               if( bCompatMethodAccepted )
03497                 attendee->setStatus( Attendee::Accepted );
03498               if( bCompatMethodAcceptedCond )
03499                 attendee->setStatus( Attendee::Declined );
03500               if( bCompatMethodDeclined )
03501                 attendee->setStatus( Attendee::Tentative );
03502             } else {
03503               attendee->setStatus(Attendee::NeedsAction);
03504               attendee->setRole(Attendee::ReqParticipant);
03505             }
03506             event->addAttendee(attendee);
03507           }
03508         }
03509         s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
03510         if( s.isEmpty() && !bIsReply )
03511           s = sSenderSearchKeyEmail;
03512         // TODO: Use the common name?
03513         if( !s.isEmpty() )
03514           event->setOrganizer( s );
03515 
03516         s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
03517           .replace( QChar( ':' ), QString::null );
03518         event->setDtStart( QDateTime::fromString( s ) ); // ## Format??
03519 
03520         s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
03521           .replace( QChar( ':' ), QString::null );
03522         event->setDtEnd( QDateTime::fromString( s ) );
03523 
03524         s = tnefMsg->findProp( 0x8208 );
03525         event->setLocation( s );
03526 
03527         // is it OK to set this to OPAQUE always ??
03528         //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
03529         //vPart += "SEQUENCE:0\n";
03530 
03531         // is "0x0023" OK  -  or should we look for "0x0003" ??
03532         s = tnefMsg->findProp( 0x0023 );
03533         event->setUid( s );
03534 
03535         // PENDING(khz): is this value in local timezone? Must it be
03536         // adjusted? Most likely this is a bug in the server or in
03537         // Outlook - we ignore it for now.
03538         s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
03539           .replace( QChar( ':' ), QString::null );
03540         // ### libkcal always uses currentDateTime()
03541         // event->setDtStamp(QDateTime::fromString(s));
03542 
03543         s = tnefMsg->findNamedProp( "Keywords" );
03544         event->setCategories( s );
03545 
03546         s = tnefMsg->findProp( 0x1000 );
03547         event->setDescription( s );
03548 
03549         s = tnefMsg->findProp( 0x0070 );
03550         event->setSummary( s );
03551 
03552         s = tnefMsg->findProp( 0x0026 );
03553         event->setPriority( s.toInt() );
03554 
03555         // is reminder flag set ?
03556         if(!tnefMsg->findProp(0x8503).isEmpty()) {
03557           Alarm *alarm = new Alarm(event);
03558           QDateTime highNoonTime =
03559             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
03560                                      .replace( QChar( '-' ), "" )
03561                                      .replace( QChar( ':' ), "" ) );
03562           QDateTime wakeMeUpTime =
03563             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
03564                                      .replace( QChar( '-' ), "" )
03565                                      .replace( QChar( ':' ), "" ) );
03566           alarm->setTime(wakeMeUpTime);
03567 
03568           if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
03569             alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
03570           else
03571             // default: wake them up 15 minutes before the appointment
03572             alarm->setStartOffset( Duration( 15*60 ) );
03573           alarm->setDisplayAlarm( i18n( "Reminder" ) );
03574 
03575           // Sorry: the different action types are not known (yet)
03576           //        so we always set 'DISPLAY' (no sounds, no images...)
03577           event->addAlarm( alarm );
03578         }
03579         cal.addEvent( event );
03580         // we finished composing a vCal
03581       } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
03582         addressee.setUid( stringProp( tnefMsg, attMSGID ) );
03583         addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) );
03584         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
03585         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
03586         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
03587         addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) );
03588         addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) );
03589         addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
03590         addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
03591         addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) );
03592         addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) );
03593         addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) );
03594 
03595         QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
03596           .replace( QChar( '-' ), QString::null )
03597           .replace( QChar( ':' ), QString::null );
03598         if( !s.isEmpty() )
03599           addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );
03600 
03601         addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );
03602 
03603         // collect parts of Name entry
03604         addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
03605         addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
03606         addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) );
03607         addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) );
03608         addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );
03609 
03610         addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
03611         addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
03612         addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
03613         /*
03614         the MAPI property ID of this (multiline) )field is unknown:
03615         vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
03616         */
03617 
03618         KABC::Address adr;
03619         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) );
03620         adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
03621         adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
03622         adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
03623         adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) );
03624         adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
03625         adr.setType(KABC::Address::Home);
03626         addressee.insertAddress(adr);
03627 
03628         adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
03629         adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) );
03630         adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) );
03631         adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) );
03632         adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
03633         adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) );
03634         adr.setType( KABC::Address::Work );
03635         addressee.insertAddress( adr );
03636 
03637         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) );
03638         adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
03639         adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
03640         adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
03641         adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) );
03642         adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
03643         adr.setType( KABC::Address::Dom );
03644         addressee.insertAddress(adr);
03645 
03646         // problem: the 'other' address was stored by KOrganizer in
03647         //          a line looking like the following one:
03648         // vPart += "\nADR;TYPE=dom;TYPE=intl;TYPE=parcel;TYPE=postal;TYPE=work;TYPE=home:other_pobox;;other_str1\nother_str2;other_loc;other_region;other_pocode;other_country
03649 
03650         QString nr;
03651         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
03652         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) );
03653         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
03654         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) );
03655         nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
03656         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) );
03657         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
03658         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) );
03659         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
03660         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) );
03661 
03662         s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
03663           .replace( QChar( '-' ), QString::null )
03664           .replace( QChar( ':' ), QString::null );
03665         if( !s.isEmpty() )
03666           addressee.setBirthday( QDateTime::fromString( s ) );
03667 
03668       } else if( "IPM.NOTE" == msgClass ) {
03669 
03670       } // else if ... and so on ...
03671     }
03672   }
03673 
03674   // Compose return string
03675   QString iCal = calFormat.toString( &cal );
03676   if( !iCal.isEmpty() )
03677     // This was an iCal
03678     return iCal;
03679 
03680   // Not an iCal - try a vCard
03681   KABC::VCardConverter converter;
03682   return converter.createVCard( addressee );
03683 }
03684 
03685 
03686 QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef,
03687         Calendar *mCalendar, InvitationFormatterHelper *helper )
03688 {
03689   QString vPart = IncidenceFormatter::msTNEFToVPart( tnef );
03690   QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper );
03691   if( !iCal.isEmpty() )
03692     return iCal;
03693   return vPart;
03694 }
03695 
03696 
03697 
03698 
03699 /*******************************************************************
03700  *  Helper functions for the Incidence tooltips
03701  *******************************************************************/
03702 
03703 class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
03704 {
03705   public:
03706     ToolTipVisitor()
03707       : mCalendar( 0 ), mRichText( true ), mResult( "" ) {}
03708 
03709     bool act( Calendar *calendar, IncidenceBase *incidence,
03710               const QDate &date=QDate(), bool richText=true )
03711     {
03712       mCalendar = calendar;
03713       mDate = date;
03714       mRichText = richText;
03715       mResult = "";
03716       return incidence ? incidence->accept( *this ) : false;
03717     }
03718     QString result() const { return mResult; }
03719 
03720   protected:
03721     bool visit( Event *event );
03722     bool visit( Todo *todo );
03723     bool visit( Journal *journal );
03724     bool visit( FreeBusy *fb );
03725 
03726     QString dateRangeText( Event *event, const QDate &date );
03727     QString dateRangeText( Todo *todo, const QDate &date );
03728     QString dateRangeText( Journal *journal );
03729     QString dateRangeText( FreeBusy *fb );
03730 
03731     QString generateToolTip( Incidence* incidence, QString dtRangeText );
03732 
03733   protected:
03734     Calendar *mCalendar;
03735     QDate mDate;
03736     bool mRichText;
03737     QString mResult;
03738 };
03739 
03740 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const QDate &date )
03741 {
03742   QString ret;
03743   QString tmp;
03744 
03745   QDateTime startDt = event->dtStart();
03746   QDateTime endDt = event->dtEnd();
03747   if ( event->doesRecur() ) {
03748     if ( date.isValid() ) {
03749       QDateTime dt( date, QTime( 0, 0, 0 ) );
03750       int diffDays = startDt.daysTo( dt );
03751       dt = dt.addSecs( -1 );
03752       startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() );
03753       if ( event->hasEndDate() ) {
03754         endDt = endDt.addDays( diffDays );
03755         if ( startDt > endDt ) {
03756           startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() );
03757           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
03758         }
03759       }
03760     }
03761   }
03762   if ( event->isMultiDay() ) {
03763 
03764     tmp = "<br>" + i18n("Event start", "<i>From:</i>&nbsp;%1");
03765     if (event->doesFloat())
03766       ret += tmp.arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03767     else
03768       ret += tmp.arg( IncidenceFormatter::dateToString( startDt ).replace(" ", "&nbsp;") );
03769 
03770     tmp = "<br>" + i18n("Event end","<i>To:</i>&nbsp;%1");
03771     if (event->doesFloat())
03772       ret += tmp.arg( IncidenceFormatter::dateToString( endDt, false ).replace(" ", "&nbsp;") );
03773     else
03774       ret += tmp.arg( IncidenceFormatter::dateToString( endDt ).replace(" ", "&nbsp;") );
03775 
03776   } else {
03777 
03778     ret += "<br>"+i18n("<i>Date:</i>&nbsp;%1").
03779            arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03780     if ( !event->doesFloat() ) {
03781       const QString dtStartTime =
03782         IncidenceFormatter::timeToString( startDt, true ).replace( " ", "&nbsp;" );
03783       const QString dtEndTime =
03784         IncidenceFormatter::timeToString( endDt, true ).replace( " ", "&nbsp;" );
03785       if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00'
03786         tmp = "<br>" + i18n("time for event, &nbsp; to prevent ugly line breaks",
03787         "<i>Time:</i>&nbsp;%1").
03788         arg( dtStartTime );
03789       } else {
03790         tmp = "<br>" + i18n("time range for event, &nbsp; to prevent ugly line breaks",
03791         "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2").
03792         arg( dtStartTime, dtEndTime );
03793       }
03794       ret += tmp;
03795     }
03796 
03797   }
03798   return ret;
03799 }
03800 
03801 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const QDate &date )
03802 {
03803   QString ret;
03804   bool floats( todo->doesFloat() );
03805 
03806   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03807     QDateTime startDt = todo->dtStart();
03808     if ( todo->doesRecur() ) {
03809       if ( date.isValid() ) {
03810         startDt.setDate( date );
03811       }
03812     }
03813     ret += "<br>" +
03814            i18n("<i>Start:</i>&nbsp;%1").
03815            arg( IncidenceFormatter::dateTimeToString( startDt, floats, false ).
03816                 replace( " ", "&nbsp;" ) );
03817   }
03818 
03819   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03820     QDateTime dueDt = todo->dtDue();
03821     if ( todo->doesRecur() ) {
03822       if ( date.isValid() ) {
03823         QDateTime dt( date, QTime( 0, 0, 0 ) );
03824         dt = dt.addSecs( -1 );
03825         dueDt.setDate( todo->recurrence()->getNextDateTime( dt ).date() );
03826       }
03827     }
03828     ret += "<br>" +
03829            i18n("<i>Due:</i>&nbsp;%1").
03830            arg( IncidenceFormatter::dateTimeToString( dueDt, floats, false ).
03831                 replace( " ", "&nbsp;" ) );
03832   }
03833 
03834   // Print priority and completed info here, for lack of a better place
03835 
03836   if ( todo->priority() > 0 ) {
03837     ret += "<br>";
03838     ret += "<i>" + i18n( "Priority:" ) + "</i>" + "&nbsp;";
03839     ret += QString::number( todo->priority() );
03840   }
03841 
03842   ret += "<br>";
03843   if ( todo->isCompleted() ) {
03844     ret += "<i>" + i18n( "Completed:" ) + "</i>" + "&nbsp;";
03845     ret += todo->completedStr().replace( " ", "&nbsp;" );
03846   } else {
03847     ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + "&nbsp;";
03848     ret += i18n( "%1%" ).arg( todo->percentComplete() );
03849   }
03850 
03851   return ret;
03852 }
03853 
03854 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal )
03855 {
03856   QString ret;
03857   if (journal->dtStart().isValid() ) {
03858     ret += "<br>" +
03859            i18n("<i>Date:</i>&nbsp;%1").
03860            arg( IncidenceFormatter::dateToString( journal->dtStart(), false ) );
03861   }
03862   return ret;
03863 }
03864 
03865 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
03866 {
03867   QString tmp( "<br>" + i18n("<i>Period start:</i>&nbsp;%1") );
03868   QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) );
03869   tmp = "<br>" + i18n("<i>Period start:</i>&nbsp;%1");
03870   ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) );
03871   return ret;
03872 }
03873 
03874 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
03875 {
03876   mResult = generateToolTip( event, dateRangeText( event, mDate ) );
03877   return !mResult.isEmpty();
03878 }
03879 
03880 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
03881 {
03882   mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
03883   return !mResult.isEmpty();
03884 }
03885 
03886 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
03887 {
03888   mResult = generateToolTip( journal, dateRangeText( journal ) );
03889   return !mResult.isEmpty();
03890 }
03891 
03892 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
03893 {
03894   mResult = "<qt><b>" + i18n("Free/Busy information for %1")
03895         .arg(fb->organizer().fullName()) + "</b>";
03896   mResult += dateRangeText( fb );
03897   mResult += "</qt>";
03898   return !mResult.isEmpty();
03899 }
03900 
03901 static QString tooltipPerson( const QString &email, const QString &name, Attendee::PartStat status )
03902 {
03903   // Search for a new print name, if needed.
03904   const QString printName = searchName( email, name );
03905 
03906   // Get the icon corresponding to the attendee participation status.
03907   const QString iconPath = rsvpStatusIconPath( status );
03908 
03909   // Make the return string.
03910   QString personString;
03911   if ( !iconPath.isEmpty() ) {
03912     personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
03913   }
03914   if ( status != Attendee::None ) {
03915     personString += i18n( "attendee name (attendee status)", "%1 (%2)" ).
03916                     arg( printName.isEmpty() ? email : printName ).
03917                     arg( Attendee::statusName( status ) );
03918   } else {
03919     personString += i18n( "%1" ).arg( printName.isEmpty() ? email : printName );
03920   }
03921   return personString;
03922 }
03923 
03924 static QString tooltipFormatOrganizer( const QString &email, const QString &name )
03925 {
03926   // Search for a new print name, if needed
03927   const QString printName = searchName( email, name );
03928 
03929   // Get the icon for organizer
03930   const QString iconPath = KGlobal::iconLoader()->iconPath( "organizer", KIcon::Small );
03931 
03932   // Make the return string.
03933   QString personString;
03934   personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
03935   personString += ( printName.isEmpty() ? email : printName );
03936   return personString;
03937 }
03938 
03939 static QString tooltipFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role,
03940                                               bool showStatus )
03941 {
03942   const int maxNumAtts = 8; // maximum number of people to print per attendee role
03943   const QString etc = i18n( "elipsis", "..." );
03944 
03945   int i = 0;
03946   QString tmpStr;
03947   Attendee::List::ConstIterator it;
03948   Attendee::List attendees = incidence->attendees();
03949 
03950   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
03951     Attendee *a = *it;
03952     if ( a->role() != role ) {
03953       // skip not this role
03954       continue;
03955     }
03956     if ( attendeeIsOrganizer( incidence, a ) ) {
03957       // skip attendee that is also the organizer
03958       continue;
03959     }
03960     if ( i == maxNumAtts ) {
03961       tmpStr += "&nbsp;&nbsp;" + etc;
03962       break;
03963     }
03964     tmpStr += "&nbsp;&nbsp;" + tooltipPerson( a->email(), a->name(),
03965                                               showStatus ? a->status() : Attendee::None );
03966     if ( !a->delegator().isEmpty() ) {
03967       tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
03968     }
03969     if ( !a->delegate().isEmpty() ) {
03970       tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
03971     }
03972     tmpStr += "<br>";
03973     i++;
03974   }
03975 
03976   if ( tmpStr.endsWith( "<br>" ) ) {
03977     tmpStr.truncate( tmpStr.length() - 4 );
03978   }
03979   return tmpStr;
03980 }
03981 
03982 static QString tooltipFormatAttendees( Calendar *calendar, Incidence *incidence )
03983 {
03984   QString tmpStr, str;
03985 
03986   // Add organizer link
03987   int attendeeCount = incidence->attendees().count();
03988   if ( attendeeCount > 1 ||
03989        ( attendeeCount == 1 &&
03990          !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) {
03991     tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "<br>";
03992     tmpStr += "&nbsp;&nbsp;" + tooltipFormatOrganizer( incidence->organizer().email(),
03993                                                        incidence->organizer().name() );
03994   }
03995 
03996   // Show the attendee status if the incidence's organizer owns the resource calendar,
03997   // which means they are running the show and have all the up-to-date response info.
03998   bool showStatus = CalHelper::incOrganizerOwnsCalendar( calendar, incidence );
03999 
04000   // Add "chair"
04001   str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus );
04002   if ( !str.isEmpty() ) {
04003     tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "<br>";
04004     tmpStr += str;
04005   }
04006 
04007   // Add required participants
04008   str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus );
04009   if ( !str.isEmpty() ) {
04010     tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "<br>";
04011     tmpStr += str;
04012   }
04013 
04014   // Add optional participants
04015   str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus );
04016   if ( !str.isEmpty() ) {
04017     tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "<br>";
04018     tmpStr += str;
04019   }
04020 
04021   // Add observers
04022   str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus );
04023   if ( !str.isEmpty() ) {
04024     tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "<br>";
04025     tmpStr += str;
04026   }
04027 
04028   return tmpStr;
04029 }
04030 
04031 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence, QString dtRangeText )
04032 {
04033   const QString etc = i18n( "elipsis", "..." );
04034   const uint maxDescLen = 120; // maximum description chars to print (before elipsis)
04035 
04036   if ( !incidence ) {
04037     return QString::null;
04038   }
04039 
04040   QString tmp = "<qt>";
04041 
04042   // header
04043 
04044 #ifdef KORG_DEBUG_SCHEDULING_IDS
04045   tmp += "<b>Uid: " + incidence->uid() + "<br>schedulingID: " + incidence->schedulingID() + "</b><br>";
04046 #endif
04047 
04048   tmp += "<b>" + incidence->summary().replace( "\n", "<br>" ) + "</b>";
04049   //NOTE: using <hr> seems to confuse Qt3 tooltips in some cases so use "-----"
04050   tmp += "<br>----------<br>";
04051 
04052   if ( mCalendar ) {
04053     QString calStr = IncidenceFormatter::resourceString( mCalendar, incidence );
04054     if ( !calStr.isEmpty() ) {
04055       tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + "&nbsp;";
04056       tmp += calStr;
04057     }
04058   }
04059 
04060   tmp += dtRangeText;
04061 
04062   if ( !incidence->location().isEmpty() ) {
04063     tmp += "<br>";
04064     tmp += "<i>" + i18n( "Location:" ) + "</i>" + "&nbsp;";
04065     tmp += incidence->location().replace( "\n", "<br>" );
04066   }
04067 
04068   QString durStr = IncidenceFormatter::durationString( incidence );
04069   if ( !durStr.isEmpty() ) {
04070     tmp += "<br>";
04071     tmp += "<i>" + i18n( "Duration:" ) + "</i>" + "&nbsp;";
04072     tmp += durStr;
04073   }
04074 
04075   if ( incidence->doesRecur() ) {
04076     tmp += "<br>";
04077     tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + "&nbsp;";
04078     tmp += IncidenceFormatter::recurrenceString( incidence );
04079   }
04080 
04081   if ( !incidence->description().isEmpty() ) {
04082     QString desc( incidence->description() );
04083     if ( desc.length() > maxDescLen ) {
04084       desc = desc.left( maxDescLen ) + etc;
04085     }
04086     tmp += "<br>----------<br>";
04087     tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
04088     tmp += desc.replace( "\n", "<br>" );
04089     tmp += "<br>----------";
04090   }
04091 
04092   int reminderCount = incidence->alarms().count();
04093   if ( reminderCount > 0 && incidence->isAlarmEnabled() ) {
04094     tmp += "<br>";
04095     tmp += "<i>" + i18n( "Reminder:", "%n Reminders:", reminderCount ) + "</i>" + "&nbsp;";
04096     tmp += IncidenceFormatter::reminderStringList( incidence ).join( ", " );
04097   }
04098 
04099   tmp += "<br>";
04100   tmp += tooltipFormatAttendees( mCalendar, incidence );
04101 
04102   int categoryCount = incidence->categories().count();
04103   if ( categoryCount > 0 ) {
04104     tmp += "<br>";
04105     tmp += "<i>" + i18n( "Category:", "%n Categories:", categoryCount ) + "</i>" + "&nbsp;";
04106     tmp += incidence->categories().join( ", " );
04107   }
04108 
04109   tmp += "</qt>";
04110   return tmp;
04111 }
04112 
04113 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
04114 {
04115   return toolTipStr( 0, incidence, QDate(), richText );
04116 }
04117 
04118 QString IncidenceFormatter::toolTipStr( Calendar *calendar,
04119                                         IncidenceBase *incidence,
04120                                         const QDate &date,
04121                                         bool richText )
04122 {
04123   ToolTipVisitor v;
04124   if ( v.act( calendar, incidence, date, richText ) ) {
04125     return v.result();
04126   } else {
04127     return QString::null;
04128   }
04129 }
04130 
04131 /*******************************************************************
04132  *  Helper functions for the Incidence tooltips
04133  *******************************************************************/
04134 
04135 class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
04136 {
04137   public:
04138     MailBodyVisitor() : mResult( "" ) {}
04139 
04140     bool act( IncidenceBase *incidence )
04141     {
04142       mResult = "";
04143       return incidence ? incidence->accept( *this ) : false;
04144     }
04145     QString result() const { return mResult; }
04146 
04147   protected:
04148     bool visit( Event *event );
04149     bool visit( Todo *todo );
04150     bool visit( Journal *journal );
04151     bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); }
04152   protected:
04153     QString mResult;
04154 };
04155 
04156 
04157 static QString mailBodyIncidence( Incidence *incidence )
04158 {
04159   QString body;
04160   if ( !incidence->summary().isEmpty() ) {
04161     body += i18n("Summary: %1\n").arg( incidence->summary() );
04162   }
04163   if ( !incidence->organizer().isEmpty() ) {
04164     body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() );
04165   }
04166   if ( !incidence->location().isEmpty() ) {
04167     body += i18n("Location: %1\n").arg( incidence->location() );
04168   }
04169   return body;
04170 }
04171 
04172 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
04173 {
04174   QString recurrence[]= {i18n("no recurrence", "None"),
04175     i18n("Minutely"), i18n("Hourly"), i18n("Daily"),
04176     i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"),
04177     i18n("Yearly"), i18n("Yearly"), i18n("Yearly")};
04178 
04179   mResult = mailBodyIncidence( event );
04180   mResult += i18n("Start Date: %1\n").
04181              arg( IncidenceFormatter::dateToString( event->dtStart(), true ) );
04182   if ( !event->doesFloat() ) {
04183     mResult += i18n("Start Time: %1\n").
04184                arg( IncidenceFormatter::timeToString( event->dtStart(), true ) );
04185   }
04186   if ( event->dtStart() != event->dtEnd() ) {
04187     mResult += i18n("End Date: %1\n").
04188                arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) );
04189   }
04190   if ( !event->doesFloat() ) {
04191     mResult += i18n("End Time: %1\n").
04192                arg( IncidenceFormatter::timeToString( event->dtEnd(), true ) );
04193   }
04194   if ( event->doesRecur() ) {
04195     Recurrence *recur = event->recurrence();
04196     // TODO: Merge these two to one of the form "Recurs every 3 days"
04197     mResult += i18n("Recurs: %1\n")
04198              .arg( recurrence[ recur->recurrenceType() ] );
04199     mResult += i18n("Frequency: %1\n")
04200              .arg( event->recurrence()->frequency() );
04201 
04202     if ( recur->duration() > 0 ) {
04203       mResult += i18n ("Repeats once", "Repeats %n times", recur->duration());
04204       mResult += '\n';
04205     } else {
04206       if ( recur->duration() != -1 ) {
04207 // TODO_Recurrence: What to do with floating
04208         QString endstr;
04209         if ( event->doesFloat() ) {
04210           endstr = KGlobal::locale()->formatDate( recur->endDate() );
04211         } else {
04212           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() );
04213         }
04214         mResult += i18n("Repeat until: %1\n").arg( endstr );
04215       } else {
04216         mResult += i18n("Repeats forever\n");
04217       }
04218     }
04219   }
04220   QString details = event->description();
04221   if ( !details.isEmpty() ) {
04222     mResult += i18n("Details:\n%1\n").arg( details );
04223   }
04224   return !mResult.isEmpty();
04225 }
04226 
04227 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
04228 {
04229   mResult = mailBodyIncidence( todo );
04230 
04231   if ( todo->hasStartDate() ) {
04232     mResult += i18n("Start Date: %1\n").
04233                arg( IncidenceFormatter::dateToString( todo->dtStart( false ), true ) );
04234     if ( !todo->doesFloat() ) {
04235       mResult += i18n("Start Time: %1\n").
04236                  arg( IncidenceFormatter::timeToString( todo->dtStart( false ),true ) );
04237     }
04238   }
04239   if ( todo->hasDueDate() ) {
04240     mResult += i18n("Due Date: %1\n").
04241                arg( IncidenceFormatter::dateToString( todo->dtDue(), true ) );
04242     if ( !todo->doesFloat() ) {
04243       mResult += i18n("Due Time: %1\n").
04244                  arg( IncidenceFormatter::timeToString( todo->dtDue(), true ) );
04245     }
04246   }
04247   QString details = todo->description();
04248   if ( !details.isEmpty() ) {
04249     mResult += i18n("Details:\n%1\n").arg( details );
04250   }
04251   return !mResult.isEmpty();
04252 }
04253 
04254 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
04255 {
04256   mResult = mailBodyIncidence( journal );
04257   mResult += i18n("Date: %1\n").
04258              arg( IncidenceFormatter::dateToString( journal->dtStart(), true ) );
04259   if ( !journal->doesFloat() ) {
04260     mResult += i18n("Time: %1\n").
04261                arg( IncidenceFormatter::timeToString( journal->dtStart(), true ) );
04262   }
04263   if ( !journal->description().isEmpty() )
04264     mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() );
04265   return !mResult.isEmpty();
04266 }
04267 
04268 
04269 
04270 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
04271 {
04272   if ( !incidence )
04273     return QString::null;
04274 
04275   MailBodyVisitor v;
04276   if ( v.act( incidence ) ) {
04277     return v.result();
04278   }
04279   return QString::null;
04280 }
04281 
04282 static QString recurEnd( Incidence *incidence )
04283 {
04284   QString endstr;
04285   if ( incidence->doesFloat() ) {
04286     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
04287   } else {
04288     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
04289   }
04290   return endstr;
04291 }
04292 
04293 /************************************
04294  *  More static formatting functions
04295  ************************************/
04296 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
04297 {
04298   if ( !incidence->doesRecur() ) {
04299     return i18n( "No recurrence" );
04300   }
04301   QStringList dayList;
04302   dayList.append( i18n( "31st Last" ) );
04303   dayList.append( i18n( "30th Last" ) );
04304   dayList.append( i18n( "29th Last" ) );
04305   dayList.append( i18n( "28th Last" ) );
04306   dayList.append( i18n( "27th Last" ) );
04307   dayList.append( i18n( "26th Last" ) );
04308   dayList.append( i18n( "25th Last" ) );
04309   dayList.append( i18n( "24th Last" ) );
04310   dayList.append( i18n( "23rd Last" ) );
04311   dayList.append( i18n( "22nd Last" ) );
04312   dayList.append( i18n( "21st Last" ) );
04313   dayList.append( i18n( "20th Last" ) );
04314   dayList.append( i18n( "19th Last" ) );
04315   dayList.append( i18n( "18th Last" ) );
04316   dayList.append( i18n( "17th Last" ) );
04317   dayList.append( i18n( "16th Last" ) );
04318   dayList.append( i18n( "15th Last" ) );
04319   dayList.append( i18n( "14th Last" ) );
04320   dayList.append( i18n( "13th Last" ) );
04321   dayList.append( i18n( "12th Last" ) );
04322   dayList.append( i18n( "11th Last" ) );
04323   dayList.append( i18n( "10th Last" ) );
04324   dayList.append( i18n( "9th Last" ) );
04325   dayList.append( i18n( "8th Last" ) );
04326   dayList.append( i18n( "7th Last" ) );
04327   dayList.append( i18n( "6th Last" ) );
04328   dayList.append( i18n( "5th Last" ) );
04329   dayList.append( i18n( "4th Last" ) );
04330   dayList.append( i18n( "3rd Last" ) );
04331   dayList.append( i18n( "2nd Last" ) );
04332   dayList.append( i18n( "last day of the month", "Last" ) );
04333   dayList.append( i18n( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
04334   dayList.append( i18n( "1st" ) );
04335   dayList.append( i18n( "2nd" ) );
04336   dayList.append( i18n( "3rd" ) );
04337   dayList.append( i18n( "4th" ) );
04338   dayList.append( i18n( "5th" ) );
04339   dayList.append( i18n( "6th" ) );
04340   dayList.append( i18n( "7th" ) );
04341   dayList.append( i18n( "8th" ) );
04342   dayList.append( i18n( "9th" ) );
04343   dayList.append( i18n( "10th" ) );
04344   dayList.append( i18n( "11th" ) );
04345   dayList.append( i18n( "12th" ) );
04346   dayList.append( i18n( "13th" ) );
04347   dayList.append( i18n( "14th" ) );
04348   dayList.append( i18n( "15th" ) );
04349   dayList.append( i18n( "16th" ) );
04350   dayList.append( i18n( "17th" ) );
04351   dayList.append( i18n( "18th" ) );
04352   dayList.append( i18n( "19th" ) );
04353   dayList.append( i18n( "20th" ) );
04354   dayList.append( i18n( "21st" ) );
04355   dayList.append( i18n( "22nd" ) );
04356   dayList.append( i18n( "23rd" ) );
04357   dayList.append( i18n( "24th" ) );
04358   dayList.append( i18n( "25th" ) );
04359   dayList.append( i18n( "26th" ) );
04360   dayList.append( i18n( "27th" ) );
04361   dayList.append( i18n( "28th" ) );
04362   dayList.append( i18n( "29th" ) );
04363   dayList.append( i18n( "30th" ) );
04364   dayList.append( i18n( "31st" ) );
04365 
04366   int weekStart = KGlobal::locale()->weekStartDay();
04367   QString dayNames;
04368 
04369   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
04370 
04371   Recurrence *recur = incidence->recurrence();
04372 
04373   QString recurStr;
04374   switch ( recur->recurrenceType() ) {
04375   case Recurrence::rNone:
04376     return i18n( "No recurrence" );
04377 
04378   case Recurrence::rMinutely:
04379     recurStr = i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() );
04380     if ( recur->duration() != -1 ) {
04381       recurStr = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
04382       if ( recur->duration() >  0 ) {
04383         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04384       }
04385     }
04386     break;
04387 
04388   case Recurrence::rHourly:
04389     recurStr = i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() );
04390     if ( recur->duration() != -1 ) {
04391       recurStr = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
04392       if ( recur->duration() >  0 ) {
04393         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04394       }
04395     }
04396     break;
04397 
04398   case Recurrence::rDaily:
04399     recurStr = i18n( "Recurs daily", "Recurs every %n days", recur->frequency() );
04400     if ( recur->duration() != -1 ) {
04401       recurStr = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
04402       if ( recur->duration() >  0 ) {
04403         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04404       }
04405     }
04406     break;
04407 
04408   case Recurrence::rWeekly:
04409   {
04410     recurStr = i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() );
04411 
04412     bool addSpace = false;
04413     for ( int i = 0; i < 7; ++i ) {
04414       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
04415         if ( addSpace ) {
04416           dayNames.append( i18n( "separator for list of days", ", " ) );
04417         }
04418         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, true ) );
04419         addSpace = true;
04420       }
04421     }
04422     if ( dayNames.isEmpty() ) {
04423       dayNames = i18n( "Recurs weekly on no days", "no days" );
04424     }
04425     if ( recur->duration() != -1 ) {
04426       recurStr = i18n( "%1 on %2 until %3" ).
04427                  arg( recurStr ).arg( dayNames ).arg( recurEnd( incidence ) );
04428       if ( recur->duration() >  0 ) {
04429         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04430       }
04431     } else {
04432       recurStr = i18n( "%1 on %2" ).arg( recurStr ).arg( dayNames );
04433     }
04434     break;
04435   }
04436 
04437   case Recurrence::rMonthlyPos:
04438   {
04439     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
04440 
04441     if ( !recur->monthPositions().isEmpty() ) {
04442       KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
04443       if ( recur->duration() != -1 ) {
04444         recurStr = i18n( "%1 on the %2 %3 until %4" ).
04445                    arg( recurStr ).
04446                    arg( dayList[rule.pos() + 31] ).
04447                    arg( calSys->weekDayName( rule.day(), false ) ).
04448                    arg( recurEnd( incidence ) );
04449         if ( recur->duration() >  0 ) {
04450           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04451         }
04452       } else {
04453         recurStr = i18n( "%1 on the %2 %3" ).
04454                    arg( recurStr ).
04455                    arg( dayList[rule.pos() + 31] ).
04456                    arg( calSys->weekDayName( rule.day(), false ) );
04457       }
04458     }
04459     break;
04460   }
04461 
04462   case Recurrence::rMonthlyDay:
04463   {
04464     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
04465 
04466     if ( !recur->monthDays().isEmpty() ) {
04467       int days = recur->monthDays()[0];
04468       if ( recur->duration() != -1 ) {
04469         recurStr = i18n( "%1 on the %2 day until %3" ).
04470                    arg( recurStr ).
04471                    arg( dayList[days + 31] ).
04472                    arg( recurEnd( incidence ) );
04473         if ( recur->duration() >  0 ) {
04474           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04475         }
04476       } else {
04477         recurStr = i18n( "%1 on the %2 day" ).arg( recurStr ).arg( dayList[days + 31] );
04478       }
04479     }
04480     break;
04481   }
04482   case Recurrence::rYearlyMonth:
04483   {
04484     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
04485 
04486     if ( recur->duration() != -1 ) {
04487       if ( !recur->yearDates().isEmpty() ) {
04488         recurStr = i18n( "%1 on %2 %3 until %4" ).
04489                    arg( recurStr ).
04490                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
04491                    arg( dayList[ recur->yearDates()[0] + 31 ] ).
04492                    arg( recurEnd( incidence ) );
04493         if ( recur->duration() >  0 ) {
04494           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04495         }
04496       }
04497     } else {
04498       if ( !recur->yearDates().isEmpty() ) {
04499         recurStr = i18n( "%1 on %2 %3" ).
04500                    arg( recurStr ).
04501                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
04502                    arg( dayList[ recur->yearDates()[0] + 31 ] );
04503       } else {
04504         if ( !recur->yearMonths().isEmpty() ) {
04505           recurStr = i18n( "Recurs yearly on %1 %2" ).
04506                      arg( calSys->monthName( recur->yearMonths()[0],
04507                                              recur->startDate().year() ) ).
04508                      arg( dayList[ recur->startDate().day() + 31 ] );
04509         } else {
04510           recurStr = i18n( "Recurs yearly on %1 %2" ).
04511                      arg( calSys->monthName( recur->startDate().month(),
04512                                              recur->startDate().year() ) ).
04513                      arg( dayList[ recur->startDate().day() + 31 ] );
04514         }
04515       }
04516     }
04517     break;
04518   }
04519   case Recurrence::rYearlyDay:
04520   {
04521     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
04522     if ( !recur->yearDays().isEmpty() ) {
04523       if ( recur->duration() != -1 ) {
04524         recurStr = i18n( "%1 on day %2 until %3" ).
04525                    arg( recurStr ).
04526                    arg( recur->yearDays()[0] ).
04527                    arg( recurEnd( incidence ) );
04528         if ( recur->duration() >  0 ) {
04529           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04530         }
04531       } else {
04532         recurStr = i18n( "%1 on day %2" ).arg( recurStr ).arg( recur->yearDays()[0] );
04533       }
04534     }
04535     break;
04536   }
04537   case Recurrence::rYearlyPos:
04538   {
04539     recurStr = i18n( "Every year", "Every %n years", recur->frequency() );
04540     if ( !recur->yearPositions().isEmpty() && !recur->yearMonths().isEmpty() ) {
04541       KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
04542       if ( recur->duration() != -1 ) {
04543         recurStr = i18n( "%1 on the %2 %3 of %4 until %5" ).
04544                    arg( recurStr ).
04545                    arg( dayList[rule.pos() + 31] ).
04546                    arg( calSys->weekDayName( rule.day(), false ) ).
04547                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
04548                    arg( recurEnd( incidence ) );
04549         if ( recur->duration() >  0 ) {
04550           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04551         }
04552       } else {
04553         recurStr = i18n( "%1 on the %2 %3 of %4" ).
04554                    arg( recurStr ).
04555                    arg( dayList[rule.pos() + 31] ).
04556                    arg( calSys->weekDayName( rule.day(), false ) ).
04557                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
04558       }
04559     }
04560    break;
04561   }
04562   }
04563 
04564   if ( recurStr.isEmpty() ) {
04565     recurStr = i18n( "Incidence recurs" );
04566   }
04567   // Now, append the EXDATEs
04568   DateTimeList l = recur->exDateTimes();
04569   DateTimeList::ConstIterator il;
04570   QStringList exStr;
04571   for ( il = l.constBegin(); il != l.constEnd(); ++il ) {
04572     switch ( recur->recurrenceType() ) {
04573     case Recurrence::rMinutely:
04574       exStr << i18n( "minute %1" ).arg( (*il).time().minute() );
04575       break;
04576     case Recurrence::rHourly:
04577       exStr << KGlobal::locale()->formatTime( (*il).time() );
04578       break;
04579     case Recurrence::rDaily:
04580       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04581       break;
04582     case Recurrence::rWeekly:
04583       exStr << calSys->weekDayName( (*il).date(), true );
04584       break;
04585     case Recurrence::rMonthlyPos:
04586       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04587       break;
04588     case Recurrence::rMonthlyDay:
04589       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04590       break;
04591     case Recurrence::rYearlyMonth:
04592       exStr << calSys->monthName( (*il).date(), false );
04593       break;
04594     case Recurrence::rYearlyDay:
04595       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04596       break;
04597     case Recurrence::rYearlyPos:
04598       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04599       break;
04600     }
04601   }
04602 
04603   DateList d = recur->exDates();
04604   DateList::ConstIterator dl;
04605   for ( dl = d.constBegin(); dl != d.constEnd(); ++dl ) {
04606     switch ( recur->recurrenceType() ) {
04607     case Recurrence::rDaily:
04608       exStr << KGlobal::locale()->formatDate( (*dl), true );
04609       break;
04610     case Recurrence::rWeekly:
04611       // exStr << calSys->weekDayName( (*dl), true );
04612       // kolab/issue4735, should be ( excluding 3 days ), instead of excluding( Fr,Fr,Fr )
04613       if ( exStr.isEmpty() )
04614         exStr << i18n( "1 day", "%n days", recur->exDates().count() );
04615       break;
04616     case Recurrence::rMonthlyPos:
04617       exStr << KGlobal::locale()->formatDate( (*dl), true );
04618       break;
04619     case Recurrence::rMonthlyDay:
04620       exStr << KGlobal::locale()->formatDate( (*dl), true );
04621       break;
04622     case Recurrence::rYearlyMonth:
04623       exStr << calSys->monthName( (*dl), false );
04624       break;
04625     case Recurrence::rYearlyDay:
04626       exStr << KGlobal::locale()->formatDate( (*dl), true );
04627       break;
04628     case Recurrence::rYearlyPos:
04629       exStr << KGlobal::locale()->formatDate( (*dl), true );
04630       break;
04631     }
04632   }
04633 
04634   if ( !exStr.isEmpty() ) {
04635     recurStr = i18n( "%1 (excluding %2)" ).arg( recurStr, exStr.join( "," ) );
04636   }
04637 
04638   return recurStr;
04639 }
04640 
04641 QString IncidenceFormatter::timeToString( const QDateTime &date, bool shortfmt )
04642 {
04643   return KGlobal::locale()->formatTime( date.time(), !shortfmt );
04644 }
04645 
04646 QString IncidenceFormatter::dateToString( const QDateTime &date, bool shortfmt )
04647 {
04648   return
04649     KGlobal::locale()->formatDate( date.date(), shortfmt );
04650 }
04651 
04652 QString IncidenceFormatter::dateTimeToString( const QDateTime &date,
04653                                               bool allDay, bool shortfmt )
04654 {
04655   if ( allDay ) {
04656     return dateToString( date, shortfmt );
04657   }
04658 
04659   return  KGlobal::locale()->formatDateTime( date, shortfmt );
04660 }
04661 
04662 QString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence )
04663 {
04664   if ( !calendar || !incidence ) {
04665     return QString::null;
04666   }
04667 
04668   CalendarResources *calendarResource = dynamic_cast<CalendarResources*>( calendar );
04669   if ( !calendarResource ) {
04670     return QString::null;
04671   }
04672 
04673   ResourceCalendar *resourceCalendar = calendarResource->resource( incidence );
04674   if ( resourceCalendar ) {
04675     if ( !resourceCalendar->subresources().isEmpty() ) {
04676       QString subRes = resourceCalendar->subresourceIdentifier( incidence );
04677       if ( subRes.isEmpty() ) {
04678         return resourceCalendar->resourceName();
04679       } else {
04680         return resourceCalendar->labelForSubresource( subRes );
04681       }
04682     }
04683     return resourceCalendar->resourceName();
04684   }
04685 
04686   return QString::null;
04687 }
04688 
04689 static QString secs2Duration( int secs )
04690 {
04691   QString tmp;
04692   int days = secs / 86400;
04693   if ( days > 0 ) {
04694     tmp += i18n( "1 day", "%n days", days );
04695     tmp += ' ';
04696     secs -= ( days * 86400 );
04697   }
04698   int hours = secs / 3600;
04699   if ( hours > 0 ) {
04700     tmp += i18n( "1 hour", "%n hours", hours );
04701     tmp += ' ';
04702     secs -= ( hours * 3600 );
04703   }
04704   int mins = secs / 60;
04705   if ( mins > 0 ) {
04706     tmp += i18n( "1 minute", "%n minutes",  mins );
04707   }
04708   return tmp;
04709 }
04710 
04711 QString IncidenceFormatter::durationString( Incidence *incidence )
04712 {
04713   QString tmp;
04714   if ( incidence->type() == "Event" ) {
04715     Event *event = static_cast<Event *>( incidence );
04716     if ( event->hasEndDate() ) {
04717       if ( !event->doesFloat() ) {
04718         tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
04719       } else {
04720         tmp = i18n( "1 day", "%n days",
04721                     event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
04722       }
04723     } else {
04724       tmp = i18n( "forever" );
04725     }
04726   } else if ( incidence->type() == "Todo" ) {
04727     Todo *todo = static_cast<Todo *>( incidence );
04728     if ( todo->hasDueDate() ) {
04729       if ( todo->hasStartDate() ) {
04730         if ( !todo->doesFloat() ) {
04731           tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
04732         } else {
04733           tmp = i18n( "1 day", "%n days",
04734                       todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
04735         }
04736       }
04737     }
04738   }
04739   return tmp;
04740 }
04741 
04742 QStringList IncidenceFormatter::reminderStringList( Incidence *incidence, bool shortfmt )
04743 {
04744   //TODO: implement shortfmt=false
04745   Q_UNUSED( shortfmt );
04746 
04747   QStringList reminderStringList;
04748 
04749   if ( incidence ) {
04750     Alarm::List alarms = incidence->alarms();
04751     Alarm::List::ConstIterator it;
04752     for ( it = alarms.begin(); it != alarms.end(); ++it ) {
04753       Alarm *alarm = *it;
04754       int offset = 0;
04755       QString remStr, atStr, offsetStr;
04756       if ( alarm->hasTime() ) {
04757         offset = 0;
04758         if ( alarm->time().isValid() ) {
04759           atStr = KGlobal::locale()->formatDateTime( alarm->time() );
04760         }
04761       } else if ( alarm->hasStartOffset() ) {
04762         offset = alarm->startOffset().asSeconds();
04763         if ( offset < 0 ) {
04764           offset = -offset;
04765           offsetStr = i18n( "N days/hours/minutes before the start datetime",
04766                             "%1 before the start" );
04767         } else if ( offset > 0 ) {
04768           offsetStr = i18n( "N days/hours/minutes after the start datetime",
04769                             "%1 after the start" );
04770         } else { //offset is 0
04771           if ( incidence->dtStart().isValid() ) {
04772             atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() );
04773           }
04774         }
04775       } else if ( alarm->hasEndOffset() ) {
04776         offset = alarm->endOffset().asSeconds();
04777         if ( offset < 0 ) {
04778           offset = -offset;
04779           if ( incidence->type() == "Todo" ) {
04780             offsetStr = i18n( "N days/hours/minutes before the due datetime",
04781                               "%1 before the to-do is due" );
04782           } else {
04783             offsetStr = i18n( "N days/hours/minutes before the end datetime",
04784                               "%1 before the end" );
04785           }
04786         } else if ( offset > 0 ) {
04787           if ( incidence->type() == "Todo" ) {
04788             offsetStr = i18n( "N days/hours/minutes after the due datetime",
04789                               "%1 after the to-do is due" );
04790           } else {
04791             offsetStr = i18n( "N days/hours/minutes after the end datetime",
04792                               "%1 after the end" );
04793           }
04794         } else { //offset is 0
04795           if ( incidence->type() == "Todo" ) {
04796             Todo *t = static_cast<Todo *>( incidence );
04797             if ( t->dtDue().isValid() ) {
04798               atStr = KGlobal::locale()->formatDateTime( t->dtDue() );
04799             }
04800           } else {
04801             Event *e = static_cast<Event *>( incidence );
04802             if ( e->dtEnd().isValid() ) {
04803               atStr = KGlobal::locale()->formatDateTime( e->dtEnd() );
04804             }
04805           }
04806         }
04807       }
04808       if ( offset == 0 ) {
04809         if ( !atStr.isEmpty() ) {
04810           remStr = i18n( "reminder occurs at datetime", "at %1" ).arg( atStr );
04811         }
04812       } else {
04813         remStr = offsetStr.arg( secs2Duration( offset ) );
04814       }
04815 
04816       if ( alarm->repeatCount() > 0 ) {
04817         QString countStr = i18n( "repeats once", "repeats %n times", alarm->repeatCount() );
04818         QString intervalStr = i18n( "interval is N days/hours/minutes", "interval is %1" ).
04819                               arg( secs2Duration( alarm->snoozeTime().asSeconds() ) );
04820         QString repeatStr = i18n( "(repeat string, interval string)", "(%1, %2)" ).
04821                             arg( countStr, intervalStr );
04822         remStr = remStr + ' ' + repeatStr;
04823 
04824       }
04825       reminderStringList << remStr;
04826     }
04827   }
04828 
04829   return reminderStringList;
04830 }
KDE Home | KDE Accessibility Home | Description of Access Keys