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