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