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 ( !existingIncidence ) {
02897       kdDebug() << "formatICalInvitationHelper: Incidence is not in our calendar. uid: "
02898                 << incBase->uid() << "; incidence count: "
02899                 << helper->calendar()->incidences().count()
02900                 << "; Calendar = " << helper->calendar()
02901                 << endl;
02902     }
02903 
02904     if ( !CalHelper::isMyCalendarIncidence( helper->calendar(), existingIncidence ) ) {
02905       //sergio, 03/2011: This never happens I think. If the incidence is in a shared calendar
02906       //then it's uid will be a new one, and won't match incBase->uid().
02907       existingIncidence = 0;
02908     }
02909     if ( !existingIncidence ) {
02910       const Incidence::List list = helper->calendar()->incidences();
02911       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
02912         if ( (*it)->schedulingID() == incBase->uid() &&
02913              CalHelper::isMyCalendarIncidence( helper->calendar(), *it ) ) {
02914           existingIncidence = *it;
02915           break;
02916         }
02917       }
02918     }
02919   }
02920 
02921   Incidence *inc = dynamic_cast<Incidence*>( incBase ); // the incidence in the invitation email
02922 
02923   // First make the text of the message
02924   QString html;
02925 
02926   QString tableStyle = QString::fromLatin1(
02927     "style=\"border: solid 1px; margin: 0em;\"" );
02928   QString tableHead = QString::fromLatin1(
02929     "<div align=\"center\">"
02930     "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
02931     "<tr><td>").arg(tableStyle);
02932 
02933   html += tableHead;
02934   InvitationHeaderVisitor headerVisitor;
02935   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
02936   if ( !headerVisitor.act( inc, existingIncidence, msg, sender ) ) {
02937     return QString::null;
02938   }
02939   html += "<b>" + headerVisitor.result() + "</b>";
02940 
02941   if ( outlookCompareStyle ||
02942        msg->method() == Scheduler::Declinecounter ) { //use Outlook style for decline counters
02943     // use the Outlook 2007 Comparison Style
02944     InvitationBodyVisitor bodyVisitor( noHtmlMode );
02945     bool bodyOk;
02946     if ( msg->method() == Scheduler::Request || msg->method() == Scheduler::Reply ||
02947          msg->method() == Scheduler::Declinecounter ) {
02948       if ( inc && existingIncidence &&
02949            inc->lastModified() < existingIncidence->lastModified() ) {
02950         bodyOk = bodyVisitor.act( existingIncidence, inc, msg, sender );
02951       } else {
02952         bodyOk = bodyVisitor.act( inc, existingIncidence, msg, sender );
02953       }
02954     } else {
02955       bodyOk = bodyVisitor.act( inc, 0, msg, sender );
02956     }
02957     if ( bodyOk ) {
02958       html += bodyVisitor.result();
02959     } else {
02960       return QString::null;
02961     }
02962   } else {
02963     // use our "Classic: Comparison Style
02964     InvitationBodyVisitor bodyVisitor( noHtmlMode );
02965     if ( !bodyVisitor.act( inc, 0, msg, sender ) )
02966       return QString::null;
02967     html += bodyVisitor.result();
02968 
02969     if ( msg->method() == Scheduler::Request ) {
02970       IncidenceCompareVisitor compareVisitor;
02971       if ( compareVisitor.act( inc, existingIncidence, msg->method() ) ) {
02972         html += "<p align=\"left\">";
02973         if ( senderIsOrganizer( inc, sender ) ) {
02974           html += i18n( "The following changes have been made by the organizer:" );
02975         } else if ( !sender.isEmpty() ) {
02976           html += i18n( "The following changes have been made by %1:" ).arg( sender );
02977         } else {
02978           html += i18n( "The following changes have been made:" );
02979         }
02980         html += "</p>";
02981         html += compareVisitor.result();
02982       }
02983     }
02984     if ( msg->method() == Scheduler::Reply ) {
02985       IncidenceCompareVisitor compareVisitor;
02986       if ( compareVisitor.act( inc, existingIncidence, msg->method() ) ) {
02987         html += "<p align=\"left\">";
02988         if ( !sender.isEmpty() ) {
02989           html += i18n( "The following changes have been made by %1:" ).arg( sender );
02990         } else {
02991           html += i18n( "The following changes have been made by an attendee:" );
02992         }
02993         html += "</p>";
02994         html += compareVisitor.result();
02995       }
02996     }
02997   }
02998 
02999   // determine if I am the organizer for this invitation
03000   bool myInc = iamOrganizer( inc );
03001 
03002   // determine if the invitation response has already been recorded
03003   bool rsvpRec = false;
03004   Attendee *ea = 0;
03005   if ( !myInc ) {
03006     Incidence *rsvpIncidence = existingIncidence;
03007     if ( !rsvpIncidence && inc && inc->revision() > 0 ) {
03008       rsvpIncidence = inc;
03009     }
03010     if ( rsvpIncidence ) {
03011       ea = findMyAttendee( rsvpIncidence );
03012     }
03013     if ( ea &&
03014          ( ea->status() == Attendee::Accepted ||
03015            ea->status() == Attendee::Declined ||
03016            ea->status() == Attendee::Tentative ) ) {
03017       rsvpRec = true;
03018     }
03019   }
03020 
03021   // determine invitation role
03022   QString role;
03023   bool isDelegated = false;
03024   Attendee *a = findMyAttendee( inc );
03025   if ( !a && inc ) {
03026     if ( !inc->attendees().isEmpty() ) {
03027       a = inc->attendees().first();
03028     }
03029   }
03030   if ( a ) {
03031     isDelegated = ( a->status() == Attendee::Delegated );
03032     role = Attendee::roleName( a->role() );
03033   }
03034 
03035   // determine if RSVP needed, not-needed, or response already recorded
03036   bool rsvpReq = rsvpRequested( inc );
03037   if ( !myInc && a ) {
03038     html += "<br/>";
03039     html += "<i><u>";
03040     if ( rsvpRec && inc ) {
03041       if ( inc->revision() == 0 ) {
03042         html += i18n( "Your <b>%1</b> response has been recorded" ).
03043                 arg( ea->statusStr() );
03044       } else {
03045         html += i18n( "Your status for this invitation is <b>%1</b>" ).
03046                 arg( ea->statusStr() );
03047       }
03048       rsvpReq = false;
03049     } else if ( msg->method() == Scheduler::Cancel ) {
03050       html += i18n( "This invitation was canceled" );
03051     } else if ( msg->method() == Scheduler::Add ) {
03052       html += i18n( "This invitation was accepted" );
03053     } else if ( msg->method() == Scheduler::Declinecounter ) {
03054       rsvpReq = true;
03055       html += rsvpRequestedStr( rsvpReq, role );
03056     } else {
03057       if ( !isDelegated ) {
03058         html += rsvpRequestedStr( rsvpReq, role );
03059       } else {
03060         html += i18n( "Awaiting delegation response" );
03061       }
03062     }
03063     html += "</u></i>";
03064   }
03065 
03066   // Print if the organizer gave you a preset status
03067   if ( !myInc ) {
03068     if ( inc && inc->revision() == 0 ) {
03069       QString statStr = myStatusStr( inc );
03070       if ( !statStr.isEmpty() ) {
03071         html += "<br/>";
03072         html += "<i>";
03073         html += statStr;
03074         html += "</i>";
03075       }
03076     }
03077   }
03078 
03079   // Add groupware links
03080 
03081   html += "<br><table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td></tr>";
03082 
03083   switch ( msg->method() ) {
03084     case Scheduler::Publish:
03085     case Scheduler::Request:
03086     case Scheduler::Refresh:
03087     case Scheduler::Add:
03088     {
03089       if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
03090         html += "<tr><td colspan=\"13\">";
03091         if ( inc->type() == "Todo" ) {
03092           html += helper->makeLink( "reply", i18n( "[Record in my task list]" ) );
03093         } else {
03094           html += helper->makeLink( "reply", i18n( "[Record in my calendar]" ) );
03095         }
03096         html += "</td></tr>";
03097       }
03098 
03099       if ( !myInc && a ) {
03100         html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
03101       }
03102       break;
03103     }
03104 
03105     case Scheduler::Cancel:
03106       // Remove invitation
03107       if ( inc ) {
03108         html += "<tr><td colspan=\"13\">";
03109         if ( inc->type() == "Todo" ) {
03110           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my task list]" ) );
03111         } else {
03112           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my calendar]" ) );
03113         }
03114         html += "</td></tr>";
03115       }
03116       break;
03117 
03118     case Scheduler::Reply:
03119     {
03120       // Record invitation response
03121       Attendee *a = 0;
03122       Attendee *ea = 0;
03123       if ( inc ) {
03124         // First, determine if this reply is really a counter in disguise.
03125         if ( replyMeansCounter( inc ) ) {
03126           html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
03127           break;
03128         }
03129 
03130         // Next, maybe this is a declined reply that was delegated from me?
03131         // find first attendee who is delegated-from me
03132         // look a their PARTSTAT response, if the response is declined,
03133         // then we need to start over which means putting all the action
03134         // buttons and NOT putting on the [Record response..] button
03135         a = findDelegatedFromMyAttendee( inc );
03136         if ( a ) {
03137           if ( a->status() != Attendee::Accepted ||
03138                a->status() != Attendee::Tentative ) {
03139             html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
03140             break;
03141           }
03142         }
03143 
03144         // Finally, simply allow a Record of the reply
03145         if ( !inc->attendees().isEmpty() ) {
03146           a = inc->attendees().first();
03147         }
03148         if ( a ) {
03149           ea = findAttendee( existingIncidence, a->email() );
03150         }
03151       }
03152       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
03153         // we have seen this invitation and recorded a response
03154         if ( inc->revision() > 0 &&
03155              ( inc->lastModified() > existingIncidence->lastModified() ) ) {
03156           // newer than we have recorded, so an update
03157           html += "<tr><td>";
03158           if ( inc->type() == "Todo" ) {
03159             html += helper->makeLink( "reply", i18n( "[Record update in my task list]" ) );
03160           } else {
03161             html += helper->makeLink( "reply", i18n( "[Record update in my calendar]" ) );
03162           }
03163           html += "</td></tr>";
03164         } else {
03165           // not newer than we have recorded
03166           html += "<br><u><i>";
03167           if ( inc->revision() > 0 ) {
03168             // an update we already have recorded
03169             html += i18n( "This update has been recorded" );
03170           } else {
03171             // not an update
03172             html += i18n( "This <b>%1</b> response has been recorded" ).arg( ea->statusStr() );
03173           }
03174           html += "</i></u>";
03175         }
03176       } else {
03177         // Not seen or recorded with a response yet
03178         if ( inc ) {
03179           html += "<tr><td>";
03180           if ( inc->type() == "Todo" ) {
03181             html += helper->makeLink( "reply", i18n( "[Record response in my task list]" ) );
03182           } else {
03183             html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
03184           }
03185           html += "</td></tr>";
03186         }
03187       }
03188       break;
03189     }
03190 
03191     case Scheduler::Counter:
03192       // Counter proposal
03193       html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
03194       break;
03195 
03196     case Scheduler::Declinecounter:
03197       html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
03198       break;
03199 
03200     case Scheduler::NoMethod:
03201       break;
03202   }
03203 
03204   // close the groupware table
03205   html += "</td></tr></table>";
03206 
03207   // Add the attendee list
03208   if ( myInc ) {
03209     html += invitationRsvpList( existingIncidence, a );
03210   } else {
03211     html += invitationAttendeeList( inc );
03212   }
03213 
03214   // close the top-level table
03215   html += "</td></tr></table><br></div>";
03216 
03217   // Add the attachment list
03218   html += invitationAttachments( helper, inc );
03219 
03220   return html;
03221 }
03222 
03223 QString IncidenceFormatter::formatICalInvitation( QString invitation,
03224                                                   Calendar *mCalendar,
03225                                                   InvitationFormatterHelper *helper )
03226 {
03227   return formatICalInvitationHelper( invitation, mCalendar, helper, false, QString(), true );
03228 }
03229 
03230 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
03231                                                         Calendar *mCalendar,
03232                                                         InvitationFormatterHelper *helper )
03233 {
03234   return formatICalInvitationHelper( invitation, mCalendar, helper, true, QString(), true );
03235 }
03236 
03237 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
03238                                                         Calendar *mCalendar,
03239                                                         InvitationFormatterHelper *helper,
03240                                                         const QString &sender )
03241 {
03242   return formatICalInvitationHelper( invitation, mCalendar, helper, true, sender, true );
03243 }
03244 
03245 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
03246                                                         Calendar *mCalendar,
03247                                                         InvitationFormatterHelper *helper,
03248                                                         const QString &sender,
03249                                                         bool outlookCompareStyle )
03250 {
03251   return formatICalInvitationHelper( invitation, mCalendar, helper, true, sender, outlookCompareStyle );
03252 }
03253 
03254 /*******************************************************************
03255  *  Helper functions for the msTNEF -> VPart converter
03256  *******************************************************************/
03257 
03258 
03259 //-----------------------------------------------------------------------------
03260 
03261 static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
03262                            const QString& fallback = QString::null)
03263 {
03264   return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
03265                             fallback );
03266 }
03267 
03268 static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
03269                            const QString& fallback = QString::null )
03270 {
03271   return tnefMsg->findNamedProp( name, fallback );
03272 }
03273 
03274 struct save_tz { char* old_tz; char* tz_env_str; };
03275 
03276 /* temporarily go to a different timezone */
03277 static struct save_tz set_tz( const char* _tc )
03278 {
03279   const char *tc = _tc?_tc:"UTC";
03280 
03281   struct save_tz rv;
03282 
03283   rv.old_tz = 0;
03284   rv.tz_env_str = 0;
03285 
03286   //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;
03287 
03288   char* tz_env = 0;
03289   if( getenv( "TZ" ) ) {
03290     tz_env = strdup( getenv( "TZ" ) );
03291     rv.old_tz = tz_env;
03292   }
03293   char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
03294   strcpy( tmp_env, "TZ=" );
03295   strcpy( tmp_env+3, tc );
03296   putenv( tmp_env );
03297 
03298   rv.tz_env_str = tmp_env;
03299 
03300   /* tmp_env is not free'ed -- it is part of the environment */
03301 
03302   tzset();
03303   //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;
03304 
03305   return rv;
03306 }
03307 
03308 /* restore previous timezone */
03309 static void unset_tz( struct save_tz old_tz )
03310 {
03311   if( old_tz.old_tz ) {
03312     char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
03313     strcpy( tmp_env, "TZ=" );
03314     strcpy( tmp_env+3, old_tz.old_tz );
03315     putenv( tmp_env );
03316     /* tmp_env is not free'ed -- it is part of the environment */
03317     free( old_tz.old_tz );
03318   } else {
03319     /* clear TZ from env */
03320     putenv( strdup("TZ") );
03321   }
03322   tzset();
03323 
03324   /* is this OK? */
03325   if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
03326 }
03327 
03328 static QDateTime utc2Local( const QDateTime& utcdt )
03329 {
03330   struct tm tmL;
03331 
03332   save_tz tmp_tz = set_tz("UTC");
03333   time_t utc = utcdt.toTime_t();
03334   unset_tz( tmp_tz );
03335 
03336   localtime_r( &utc, &tmL );
03337   return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
03338                     QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
03339 }
03340 
03341 
03342 static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
03343                                           bool bDateOnly = false )
03344 {
03345   QDate tmpDate;
03346   QTime tmpTime;
03347   int year, month, day, hour, minute, second;
03348 
03349   if( bDateOnly ) {
03350     year = dtStr.left( 4 ).toInt();
03351     month = dtStr.mid( 4, 2 ).toInt();
03352     day = dtStr.mid( 6, 2 ).toInt();
03353     hour = 0;
03354     minute = 0;
03355     second = 0;
03356   } else {
03357     year = dtStr.left( 4 ).toInt();
03358     month = dtStr.mid( 4, 2 ).toInt();
03359     day = dtStr.mid( 6, 2 ).toInt();
03360     hour = dtStr.mid( 9, 2 ).toInt();
03361     minute = dtStr.mid( 11, 2 ).toInt();
03362     second = dtStr.mid( 13, 2 ).toInt();
03363   }
03364   tmpDate.setYMD( year, month, day );
03365   tmpTime.setHMS( hour, minute, second );
03366 
03367   if( tmpDate.isValid() && tmpTime.isValid() ) {
03368     QDateTime dT = QDateTime( tmpDate, tmpTime );
03369 
03370     if( !bDateOnly ) {
03371       // correct for GMT ( == Zulu time == UTC )
03372       if (dtStr.at(dtStr.length()-1) == 'Z') {
03373         //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
03374         //localUTCOffset( dT ) );
03375         dT = utc2Local( dT );
03376       }
03377     }
03378     return dT;
03379   } else
03380     return QDateTime();
03381 }
03382 
03383 
03384 
03385 QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef )
03386 {
03387   KTNEFParser parser;
03388   QBuffer buf( tnef );
03389   CalendarLocal cal ( QString::fromLatin1( "UTC" ) );
03390   KABC::Addressee addressee;
03391   KABC::VCardConverter cardConv;
03392   ICalFormat calFormat;
03393   Event* event = new Event();
03394 
03395   if( parser.openDevice( &buf ) ) {
03396     KTNEFMessage* tnefMsg = parser.message();
03397     //QMap<int,KTNEFProperty*> props = parser.message()->properties();
03398 
03399     // Everything depends from property PR_MESSAGE_CLASS
03400     // (this is added by KTNEFParser):
03401     QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
03402       .upper();
03403     if( !msgClass.isEmpty() ) {
03404       // Match the old class names that might be used by Outlook for
03405       // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
03406       bool bCompatClassAppointment = false;
03407       bool bCompatMethodRequest = false;
03408       bool bCompatMethodCancled = false;
03409       bool bCompatMethodAccepted = false;
03410       bool bCompatMethodAcceptedCond = false;
03411       bool bCompatMethodDeclined = false;
03412       if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
03413         bCompatClassAppointment = true;
03414         if( msgClass.endsWith( ".MTGREQ" ) )
03415           bCompatMethodRequest = true;
03416         if( msgClass.endsWith( ".MTGCNCL" ) )
03417           bCompatMethodCancled = true;
03418         if( msgClass.endsWith( ".MTGRESPP" ) )
03419           bCompatMethodAccepted = true;
03420         if( msgClass.endsWith( ".MTGRESPA" ) )
03421           bCompatMethodAcceptedCond = true;
03422         if( msgClass.endsWith( ".MTGRESPN" ) )
03423           bCompatMethodDeclined = true;
03424       }
03425       bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );
03426 
03427       if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
03428         // Compose a vCal
03429         bool bIsReply = false;
03430         QString prodID = "-//Microsoft Corporation//Outlook ";
03431         prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
03432         prodID += "MIMEDIR/EN\n";
03433         prodID += "VERSION:2.0\n";
03434         calFormat.setApplication( "Outlook", prodID );
03435 
03436         Scheduler::Method method;
03437         if( bCompatMethodRequest )
03438           method = Scheduler::Request;
03439         else if( bCompatMethodCancled )
03440           method = Scheduler::Cancel;
03441         else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
03442                  bCompatMethodDeclined ) {
03443           method = Scheduler::Reply;
03444           bIsReply = true;
03445         } else {
03446           // pending(khz): verify whether "0x0c17" is the right tag ???
03447           //
03448           // at the moment we think there are REQUESTS and UPDATES
03449           //
03450           // but WHAT ABOUT REPLIES ???
03451           //
03452           //
03453 
03454           if( tnefMsg->findProp(0x0c17) == "1" )
03455             bIsReply = true;
03456           method = Scheduler::Request;
03457         }
03458 
03460         ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );
03461 
03462         QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );
03463 
03464         if( !sSenderSearchKeyEmail.isEmpty() ) {
03465           int colon = sSenderSearchKeyEmail.find( ':' );
03466           // May be e.g. "SMTP:KHZ@KDE.ORG"
03467           if( sSenderSearchKeyEmail.find( ':' ) == -1 )
03468             sSenderSearchKeyEmail.remove( 0, colon+1 );
03469         }
03470 
03471         QString s( tnefMsg->findProp( 0x0e04 ) );
03472         QStringList attendees = QStringList::split( ';', s );
03473         if( attendees.count() ) {
03474           for( QStringList::Iterator it = attendees.begin();
03475                it != attendees.end(); ++it ) {
03476             // Skip all entries that have no '@' since these are
03477             // no mail addresses
03478             if( (*it).find('@') == -1 ) {
03479               s = (*it).stripWhiteSpace();
03480 
03481               Attendee *attendee = new Attendee( s, s, true );
03482               if( bIsReply ) {
03483                 if( bCompatMethodAccepted )
03484                   attendee->setStatus( Attendee::Accepted );
03485                 if( bCompatMethodDeclined )
03486                   attendee->setStatus( Attendee::Declined );
03487                 if( bCompatMethodAcceptedCond )
03488                   attendee->setStatus(Attendee::Tentative);
03489               } else {
03490                 attendee->setStatus( Attendee::NeedsAction );
03491                 attendee->setRole( Attendee::ReqParticipant );
03492               }
03493               event->addAttendee(attendee);
03494             }
03495           }
03496         } else {
03497           // Oops, no attendees?
03498           // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
03499           s = sSenderSearchKeyEmail;
03500           if( !s.isEmpty() ) {
03501             Attendee *attendee = new Attendee( QString::null, QString::null,
03502                                                true );
03503             if( bIsReply ) {
03504               if( bCompatMethodAccepted )
03505                 attendee->setStatus( Attendee::Accepted );
03506               if( bCompatMethodAcceptedCond )
03507                 attendee->setStatus( Attendee::Declined );
03508               if( bCompatMethodDeclined )
03509                 attendee->setStatus( Attendee::Tentative );
03510             } else {
03511               attendee->setStatus(Attendee::NeedsAction);
03512               attendee->setRole(Attendee::ReqParticipant);
03513             }
03514             event->addAttendee(attendee);
03515           }
03516         }
03517         s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
03518         if( s.isEmpty() && !bIsReply )
03519           s = sSenderSearchKeyEmail;
03520         // TODO: Use the common name?
03521         if( !s.isEmpty() )
03522           event->setOrganizer( s );
03523 
03524         s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
03525           .replace( QChar( ':' ), QString::null );
03526         event->setDtStart( QDateTime::fromString( s ) ); // ## Format??
03527 
03528         s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
03529           .replace( QChar( ':' ), QString::null );
03530         event->setDtEnd( QDateTime::fromString( s ) );
03531 
03532         s = tnefMsg->findProp( 0x8208 );
03533         event->setLocation( s );
03534 
03535         // is it OK to set this to OPAQUE always ??
03536         //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
03537         //vPart += "SEQUENCE:0\n";
03538 
03539         // is "0x0023" OK  -  or should we look for "0x0003" ??
03540         s = tnefMsg->findProp( 0x0023 );
03541         event->setUid( s );
03542 
03543         // PENDING(khz): is this value in local timezone? Must it be
03544         // adjusted? Most likely this is a bug in the server or in
03545         // Outlook - we ignore it for now.
03546         s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
03547           .replace( QChar( ':' ), QString::null );
03548         // ### libkcal always uses currentDateTime()
03549         // event->setDtStamp(QDateTime::fromString(s));
03550 
03551         s = tnefMsg->findNamedProp( "Keywords" );
03552         event->setCategories( s );
03553 
03554         s = tnefMsg->findProp( 0x1000 );
03555         event->setDescription( s );
03556 
03557         s = tnefMsg->findProp( 0x0070 );
03558         event->setSummary( s );
03559 
03560         s = tnefMsg->findProp( 0x0026 );
03561         event->setPriority( s.toInt() );
03562 
03563         // is reminder flag set ?
03564         if(!tnefMsg->findProp(0x8503).isEmpty()) {
03565           Alarm *alarm = new Alarm(event);
03566           QDateTime highNoonTime =
03567             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
03568                                      .replace( QChar( '-' ), "" )
03569                                      .replace( QChar( ':' ), "" ) );
03570           QDateTime wakeMeUpTime =
03571             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
03572                                      .replace( QChar( '-' ), "" )
03573                                      .replace( QChar( ':' ), "" ) );
03574           alarm->setTime(wakeMeUpTime);
03575 
03576           if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
03577             alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
03578           else
03579             // default: wake them up 15 minutes before the appointment
03580             alarm->setStartOffset( Duration( 15*60 ) );
03581           alarm->setDisplayAlarm( i18n( "Reminder" ) );
03582 
03583           // Sorry: the different action types are not known (yet)
03584           //        so we always set 'DISPLAY' (no sounds, no images...)
03585           event->addAlarm( alarm );
03586         }
03587         cal.addEvent( event );
03588         // we finished composing a vCal
03589       } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
03590         addressee.setUid( stringProp( tnefMsg, attMSGID ) );
03591         addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) );
03592         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
03593         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
03594         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
03595         addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) );
03596         addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) );
03597         addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
03598         addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
03599         addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) );
03600         addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) );
03601         addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) );
03602 
03603         QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
03604           .replace( QChar( '-' ), QString::null )
03605           .replace( QChar( ':' ), QString::null );
03606         if( !s.isEmpty() )
03607           addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );
03608 
03609         addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );
03610 
03611         // collect parts of Name entry
03612         addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
03613         addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
03614         addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) );
03615         addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) );
03616         addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );
03617 
03618         addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
03619         addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
03620         addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
03621         /*
03622         the MAPI property ID of this (multiline) )field is unknown:
03623         vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
03624         */
03625 
03626         KABC::Address adr;
03627         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) );
03628         adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
03629         adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
03630         adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
03631         adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) );
03632         adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
03633         adr.setType(KABC::Address::Home);
03634         addressee.insertAddress(adr);
03635 
03636         adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
03637         adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) );
03638         adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) );
03639         adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) );
03640         adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
03641         adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) );
03642         adr.setType( KABC::Address::Work );
03643         addressee.insertAddress( adr );
03644 
03645         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) );
03646         adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
03647         adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
03648         adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
03649         adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) );
03650         adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
03651         adr.setType( KABC::Address::Dom );
03652         addressee.insertAddress(adr);
03653 
03654         // problem: the 'other' address was stored by KOrganizer in
03655         //          a line looking like the following one:
03656         // 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
03657 
03658         QString nr;
03659         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
03660         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) );
03661         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
03662         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) );
03663         nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
03664         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) );
03665         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
03666         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) );
03667         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
03668         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) );
03669 
03670         s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
03671           .replace( QChar( '-' ), QString::null )
03672           .replace( QChar( ':' ), QString::null );
03673         if( !s.isEmpty() )
03674           addressee.setBirthday( QDateTime::fromString( s ) );
03675 
03676       } else if( "IPM.NOTE" == msgClass ) {
03677 
03678       } // else if ... and so on ...
03679     }
03680   }
03681 
03682   // Compose return string
03683   QString iCal = calFormat.toString( &cal );
03684   if( !iCal.isEmpty() )
03685     // This was an iCal
03686     return iCal;
03687 
03688   // Not an iCal - try a vCard
03689   KABC::VCardConverter converter;
03690   return converter.createVCard( addressee );
03691 }
03692 
03693 
03694 QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef,
03695         Calendar *mCalendar, InvitationFormatterHelper *helper )
03696 {
03697   QString vPart = IncidenceFormatter::msTNEFToVPart( tnef );
03698   QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper );
03699   if( !iCal.isEmpty() )
03700     return iCal;
03701   return vPart;
03702 }
03703 
03704 
03705 
03706 
03707 /*******************************************************************
03708  *  Helper functions for the Incidence tooltips
03709  *******************************************************************/
03710 
03711 class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
03712 {
03713   public:
03714     ToolTipVisitor()
03715       : mCalendar( 0 ), mRichText( true ), mResult( "" ) {}
03716 
03717     bool act( Calendar *calendar, IncidenceBase *incidence,
03718               const QDate &date=QDate(), bool richText=true )
03719     {
03720       mCalendar = calendar;
03721       mDate = date;
03722       mRichText = richText;
03723       mResult = "";
03724       return incidence ? incidence->accept( *this ) : false;
03725     }
03726     QString result() const { return mResult; }
03727 
03728   protected:
03729     bool visit( Event *event );
03730     bool visit( Todo *todo );
03731     bool visit( Journal *journal );
03732     bool visit( FreeBusy *fb );
03733 
03734     QString dateRangeText( Event *event, const QDate &date );
03735     QString dateRangeText( Todo *todo, const QDate &date );
03736     QString dateRangeText( Journal *journal );
03737     QString dateRangeText( FreeBusy *fb );
03738 
03739     QString generateToolTip( Incidence* incidence, QString dtRangeText );
03740 
03741   protected:
03742     Calendar *mCalendar;
03743     QDate mDate;
03744     bool mRichText;
03745     QString mResult;
03746 };
03747 
03748 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const QDate &date )
03749 {
03750   QString ret;
03751   QString tmp;
03752 
03753   QDateTime startDt = event->dtStart();
03754   QDateTime endDt = event->dtEnd();
03755   if ( event->doesRecur() ) {
03756     if ( date.isValid() ) {
03757       QDateTime dt( date, QTime( 0, 0, 0 ) );
03758       int diffDays = startDt.daysTo( dt );
03759       dt = dt.addSecs( -1 );
03760       startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() );
03761       if ( event->hasEndDate() ) {
03762         endDt = endDt.addDays( diffDays );
03763         if ( startDt > endDt ) {
03764           startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() );
03765           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
03766         }
03767       }
03768     }
03769   }
03770   if ( event->isMultiDay() ) {
03771 
03772     tmp = "<br>" + i18n("Event start", "<i>From:</i>&nbsp;%1");
03773     if (event->doesFloat())
03774       ret += tmp.arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03775     else
03776       ret += tmp.arg( IncidenceFormatter::dateToString( startDt ).replace(" ", "&nbsp;") );
03777 
03778     tmp = "<br>" + i18n("Event end","<i>To:</i>&nbsp;%1");
03779     if (event->doesFloat())
03780       ret += tmp.arg( IncidenceFormatter::dateToString( endDt, false ).replace(" ", "&nbsp;") );
03781     else
03782       ret += tmp.arg( IncidenceFormatter::dateToString( endDt ).replace(" ", "&nbsp;") );
03783 
03784   } else {
03785 
03786     ret += "<br>"+i18n("<i>Date:</i>&nbsp;%1").
03787            arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03788     if ( !event->doesFloat() ) {
03789       const QString dtStartTime =
03790         IncidenceFormatter::timeToString( startDt, true ).replace( " ", "&nbsp;" );
03791       const QString dtEndTime =
03792         IncidenceFormatter::timeToString( endDt, true ).replace( " ", "&nbsp;" );
03793       if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00'
03794         tmp = "<br>" + i18n("time for event, &nbsp; to prevent ugly line breaks",
03795         "<i>Time:</i>&nbsp;%1").
03796         arg( dtStartTime );
03797       } else {
03798         tmp = "<br>" + i18n("time range for event, &nbsp; to prevent ugly line breaks",
03799         "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2").
03800         arg( dtStartTime, dtEndTime );
03801       }
03802       ret += tmp;
03803     }
03804 
03805   }
03806   return ret;
03807 }
03808 
03809 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const QDate &date )
03810 {
03811   QString ret;
03812   bool floats( todo->doesFloat() );
03813 
03814   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03815     QDateTime startDt = todo->dtStart();
03816     if ( todo->doesRecur() ) {
03817       if ( date.isValid() ) {
03818         startDt.setDate( date );
03819       }
03820     }
03821     ret += "<br>" +
03822            i18n("<i>Start:</i>&nbsp;%1").
03823            arg( IncidenceFormatter::dateTimeToString( startDt, floats, false ).
03824                 replace( " ", "&nbsp;" ) );
03825   }
03826 
03827   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03828     QDateTime dueDt = todo->dtDue();
03829     if ( todo->doesRecur() ) {
03830       if ( date.isValid() ) {
03831         QDateTime dt( date, QTime( 0, 0, 0 ) );
03832         dt = dt.addSecs( -1 );
03833         dueDt.setDate( todo->recurrence()->getNextDateTime( dt ).date() );
03834       }
03835     }
03836     ret += "<br>" +
03837            i18n("<i>Due:</i>&nbsp;%1").
03838            arg( IncidenceFormatter::dateTimeToString( dueDt, floats, false ).
03839                 replace( " ", "&nbsp;" ) );
03840   }
03841 
03842   // Print priority and completed info here, for lack of a better place
03843 
03844   if ( todo->priority() > 0 ) {
03845     ret += "<br>";
03846     ret += "<i>" + i18n( "Priority:" ) + "</i>" + "&nbsp;";
03847     ret += QString::number( todo->priority() );
03848   }
03849 
03850   ret += "<br>";
03851   if ( todo->isCompleted() ) {
03852     ret += "<i>" + i18n( "Completed:" ) + "</i>" + "&nbsp;";
03853     ret += todo->completedStr().replace( " ", "&nbsp;" );
03854   } else {
03855     ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + "&nbsp;";
03856     ret += i18n( "%1%" ).arg( todo->percentComplete() );
03857   }
03858 
03859   return ret;
03860 }
03861 
03862 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal )
03863 {
03864   QString ret;
03865   if (journal->dtStart().isValid() ) {
03866     ret += "<br>" +
03867            i18n("<i>Date:</i>&nbsp;%1").
03868            arg( IncidenceFormatter::dateToString( journal->dtStart(), false ) );
03869   }
03870   return ret;
03871 }
03872 
03873 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
03874 {
03875   QString tmp( "<br>" + i18n("<i>Period start:</i>&nbsp;%1") );
03876   QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) );
03877   tmp = "<br>" + i18n("<i>Period start:</i>&nbsp;%1");
03878   ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) );
03879   return ret;
03880 }
03881 
03882 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
03883 {
03884   mResult = generateToolTip( event, dateRangeText( event, mDate ) );
03885   return !mResult.isEmpty();
03886 }
03887 
03888 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
03889 {
03890   mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
03891   return !mResult.isEmpty();
03892 }
03893 
03894 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
03895 {
03896   mResult = generateToolTip( journal, dateRangeText( journal ) );
03897   return !mResult.isEmpty();
03898 }
03899 
03900 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
03901 {
03902   mResult = "<qt><b>" + i18n("Free/Busy information for %1")
03903         .arg(fb->organizer().fullName()) + "</b>";
03904   mResult += dateRangeText( fb );
03905   mResult += "</qt>";
03906   return !mResult.isEmpty();
03907 }
03908 
03909 static QString tooltipPerson( const QString &email, const QString &name, Attendee::PartStat status )
03910 {
03911   // Search for a new print name, if needed.
03912   const QString printName = searchName( email, name );
03913 
03914   // Get the icon corresponding to the attendee participation status.
03915   const QString iconPath = rsvpStatusIconPath( status );
03916 
03917   // Make the return string.
03918   QString personString;
03919   if ( !iconPath.isEmpty() ) {
03920     personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
03921   }
03922   if ( status != Attendee::None ) {
03923     personString += i18n( "attendee name (attendee status)", "%1 (%2)" ).
03924                     arg( printName.isEmpty() ? email : printName ).
03925                     arg( Attendee::statusName( status ) );
03926   } else {
03927     personString += i18n( "%1" ).arg( printName.isEmpty() ? email : printName );
03928   }
03929   return personString;
03930 }
03931 
03932 static QString tooltipFormatOrganizer( const QString &email, const QString &name )
03933 {
03934   // Search for a new print name, if needed
03935   const QString printName = searchName( email, name );
03936 
03937   // Get the icon for organizer
03938   const QString iconPath = KGlobal::iconLoader()->iconPath( "organizer", KIcon::Small );
03939 
03940   // Make the return string.
03941   QString personString;
03942   personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
03943   personString += ( printName.isEmpty() ? email : printName );
03944   return personString;
03945 }
03946 
03947 static QString tooltipFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role,
03948                                               bool showStatus )
03949 {
03950   const int maxNumAtts = 8; // maximum number of people to print per attendee role
03951   const QString etc = i18n( "elipsis", "..." );
03952 
03953   int i = 0;
03954   QString tmpStr;
03955   Attendee::List::ConstIterator it;
03956   Attendee::List attendees = incidence->attendees();
03957 
03958   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
03959     Attendee *a = *it;
03960     if ( a->role() != role ) {
03961       // skip not this role
03962       continue;
03963     }
03964     if ( attendeeIsOrganizer( incidence, a ) ) {
03965       // skip attendee that is also the organizer
03966       continue;
03967     }
03968     if ( i == maxNumAtts ) {
03969       tmpStr += "&nbsp;&nbsp;" + etc;
03970       break;
03971     }
03972     tmpStr += "&nbsp;&nbsp;" + tooltipPerson( a->email(), a->name(),
03973                                               showStatus ? a->status() : Attendee::None );
03974     if ( !a->delegator().isEmpty() ) {
03975       tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
03976     }
03977     if ( !a->delegate().isEmpty() ) {
03978       tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
03979     }
03980     tmpStr += "<br>";
03981     i++;
03982   }
03983 
03984   if ( tmpStr.endsWith( "<br>" ) ) {
03985     tmpStr.truncate( tmpStr.length() - 4 );
03986   }
03987   return tmpStr;
03988 }
03989 
03990 static QString tooltipFormatAttendees( Calendar *calendar, Incidence *incidence )
03991 {
03992   QString tmpStr, str;
03993 
03994   // Add organizer link
03995   int attendeeCount = incidence->attendees().count();
03996   if ( attendeeCount > 1 ||
03997        ( attendeeCount == 1 &&
03998          !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) {
03999     tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "<br>";
04000     tmpStr += "&nbsp;&nbsp;" + tooltipFormatOrganizer( incidence->organizer().email(),
04001                                                        incidence->organizer().name() );
04002   }
04003 
04004   // Show the attendee status if the incidence's organizer owns the resource calendar,
04005   // which means they are running the show and have all the up-to-date response info.
04006   bool showStatus = CalHelper::incOrganizerOwnsCalendar( calendar, incidence );
04007 
04008   // Add "chair"
04009   str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus );
04010   if ( !str.isEmpty() ) {
04011     tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "<br>";
04012     tmpStr += str;
04013   }
04014 
04015   // Add required participants
04016   str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus );
04017   if ( !str.isEmpty() ) {
04018     tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "<br>";
04019     tmpStr += str;
04020   }
04021 
04022   // Add optional participants
04023   str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus );
04024   if ( !str.isEmpty() ) {
04025     tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "<br>";
04026     tmpStr += str;
04027   }
04028 
04029   // Add observers
04030   str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus );
04031   if ( !str.isEmpty() ) {
04032     tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "<br>";
04033     tmpStr += str;
04034   }
04035 
04036   return tmpStr;
04037 }
04038 
04039 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence, QString dtRangeText )
04040 {
04041   const QString etc = i18n( "elipsis", "..." );
04042   const uint maxDescLen = 120; // maximum description chars to print (before elipsis)
04043 
04044   if ( !incidence ) {
04045     return QString::null;
04046   }
04047 
04048   QString tmp = "<qt>";
04049 
04050   // header
04051 
04052 #ifdef KORG_DEBUG_SCHEDULING_IDS
04053   tmp += "<b>Uid: " + incidence->uid() + "<br>schedulingID: " + incidence->schedulingID() + "</b><br>";
04054 #endif
04055 
04056   tmp += "<b>" + incidence->summary().replace( "\n", "<br>" ) + "</b>";
04057   //NOTE: using <hr> seems to confuse Qt3 tooltips in some cases so use "-----"
04058   tmp += "<br>----------<br>";
04059 
04060   if ( mCalendar ) {
04061     QString calStr = IncidenceFormatter::resourceString( mCalendar, incidence );
04062     if ( !calStr.isEmpty() ) {
04063       tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + "&nbsp;";
04064       tmp += calStr;
04065     }
04066   }
04067 
04068   tmp += dtRangeText;
04069 
04070   if ( !incidence->location().isEmpty() ) {
04071     tmp += "<br>";
04072     tmp += "<i>" + i18n( "Location:" ) + "</i>" + "&nbsp;";
04073     tmp += incidence->location().replace( "\n", "<br>" );
04074   }
04075 
04076   QString durStr = IncidenceFormatter::durationString( incidence );
04077   if ( !durStr.isEmpty() ) {
04078     tmp += "<br>";
04079     tmp += "<i>" + i18n( "Duration:" ) + "</i>" + "&nbsp;";
04080     tmp += durStr;
04081   }
04082 
04083   if ( incidence->doesRecur() ) {
04084     tmp += "<br>";
04085     tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + "&nbsp;";
04086     tmp += IncidenceFormatter::recurrenceString( incidence );
04087   }
04088 
04089   if ( !incidence->description().isEmpty() ) {
04090     QString desc( incidence->description() );
04091     if ( desc.length() > maxDescLen ) {
04092       desc = desc.left( maxDescLen ) + etc;
04093     }
04094     tmp += "<br>----------<br>";
04095     tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
04096     tmp += desc.replace( "\n", "<br>" );
04097     tmp += "<br>----------";
04098   }
04099 
04100   int reminderCount = incidence->alarms().count();
04101   if ( reminderCount > 0 && incidence->isAlarmEnabled() ) {
04102     tmp += "<br>";
04103     tmp += "<i>" + i18n( "Reminder:", "%n Reminders:", reminderCount ) + "</i>" + "&nbsp;";
04104     tmp += IncidenceFormatter::reminderStringList( incidence ).join( ", " );
04105   }
04106 
04107   tmp += "<br>";
04108   tmp += tooltipFormatAttendees( mCalendar, incidence );
04109 
04110   int categoryCount = incidence->categories().count();
04111   if ( categoryCount > 0 ) {
04112     tmp += "<br>";
04113     tmp += "<i>" + i18n( "Category:", "%n Categories:", categoryCount ) + "</i>" + "&nbsp;";
04114     tmp += incidence->categories().join( ", " );
04115   }
04116 
04117   tmp += "</qt>";
04118   return tmp;
04119 }
04120 
04121 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
04122 {
04123   return toolTipStr( 0, incidence, QDate(), richText );
04124 }
04125 
04126 QString IncidenceFormatter::toolTipStr( Calendar *calendar,
04127                                         IncidenceBase *incidence,
04128                                         const QDate &date,
04129                                         bool richText )
04130 {
04131   ToolTipVisitor v;
04132   if ( v.act( calendar, incidence, date, richText ) ) {
04133     return v.result();
04134   } else {
04135     return QString::null;
04136   }
04137 }
04138 
04139 /*******************************************************************
04140  *  Helper functions for the Incidence tooltips
04141  *******************************************************************/
04142 
04143 class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
04144 {
04145   public:
04146     MailBodyVisitor() : mResult( "" ) {}
04147 
04148     bool act( IncidenceBase *incidence )
04149     {
04150       mResult = "";
04151       return incidence ? incidence->accept( *this ) : false;
04152     }
04153     QString result() const { return mResult; }
04154 
04155   protected:
04156     bool visit( Event *event );
04157     bool visit( Todo *todo );
04158     bool visit( Journal *journal );
04159     bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); }
04160   protected:
04161     QString mResult;
04162 };
04163 
04164 
04165 static QString mailBodyIncidence( Incidence *incidence )
04166 {
04167   QString body;
04168   if ( !incidence->summary().isEmpty() ) {
04169     body += i18n("Summary: %1\n").arg( incidence->summary() );
04170   }
04171   if ( !incidence->organizer().isEmpty() ) {
04172     body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() );
04173   }
04174   if ( !incidence->location().isEmpty() ) {
04175     body += i18n("Location: %1\n").arg( incidence->location() );
04176   }
04177   return body;
04178 }
04179 
04180 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
04181 {
04182   QString recurrence[]= {i18n("no recurrence", "None"),
04183     i18n("Minutely"), i18n("Hourly"), i18n("Daily"),
04184     i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"),
04185     i18n("Yearly"), i18n("Yearly"), i18n("Yearly")};
04186 
04187   mResult = mailBodyIncidence( event );
04188   mResult += i18n("Start Date: %1\n").
04189              arg( IncidenceFormatter::dateToString( event->dtStart(), true ) );
04190   if ( !event->doesFloat() ) {
04191     mResult += i18n("Start Time: %1\n").
04192                arg( IncidenceFormatter::timeToString( event->dtStart(), true ) );
04193   }
04194   if ( event->dtStart() != event->dtEnd() ) {
04195     mResult += i18n("End Date: %1\n").
04196                arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) );
04197   }
04198   if ( !event->doesFloat() ) {
04199     mResult += i18n("End Time: %1\n").
04200                arg( IncidenceFormatter::timeToString( event->dtEnd(), true ) );
04201   }
04202   if ( event->doesRecur() ) {
04203     Recurrence *recur = event->recurrence();
04204     // TODO: Merge these two to one of the form "Recurs every 3 days"
04205     mResult += i18n("Recurs: %1\n")
04206              .arg( recurrence[ recur->recurrenceType() ] );
04207     mResult += i18n("Frequency: %1\n")
04208              .arg( event->recurrence()->frequency() );
04209 
04210     if ( recur->duration() > 0 ) {
04211       mResult += i18n ("Repeats once", "Repeats %n times", recur->duration());
04212       mResult += '\n';
04213     } else {
04214       if ( recur->duration() != -1 ) {
04215 // TODO_Recurrence: What to do with floating
04216         QString endstr;
04217         if ( event->doesFloat() ) {
04218           endstr = KGlobal::locale()->formatDate( recur->endDate() );
04219         } else {
04220           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() );
04221         }
04222         mResult += i18n("Repeat until: %1\n").arg( endstr );
04223       } else {
04224         mResult += i18n("Repeats forever\n");
04225       }
04226     }
04227   }
04228   QString details = event->description();
04229   if ( !details.isEmpty() ) {
04230     mResult += i18n("Details:\n%1\n").arg( details );
04231   }
04232   return !mResult.isEmpty();
04233 }
04234 
04235 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
04236 {
04237   mResult = mailBodyIncidence( todo );
04238 
04239   if ( todo->hasStartDate() ) {
04240     mResult += i18n("Start Date: %1\n").
04241                arg( IncidenceFormatter::dateToString( todo->dtStart( false ), true ) );
04242     if ( !todo->doesFloat() ) {
04243       mResult += i18n("Start Time: %1\n").
04244                  arg( IncidenceFormatter::timeToString( todo->dtStart( false ),true ) );
04245     }
04246   }
04247   if ( todo->hasDueDate() ) {
04248     mResult += i18n("Due Date: %1\n").
04249                arg( IncidenceFormatter::dateToString( todo->dtDue(), true ) );
04250     if ( !todo->doesFloat() ) {
04251       mResult += i18n("Due Time: %1\n").
04252                  arg( IncidenceFormatter::timeToString( todo->dtDue(), true ) );
04253     }
04254   }
04255   QString details = todo->description();
04256   if ( !details.isEmpty() ) {
04257     mResult += i18n("Details:\n%1\n").arg( details );
04258   }
04259   return !mResult.isEmpty();
04260 }
04261 
04262 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
04263 {
04264   mResult = mailBodyIncidence( journal );
04265   mResult += i18n("Date: %1\n").
04266              arg( IncidenceFormatter::dateToString( journal->dtStart(), true ) );
04267   if ( !journal->doesFloat() ) {
04268     mResult += i18n("Time: %1\n").
04269                arg( IncidenceFormatter::timeToString( journal->dtStart(), true ) );
04270   }
04271   if ( !journal->description().isEmpty() )
04272     mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() );
04273   return !mResult.isEmpty();
04274 }
04275 
04276 
04277 
04278 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
04279 {
04280   if ( !incidence )
04281     return QString::null;
04282 
04283   MailBodyVisitor v;
04284   if ( v.act( incidence ) ) {
04285     return v.result();
04286   }
04287   return QString::null;
04288 }
04289 
04290 static QString recurEnd( Incidence *incidence )
04291 {
04292   QString endstr;
04293   if ( incidence->doesFloat() ) {
04294     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
04295   } else {
04296     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
04297   }
04298   return endstr;
04299 }
04300 
04301 /************************************
04302  *  More static formatting functions
04303  ************************************/
04304 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
04305 {
04306   if ( !incidence->doesRecur() ) {
04307     return i18n( "No recurrence" );
04308   }
04309   QStringList dayList;
04310   dayList.append( i18n( "31st Last" ) );
04311   dayList.append( i18n( "30th Last" ) );
04312   dayList.append( i18n( "29th Last" ) );
04313   dayList.append( i18n( "28th Last" ) );
04314   dayList.append( i18n( "27th Last" ) );
04315   dayList.append( i18n( "26th Last" ) );
04316   dayList.append( i18n( "25th Last" ) );
04317   dayList.append( i18n( "24th Last" ) );
04318   dayList.append( i18n( "23rd Last" ) );
04319   dayList.append( i18n( "22nd Last" ) );
04320   dayList.append( i18n( "21st Last" ) );
04321   dayList.append( i18n( "20th Last" ) );
04322   dayList.append( i18n( "19th Last" ) );
04323   dayList.append( i18n( "18th Last" ) );
04324   dayList.append( i18n( "17th Last" ) );
04325   dayList.append( i18n( "16th Last" ) );
04326   dayList.append( i18n( "15th Last" ) );
04327   dayList.append( i18n( "14th Last" ) );
04328   dayList.append( i18n( "13th Last" ) );
04329   dayList.append( i18n( "12th Last" ) );
04330   dayList.append( i18n( "11th Last" ) );
04331   dayList.append( i18n( "10th Last" ) );
04332   dayList.append( i18n( "9th Last" ) );
04333   dayList.append( i18n( "8th Last" ) );
04334   dayList.append( i18n( "7th Last" ) );
04335   dayList.append( i18n( "6th Last" ) );
04336   dayList.append( i18n( "5th Last" ) );
04337   dayList.append( i18n( "4th Last" ) );
04338   dayList.append( i18n( "3rd Last" ) );
04339   dayList.append( i18n( "2nd Last" ) );
04340   dayList.append( i18n( "last day of the month", "Last" ) );
04341   dayList.append( i18n( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
04342   dayList.append( i18n( "1st" ) );
04343   dayList.append( i18n( "2nd" ) );
04344   dayList.append( i18n( "3rd" ) );
04345   dayList.append( i18n( "4th" ) );
04346   dayList.append( i18n( "5th" ) );
04347   dayList.append( i18n( "6th" ) );
04348   dayList.append( i18n( "7th" ) );
04349   dayList.append( i18n( "8th" ) );
04350   dayList.append( i18n( "9th" ) );
04351   dayList.append( i18n( "10th" ) );
04352   dayList.append( i18n( "11th" ) );
04353   dayList.append( i18n( "12th" ) );
04354   dayList.append( i18n( "13th" ) );
04355   dayList.append( i18n( "14th" ) );
04356   dayList.append( i18n( "15th" ) );
04357   dayList.append( i18n( "16th" ) );
04358   dayList.append( i18n( "17th" ) );
04359   dayList.append( i18n( "18th" ) );
04360   dayList.append( i18n( "19th" ) );
04361   dayList.append( i18n( "20th" ) );
04362   dayList.append( i18n( "21st" ) );
04363   dayList.append( i18n( "22nd" ) );
04364   dayList.append( i18n( "23rd" ) );
04365   dayList.append( i18n( "24th" ) );
04366   dayList.append( i18n( "25th" ) );
04367   dayList.append( i18n( "26th" ) );
04368   dayList.append( i18n( "27th" ) );
04369   dayList.append( i18n( "28th" ) );
04370   dayList.append( i18n( "29th" ) );
04371   dayList.append( i18n( "30th" ) );
04372   dayList.append( i18n( "31st" ) );
04373 
04374   int weekStart = KGlobal::locale()->weekStartDay();
04375   QString dayNames;
04376 
04377   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
04378 
04379   Recurrence *recur = incidence->recurrence();
04380 
04381   QString recurStr;
04382   switch ( recur->recurrenceType() ) {
04383   case Recurrence::rNone:
04384     return i18n( "No recurrence" );
04385 
04386   case Recurrence::rMinutely:
04387     recurStr = i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() );
04388     if ( recur->duration() != -1 ) {
04389       recurStr = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
04390       if ( recur->duration() >  0 ) {
04391         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04392       }
04393     }
04394     break;
04395 
04396   case Recurrence::rHourly:
04397     recurStr = i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() );
04398     if ( recur->duration() != -1 ) {
04399       recurStr = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
04400       if ( recur->duration() >  0 ) {
04401         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04402       }
04403     }
04404     break;
04405 
04406   case Recurrence::rDaily:
04407     recurStr = i18n( "Recurs daily", "Recurs every %n days", recur->frequency() );
04408     if ( recur->duration() != -1 ) {
04409       recurStr = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
04410       if ( recur->duration() >  0 ) {
04411         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04412       }
04413     }
04414     break;
04415 
04416   case Recurrence::rWeekly:
04417   {
04418     recurStr = i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() );
04419 
04420     bool addSpace = false;
04421     for ( int i = 0; i < 7; ++i ) {
04422       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
04423         if ( addSpace ) {
04424           dayNames.append( i18n( "separator for list of days", ", " ) );
04425         }
04426         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, true ) );
04427         addSpace = true;
04428       }
04429     }
04430     if ( dayNames.isEmpty() ) {
04431       dayNames = i18n( "Recurs weekly on no days", "no days" );
04432     }
04433     if ( recur->duration() != -1 ) {
04434       recurStr = i18n( "%1 on %2 until %3" ).
04435                  arg( recurStr ).arg( dayNames ).arg( recurEnd( incidence ) );
04436       if ( recur->duration() >  0 ) {
04437         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04438       }
04439     } else {
04440       recurStr = i18n( "%1 on %2" ).arg( recurStr ).arg( dayNames );
04441     }
04442     break;
04443   }
04444 
04445   case Recurrence::rMonthlyPos:
04446   {
04447     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
04448 
04449     if ( !recur->monthPositions().isEmpty() ) {
04450       KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
04451       if ( recur->duration() != -1 ) {
04452         recurStr = i18n( "%1 on the %2 %3 until %4" ).
04453                    arg( recurStr ).
04454                    arg( dayList[rule.pos() + 31] ).
04455                    arg( calSys->weekDayName( rule.day(), false ) ).
04456                    arg( recurEnd( incidence ) );
04457         if ( recur->duration() >  0 ) {
04458           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04459         }
04460       } else {
04461         recurStr = i18n( "%1 on the %2 %3" ).
04462                    arg( recurStr ).
04463                    arg( dayList[rule.pos() + 31] ).
04464                    arg( calSys->weekDayName( rule.day(), false ) );
04465       }
04466     }
04467     break;
04468   }
04469 
04470   case Recurrence::rMonthlyDay:
04471   {
04472     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
04473 
04474     if ( !recur->monthDays().isEmpty() ) {
04475       int days = recur->monthDays()[0];
04476       if ( recur->duration() != -1 ) {
04477         recurStr = i18n( "%1 on the %2 day until %3" ).
04478                    arg( recurStr ).
04479                    arg( dayList[days + 31] ).
04480                    arg( recurEnd( incidence ) );
04481         if ( recur->duration() >  0 ) {
04482           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04483         }
04484       } else {
04485         recurStr = i18n( "%1 on the %2 day" ).arg( recurStr ).arg( dayList[days + 31] );
04486       }
04487     }
04488     break;
04489   }
04490   case Recurrence::rYearlyMonth:
04491   {
04492     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
04493 
04494     if ( recur->duration() != -1 ) {
04495       if ( !recur->yearDates().isEmpty() ) {
04496         recurStr = i18n( "%1 on %2 %3 until %4" ).
04497                    arg( recurStr ).
04498                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
04499                    arg( dayList[ recur->yearDates()[0] + 31 ] ).
04500                    arg( recurEnd( incidence ) );
04501         if ( recur->duration() >  0 ) {
04502           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04503         }
04504       }
04505     } else {
04506       if ( !recur->yearDates().isEmpty() ) {
04507         recurStr = i18n( "%1 on %2 %3" ).
04508                    arg( recurStr ).
04509                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
04510                    arg( dayList[ recur->yearDates()[0] + 31 ] );
04511       } else {
04512         if ( !recur->yearMonths().isEmpty() ) {
04513           recurStr = i18n( "Recurs yearly on %1 %2" ).
04514                      arg( calSys->monthName( recur->yearMonths()[0],
04515                                              recur->startDate().year() ) ).
04516                      arg( dayList[ recur->startDate().day() + 31 ] );
04517         } else {
04518           recurStr = i18n( "Recurs yearly on %1 %2" ).
04519                      arg( calSys->monthName( recur->startDate().month(),
04520                                              recur->startDate().year() ) ).
04521                      arg( dayList[ recur->startDate().day() + 31 ] );
04522         }
04523       }
04524     }
04525     break;
04526   }
04527   case Recurrence::rYearlyDay:
04528   {
04529     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
04530     if ( !recur->yearDays().isEmpty() ) {
04531       if ( recur->duration() != -1 ) {
04532         recurStr = i18n( "%1 on day %2 until %3" ).
04533                    arg( recurStr ).
04534                    arg( recur->yearDays()[0] ).
04535                    arg( recurEnd( incidence ) );
04536         if ( recur->duration() >  0 ) {
04537           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04538         }
04539       } else {
04540         recurStr = i18n( "%1 on day %2" ).arg( recurStr ).arg( recur->yearDays()[0] );
04541       }
04542     }
04543     break;
04544   }
04545   case Recurrence::rYearlyPos:
04546   {
04547     recurStr = i18n( "Every year", "Every %n years", recur->frequency() );
04548     if ( !recur->yearPositions().isEmpty() && !recur->yearMonths().isEmpty() ) {
04549       KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
04550       if ( recur->duration() != -1 ) {
04551         recurStr = i18n( "%1 on the %2 %3 of %4 until %5" ).
04552                    arg( recurStr ).
04553                    arg( dayList[rule.pos() + 31] ).
04554                    arg( calSys->weekDayName( rule.day(), false ) ).
04555                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
04556                    arg( recurEnd( incidence ) );
04557         if ( recur->duration() >  0 ) {
04558           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04559         }
04560       } else {
04561         recurStr = i18n( "%1 on the %2 %3 of %4" ).
04562                    arg( recurStr ).
04563                    arg( dayList[rule.pos() + 31] ).
04564                    arg( calSys->weekDayName( rule.day(), false ) ).
04565                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
04566       }
04567     }
04568    break;
04569   }
04570   }
04571 
04572   if ( recurStr.isEmpty() ) {
04573     recurStr = i18n( "Incidence recurs" );
04574   }
04575   // Now, append the EXDATEs
04576   DateTimeList l = recur->exDateTimes();
04577   DateTimeList::ConstIterator il;
04578   QStringList exStr;
04579   for ( il = l.constBegin(); il != l.constEnd(); ++il ) {
04580     switch ( recur->recurrenceType() ) {
04581     case Recurrence::rMinutely:
04582       exStr << i18n( "minute %1" ).arg( (*il).time().minute() );
04583       break;
04584     case Recurrence::rHourly:
04585       exStr << KGlobal::locale()->formatTime( (*il).time() );
04586       break;
04587     case Recurrence::rDaily:
04588       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04589       break;
04590     case Recurrence::rWeekly:
04591       exStr << calSys->weekDayName( (*il).date(), true );
04592       break;
04593     case Recurrence::rMonthlyPos:
04594       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04595       break;
04596     case Recurrence::rMonthlyDay:
04597       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04598       break;
04599     case Recurrence::rYearlyMonth:
04600       exStr << calSys->monthName( (*il).date(), false );
04601       break;
04602     case Recurrence::rYearlyDay:
04603       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04604       break;
04605     case Recurrence::rYearlyPos:
04606       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04607       break;
04608     }
04609   }
04610 
04611   DateList d = recur->exDates();
04612   DateList::ConstIterator dl;
04613   for ( dl = d.constBegin(); dl != d.constEnd(); ++dl ) {
04614     switch ( recur->recurrenceType() ) {
04615     case Recurrence::rDaily:
04616       exStr << KGlobal::locale()->formatDate( (*dl), true );
04617       break;
04618     case Recurrence::rWeekly:
04619       // exStr << calSys->weekDayName( (*dl), true );
04620       // kolab/issue4735, should be ( excluding 3 days ), instead of excluding( Fr,Fr,Fr )
04621       if ( exStr.isEmpty() )
04622         exStr << i18n( "1 day", "%n days", recur->exDates().count() );
04623       break;
04624     case Recurrence::rMonthlyPos:
04625       exStr << KGlobal::locale()->formatDate( (*dl), true );
04626       break;
04627     case Recurrence::rMonthlyDay:
04628       exStr << KGlobal::locale()->formatDate( (*dl), true );
04629       break;
04630     case Recurrence::rYearlyMonth:
04631       exStr << calSys->monthName( (*dl), false );
04632       break;
04633     case Recurrence::rYearlyDay:
04634       exStr << KGlobal::locale()->formatDate( (*dl), true );
04635       break;
04636     case Recurrence::rYearlyPos:
04637       exStr << KGlobal::locale()->formatDate( (*dl), true );
04638       break;
04639     }
04640   }
04641 
04642   if ( !exStr.isEmpty() ) {
04643     recurStr = i18n( "%1 (excluding %2)" ).arg( recurStr, exStr.join( "," ) );
04644   }
04645 
04646   return recurStr;
04647 }
04648 
04649 QString IncidenceFormatter::timeToString( const QDateTime &date, bool shortfmt )
04650 {
04651   return KGlobal::locale()->formatTime( date.time(), !shortfmt );
04652 }
04653 
04654 QString IncidenceFormatter::dateToString( const QDateTime &date, bool shortfmt )
04655 {
04656   return
04657     KGlobal::locale()->formatDate( date.date(), shortfmt );
04658 }
04659 
04660 QString IncidenceFormatter::dateTimeToString( const QDateTime &date,
04661                                               bool allDay, bool shortfmt )
04662 {
04663   if ( allDay ) {
04664     return dateToString( date, shortfmt );
04665   }
04666 
04667   return  KGlobal::locale()->formatDateTime( date, shortfmt );
04668 }
04669 
04670 QString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence )
04671 {
04672   if ( !calendar || !incidence ) {
04673     return QString::null;
04674   }
04675 
04676   CalendarResources *calendarResource = dynamic_cast<CalendarResources*>( calendar );
04677   if ( !calendarResource ) {
04678     return QString::null;
04679   }
04680 
04681   ResourceCalendar *resourceCalendar = calendarResource->resource( incidence );
04682   if ( resourceCalendar ) {
04683     if ( !resourceCalendar->subresources().isEmpty() ) {
04684       QString subRes = resourceCalendar->subresourceIdentifier( incidence );
04685       if ( subRes.isEmpty() ) {
04686         return resourceCalendar->resourceName();
04687       } else {
04688         return resourceCalendar->labelForSubresource( subRes );
04689       }
04690     }
04691     return resourceCalendar->resourceName();
04692   }
04693 
04694   return QString::null;
04695 }
04696 
04697 static QString secs2Duration( int secs )
04698 {
04699   QString tmp;
04700   int days = secs / 86400;
04701   if ( days > 0 ) {
04702     tmp += i18n( "1 day", "%n days", days );
04703     tmp += ' ';
04704     secs -= ( days * 86400 );
04705   }
04706   int hours = secs / 3600;
04707   if ( hours > 0 ) {
04708     tmp += i18n( "1 hour", "%n hours", hours );
04709     tmp += ' ';
04710     secs -= ( hours * 3600 );
04711   }
04712   int mins = secs / 60;
04713   if ( mins > 0 ) {
04714     tmp += i18n( "1 minute", "%n minutes",  mins );
04715   }
04716   return tmp;
04717 }
04718 
04719 QString IncidenceFormatter::durationString( Incidence *incidence )
04720 {
04721   QString tmp;
04722   if ( incidence->type() == "Event" ) {
04723     Event *event = static_cast<Event *>( incidence );
04724     if ( event->hasEndDate() ) {
04725       if ( !event->doesFloat() ) {
04726         tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
04727       } else {
04728         tmp = i18n( "1 day", "%n days",
04729                     event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
04730       }
04731     } else {
04732       tmp = i18n( "forever" );
04733     }
04734   } else if ( incidence->type() == "Todo" ) {
04735     Todo *todo = static_cast<Todo *>( incidence );
04736     if ( todo->hasDueDate() ) {
04737       if ( todo->hasStartDate() ) {
04738         if ( !todo->doesFloat() ) {
04739           tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
04740         } else {
04741           tmp = i18n( "1 day", "%n days",
04742                       todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
04743         }
04744       }
04745     }
04746   }
04747   return tmp;
04748 }
04749 
04750 QStringList IncidenceFormatter::reminderStringList( Incidence *incidence, bool shortfmt )
04751 {
04752   //TODO: implement shortfmt=false
04753   Q_UNUSED( shortfmt );
04754 
04755   QStringList reminderStringList;
04756 
04757   if ( incidence ) {
04758     Alarm::List alarms = incidence->alarms();
04759     Alarm::List::ConstIterator it;
04760     for ( it = alarms.begin(); it != alarms.end(); ++it ) {
04761       Alarm *alarm = *it;
04762       int offset = 0;
04763       QString remStr, atStr, offsetStr;
04764       if ( alarm->hasTime() ) {
04765         offset = 0;
04766         if ( alarm->time().isValid() ) {
04767           atStr = KGlobal::locale()->formatDateTime( alarm->time() );
04768         }
04769       } else if ( alarm->hasStartOffset() ) {
04770         offset = alarm->startOffset().asSeconds();
04771         if ( offset < 0 ) {
04772           offset = -offset;
04773           offsetStr = i18n( "N days/hours/minutes before the start datetime",
04774                             "%1 before the start" );
04775         } else if ( offset > 0 ) {
04776           offsetStr = i18n( "N days/hours/minutes after the start datetime",
04777                             "%1 after the start" );
04778         } else { //offset is 0
04779           if ( incidence->dtStart().isValid() ) {
04780             atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() );
04781           }
04782         }
04783       } else if ( alarm->hasEndOffset() ) {
04784         offset = alarm->endOffset().asSeconds();
04785         if ( offset < 0 ) {
04786           offset = -offset;
04787           if ( incidence->type() == "Todo" ) {
04788             offsetStr = i18n( "N days/hours/minutes before the due datetime",
04789                               "%1 before the to-do is due" );
04790           } else {
04791             offsetStr = i18n( "N days/hours/minutes before the end datetime",
04792                               "%1 before the end" );
04793           }
04794         } else if ( offset > 0 ) {
04795           if ( incidence->type() == "Todo" ) {
04796             offsetStr = i18n( "N days/hours/minutes after the due datetime",
04797                               "%1 after the to-do is due" );
04798           } else {
04799             offsetStr = i18n( "N days/hours/minutes after the end datetime",
04800                               "%1 after the end" );
04801           }
04802         } else { //offset is 0
04803           if ( incidence->type() == "Todo" ) {
04804             Todo *t = static_cast<Todo *>( incidence );
04805             if ( t->dtDue().isValid() ) {
04806               atStr = KGlobal::locale()->formatDateTime( t->dtDue() );
04807             }
04808           } else {
04809             Event *e = static_cast<Event *>( incidence );
04810             if ( e->dtEnd().isValid() ) {
04811               atStr = KGlobal::locale()->formatDateTime( e->dtEnd() );
04812             }
04813           }
04814         }
04815       }
04816       if ( offset == 0 ) {
04817         if ( !atStr.isEmpty() ) {
04818           remStr = i18n( "reminder occurs at datetime", "at %1" ).arg( atStr );
04819         }
04820       } else {
04821         remStr = offsetStr.arg( secs2Duration( offset ) );
04822       }
04823 
04824       if ( alarm->repeatCount() > 0 ) {
04825         QString countStr = i18n( "repeats once", "repeats %n times", alarm->repeatCount() );
04826         QString intervalStr = i18n( "interval is N days/hours/minutes", "interval is %1" ).
04827                               arg( secs2Duration( alarm->snoozeTime().asSeconds() ) );
04828         QString repeatStr = i18n( "(repeat string, interval string)", "(%1, %2)" ).
04829                             arg( countStr, intervalStr );
04830         remStr = remStr + ' ' + repeatStr;
04831 
04832       }
04833       reminderStringList << remStr;
04834     }
04835   }
04836 
04837   return reminderStringList;
04838 }
KDE Home | KDE Accessibility Home | Description of Access Keys