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 //  QString color = "#DE8519"; // hard-coded color from Outlook2007, per request.
01162   QString color = QColor( Qt::red ).name() ; //customer changed their mind, now they want red.
01163   QString newtitle = "<font color=\"" + color + "\">" + title + "</font>";
01164   QString newvalue = "<font color=\"" + color + "\">" + value + "</font>" +
01165                      "&nbsp;" +
01166                      "(<strike>" + oldvalue + "</strike>)";
01167   return htmlRow( newtitle, newvalue );
01168 }
01169 
01170 static Attendee *findDelegatedFromMyAttendee( Incidence *incidence )
01171 {
01172   // Return the first attendee that was delegated-from me
01173 
01174   Attendee *attendee = 0;
01175   if ( !incidence ) {
01176     return attendee;
01177   }
01178 
01179   KEMailSettings settings;
01180   QStringList profiles = settings.profiles();
01181   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
01182     settings.setProfile( *it );
01183 
01184     QString delegatorName, delegatorEmail;
01185     Attendee::List attendees = incidence->attendees();
01186     Attendee::List::ConstIterator it2;
01187     for ( it2 = attendees.begin(); it2 != attendees.end(); ++it2 ) {
01188       Attendee *a = *it2;
01189       KPIM::getNameAndMail( a->delegator(), delegatorName, delegatorEmail );
01190       if ( settings.getSetting( KEMailSettings::EmailAddress ) == delegatorEmail ) {
01191         attendee = a;
01192         break;
01193       }
01194     }
01195   }
01196   return attendee;
01197 }
01198 
01199 static Attendee *findMyAttendee( Incidence *incidence )
01200 {
01201   // Return the attendee for the incidence that is probably me
01202 
01203   Attendee *attendee = 0;
01204   if ( !incidence ) {
01205     return attendee;
01206   }
01207 
01208   KEMailSettings settings;
01209   QStringList profiles = settings.profiles();
01210   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
01211     settings.setProfile( *it );
01212 
01213     Attendee::List attendees = incidence->attendees();
01214     Attendee::List::ConstIterator it2;
01215     for ( it2 = attendees.begin(); it2 != attendees.end(); ++it2 ) {
01216       Attendee *a = *it2;
01217       if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) {
01218         attendee = a;
01219         break;
01220       }
01221     }
01222   }
01223   return attendee;
01224 }
01225 
01226 static Attendee *findAttendee( Incidence *incidence, const QString &email )
01227 {
01228   // Search for an attendee by email address
01229 
01230   Attendee *attendee = 0;
01231   if ( !incidence ) {
01232     return attendee;
01233   }
01234 
01235   Attendee::List attendees = incidence->attendees();
01236   Attendee::List::ConstIterator it;
01237   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
01238     Attendee *a = *it;
01239     if ( email == a->email() ) {
01240       attendee = a;
01241       break;
01242     }
01243   }
01244   return attendee;
01245 }
01246 
01247 static bool rsvpRequested( Incidence *incidence )
01248 {
01249   if ( !incidence ) {
01250     return false;
01251   }
01252 
01253   //use a heuristic to determine if a response is requested.
01254 
01255   bool rsvp = true; // better send superfluously than not at all
01256   Attendee::List attendees = incidence->attendees();
01257   Attendee::List::ConstIterator it;
01258   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
01259     if ( it == attendees.begin() ) {
01260       rsvp = (*it)->RSVP(); // use what the first one has
01261     } else {
01262       if ( (*it)->RSVP() != rsvp ) {
01263         rsvp = true; // they differ, default
01264         break;
01265       }
01266     }
01267   }
01268   return rsvp;
01269 }
01270 
01271 static QString rsvpRequestedStr( bool rsvpRequested, const QString &role )
01272 {
01273   if ( rsvpRequested ) {
01274     if ( role.isEmpty() ) {
01275       return i18n( "Your response is requested" );
01276     } else {
01277       return i18n( "Your response as <b>%1</b> is requested" ).arg( role );
01278     }
01279   } else {
01280     if ( role.isEmpty() ) {
01281       return i18n( "No response is necessary" );
01282     } else {
01283       return i18n( "No response as <b>%1</b> is necessary" ).arg( role );
01284     }
01285   }
01286 }
01287 
01288 static QString myStatusStr( Incidence *incidence )
01289 {
01290   QString ret;
01291   Attendee *a = findMyAttendee( incidence );
01292   if ( a &&
01293        a->status() != Attendee::NeedsAction && a->status() != Attendee::Delegated ) {
01294     ret = i18n( "(<b>Note</b>: the Organizer preset your response to <b>%1</b>)" ).
01295           arg( Attendee::statusName( a->status() ) );
01296   }
01297   return ret;
01298 }
01299 
01300 static QString invitationPerson( const QString &email, const QString &name, const QString &uid )
01301 {
01302   QPair<QString, QString> s = searchNameAndUid( email, name, uid );
01303   const QString printName = s.first;
01304   const QString printUid = s.second;
01305 
01306   QString personString;
01307   // Make the uid link
01308   if ( !printUid.isEmpty() ) {
01309     personString += htmlAddUidLink( email, printName, printUid );
01310   } else {
01311     // No UID, just show some text
01312     personString += ( printName.isEmpty() ? email : printName );
01313   }
01314   personString += '\n';
01315 
01316   // Make the mailto link
01317   if ( !email.isEmpty() ) {
01318     personString += "&nbsp;" + htmlAddMailtoLink( email, printName );
01319   }
01320   personString += "\n";
01321 
01322   return personString;
01323 }
01324 
01325 static QString invitationDetailsIncidence( Incidence *incidence, bool noHtmlMode )
01326 {
01327   // if description and comment -> use both
01328   // if description, but no comment -> use the desc as the comment (and no desc)
01329   // if comment, but no description -> use the comment and no description
01330 
01331   QString html;
01332   QString descr;
01333   QStringList comments;
01334 
01335   if ( incidence->comments().isEmpty() ) {
01336     if ( !incidence->description().isEmpty() ) {
01337       // use description as comments
01338       if ( !QStyleSheet::mightBeRichText( incidence->description() ) ) {
01339         comments << string2HTML( incidence->description() );
01340       } else {
01341         comments << incidence->description();
01342         if ( noHtmlMode ) {
01343           comments[0] = cleanHtml( comments[0] );
01344         }
01345         comments[0] = htmlAddTag( "p", comments[0] );
01346       }
01347     }
01348     //else desc and comments are empty
01349   } else {
01350     // non-empty comments
01351     QStringList cl = incidence->comments();
01352     uint i = 0;
01353     for( QStringList::Iterator it=cl.begin(); it!=cl.end(); ++it ) {
01354       if ( !QStyleSheet::mightBeRichText( *it ) ) {
01355         comments.append( string2HTML( *it ) );
01356       } else {
01357         if ( noHtmlMode ) {
01358           comments.append( cleanHtml( "<body>" + (*it) + "</body>" ) );
01359         } else {
01360           comments.append( *it );
01361         }
01362       }
01363       i++;
01364     }
01365     if ( !incidence->description().isEmpty() ) {
01366       // use description too
01367       if ( !QStyleSheet::mightBeRichText( incidence->description() ) ) {
01368         descr = string2HTML( incidence->description() );
01369       } else {
01370         descr = incidence->description();
01371         if ( noHtmlMode ) {
01372           descr = cleanHtml( descr );
01373         }
01374         descr = htmlAddTag( "p", descr );
01375       }
01376     }
01377   }
01378 
01379   if( !descr.isEmpty() ) {
01380     html += "<p>";
01381     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01382     html += "<tr><td><center>" +
01383             htmlAddTag( "u", i18n( "Description:" ) ) +
01384             "</center></td></tr>";
01385     html += "<tr><td>" + descr + "</td></tr>";
01386     html += "</table>";
01387   }
01388 
01389   if ( !comments.isEmpty() ) {
01390     html += "<p>";
01391     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01392     html += "<tr><td><center>" +
01393             htmlAddTag( "u", i18n( "Comments:" ) ) +
01394             "</center></td></tr>";
01395     html += "<tr><td>";
01396     if ( comments.count() > 1 ) {
01397       html += "<ul>";
01398       for ( uint i=0; i < comments.count(); ++i ) {
01399         html += "<li>" + comments[i] + "</li>";
01400       }
01401       html += "</ul>";
01402     } else {
01403       html += comments[0];
01404     }
01405     html += "</td></tr>";
01406     html += "</table>";
01407   }
01408   return html;
01409 }
01410 
01411 static QString invitationDetailsEvent( Event* event, bool noHtmlMode )
01412 {
01413   // Invitation details are formatted into an HTML table
01414   if ( !event ) {
01415     return QString::null;
01416   }
01417 
01418   QString html = htmlInvitationDetailsBegin();
01419   html += htmlInvitationDetailsTableBegin();
01420 
01421   // Invitation summary & location rows
01422   html += htmlRow( i18n( "What:" ), invitationSummary( event, noHtmlMode ) );
01423   html += htmlRow( i18n( "Where:" ), invitationLocation( event, noHtmlMode ) );
01424 
01425   // If a 1 day event
01426   if ( event->dtStart().date() == event->dtEnd().date() ) {
01427     html += htmlRow( i18n( "Date:" ),
01428                      IncidenceFormatter::dateToString( event->dtStart(), false ) );
01429     if ( !event->doesFloat() ) {
01430       html += htmlRow( i18n( "Time:" ),
01431                        IncidenceFormatter::timeToString( event->dtStart(), true ) +
01432                        " - " +
01433                        IncidenceFormatter::timeToString( event->dtEnd(), true ) );
01434     }
01435   } else {
01436     html += htmlRow( i18n( "Starting date of an event", "From:" ),
01437                      IncidenceFormatter::dateToString( event->dtStart(), false ) );
01438     if ( !event->doesFloat() ) {
01439       html += htmlRow( i18n( "Starting time of an event", "At:" ),
01440                        IncidenceFormatter::timeToString( event->dtStart(), true ) );
01441     }
01442     if ( event->hasEndDate() ) {
01443       html += htmlRow( i18n( "Ending date of an event", "To:" ),
01444                        IncidenceFormatter::dateToString( event->dtEnd(), false ) );
01445       if ( !event->doesFloat() ) {
01446         html += htmlRow( i18n( "Starting time of an event", "At:" ),
01447                          IncidenceFormatter::timeToString( event->dtEnd(), true ) );
01448       }
01449     } else {
01450       html += htmlRow( i18n( "Ending date of an event", "To:" ),
01451                        i18n( "no end date specified" ) );
01452     }
01453   }
01454 
01455   // Invitation Duration Row
01456   html += htmlRow( i18n( "Duration:" ), IncidenceFormatter::durationString( event ) );
01457 
01458   // Invitation Recurrence Row
01459   if ( event->doesRecur() ) {
01460     html += htmlRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( event ) );
01461   }
01462 
01463   html += htmlInvitationDetailsTableEnd();
01464   html += invitationDetailsIncidence( event, noHtmlMode );
01465   html += htmlInvitationDetailsEnd();
01466 
01467   return html;
01468 }
01469 
01470 static QString invitationDetailsEvent( Event *event, Event *oldevent, bool noHtmlMode )
01471 {
01472   if ( !oldevent ) {
01473     return invitationDetailsEvent( event, noHtmlMode );
01474   }
01475 
01476   QString html = htmlInvitationDetailsBegin();
01477   html += htmlInvitationDetailsTableBegin();
01478 
01479   html += htmlRow( i18n( "What:" ),
01480                    invitationSummary( event, noHtmlMode ),
01481                    invitationSummary( oldevent, noHtmlMode ) );
01482 
01483   html += htmlRow( i18n( "Where:" ),
01484                    invitationLocation( event, noHtmlMode ),
01485                    invitationLocation( oldevent, noHtmlMode ) );
01486 
01487 
01488   // If a 1 day event
01489   if ( event->dtStart().date() == event->dtEnd().date() ) {
01490     html += htmlRow( i18n( "Date:" ),
01491                      IncidenceFormatter::dateToString( event->dtStart(), false ),
01492                      IncidenceFormatter::dateToString( oldevent->dtStart(), false ) );
01493     QString spanStr , oldspanStr;
01494     if ( !event->doesFloat() ) {
01495       spanStr = IncidenceFormatter::timeToString( event->dtStart(), true ) +
01496                 " - " +
01497                 IncidenceFormatter::timeToString( event->dtEnd(), true );
01498     }
01499     if ( !oldevent->doesFloat() ) {
01500       oldspanStr = IncidenceFormatter::timeToString( oldevent->dtStart(), true ) +
01501                    " - " +
01502                    IncidenceFormatter::timeToString( oldevent->dtEnd(), true );
01503     }
01504     html += htmlRow( i18n( "Time:" ), spanStr, oldspanStr );
01505   } else {
01506     html += htmlRow( i18n( "Starting date of an event", "From:" ),
01507                      IncidenceFormatter::dateToString( event->dtStart(), false ),
01508                      IncidenceFormatter::dateToString( oldevent->dtStart(), false ) );
01509     QString startStr, oldstartStr;
01510     if ( !event->doesFloat() ) {
01511       startStr = IncidenceFormatter::timeToString( event->dtStart(), true );
01512     }
01513     if ( !oldevent->doesFloat() ) {
01514       oldstartStr = IncidenceFormatter::timeToString( oldevent->dtStart(), true );
01515     }
01516     html += htmlRow( i18n( "Starting time of an event", "At:" ), startStr, oldstartStr );
01517 
01518     if ( event->hasEndDate() ) {
01519       html += htmlRow( i18n( "Ending date of an event", "To:" ),
01520                        IncidenceFormatter::dateToString( event->dtEnd(), false ),
01521                        IncidenceFormatter::dateToString( oldevent->dtEnd(), false ) );
01522       QString endStr, oldendStr;
01523       if ( !event->doesFloat() ) {
01524         endStr = IncidenceFormatter::timeToString( event->dtEnd(), true );
01525       }
01526       if ( !oldevent->doesFloat() ) {
01527         oldendStr = IncidenceFormatter::timeToString( oldevent->dtEnd(), true );
01528       }
01529       html += htmlRow( i18n( "Starting time of an event", "At:" ), endStr, oldendStr );
01530     } else {
01531       QString endStr = i18n( "no end date specified" );
01532       QString oldendStr;
01533       if ( !oldevent->hasEndDate() ) {
01534         oldendStr = i18n( "no end date specified" );
01535       } else {
01536         oldendStr = IncidenceFormatter::dateTimeToString( oldevent->dtEnd(),
01537                                                           oldevent->doesFloat(), false );
01538       }
01539       html += htmlRow( i18n( "Ending date of an event", "To:" ), endStr, oldendStr );
01540     }
01541   }
01542 
01543   html += htmlRow( i18n( "Duration:" ),
01544                    IncidenceFormatter::durationString( event ),
01545                    IncidenceFormatter::durationString( oldevent ) );
01546 
01547   QString recurStr, oldrecurStr;
01548   if ( event->doesRecur() ) {
01549     recurStr = IncidenceFormatter::recurrenceString( event );
01550   }
01551   if ( oldevent->doesRecur() ) {
01552     oldrecurStr = IncidenceFormatter::recurrenceString( oldevent );
01553   }
01554   html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr );
01555 
01556   html += htmlInvitationDetailsTableEnd();
01557   html += invitationDetailsIncidence( event, noHtmlMode );
01558   html += htmlInvitationDetailsEnd();
01559 
01560   return html;
01561 }
01562 
01563 static QString invitationDetailsTodo( Todo *todo, bool noHtmlMode )
01564 {
01565   // Task details are formatted into an HTML table
01566   if ( !todo ) {
01567     return QString::null;
01568   }
01569 
01570   QString html = htmlInvitationDetailsBegin();
01571   html += htmlInvitationDetailsTableBegin();
01572 
01573   // Invitation summary & location rows
01574   html += htmlRow( i18n( "What:" ), invitationSummary( todo, noHtmlMode ) );
01575   html += htmlRow( i18n( "Where:" ), invitationLocation( todo, noHtmlMode ) );
01576 
01577   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01578     html += htmlRow( i18n( "Start Date:" ),
01579                      IncidenceFormatter::dateToString( todo->dtStart(), false ) );
01580     if ( !todo->doesFloat() ) {
01581       html += htmlRow( i18n( "Start Time:" ),
01582                        IncidenceFormatter::timeToString( todo->dtStart(), false ) );
01583     }
01584   }
01585   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01586     html += htmlRow( i18n( "Due Date:" ),
01587                      IncidenceFormatter::dateToString( todo->dtDue(), false ) );
01588     if ( !todo->doesFloat() ) {
01589       html += htmlRow( i18n( "Due Time:" ),
01590                        IncidenceFormatter::timeToString( todo->dtDue(), false ) );
01591     }
01592   } else {
01593     html += htmlRow( i18n( "Due Date:" ), i18n( "Due Date: None", "None" ) );
01594   }
01595 
01596   // Invitation Duration Row
01597   html += htmlRow( i18n( "Duration:" ), IncidenceFormatter::durationString( todo ) );
01598 
01599   // Invitation Recurrence Row
01600   if ( todo->doesRecur() ) {
01601     html += htmlRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( todo ) );
01602   }
01603 
01604   html += htmlInvitationDetailsTableEnd();
01605   html += invitationDetailsIncidence( todo, noHtmlMode );
01606   html += htmlInvitationDetailsEnd();
01607 
01608   return html;
01609 }
01610 
01611 static QString invitationDetailsTodo( Todo *todo, Todo *oldtodo, bool noHtmlMode )
01612 {
01613   if ( !oldtodo ) {
01614     return invitationDetailsTodo( todo, noHtmlMode );
01615   }
01616 
01617   QString html = htmlInvitationDetailsBegin();
01618   html += htmlInvitationDetailsTableBegin();
01619 
01620   html += htmlRow( i18n( "What:" ),
01621                    invitationSummary( todo, noHtmlMode ),
01622                    invitationSummary( todo, noHtmlMode ) );
01623 
01624   html += htmlRow( i18n( "Where:" ),
01625                    invitationLocation( todo, noHtmlMode ),
01626                    invitationLocation( oldtodo, noHtmlMode ) );
01627 
01628   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01629     html += htmlRow( i18n( "Start Date:" ),
01630                      IncidenceFormatter::dateToString( todo->dtStart(), false ),
01631                      IncidenceFormatter::dateToString( oldtodo->dtStart(), false ) );
01632     QString startTimeStr, oldstartTimeStr;
01633     if ( !todo->doesFloat() ) {
01634       startTimeStr = IncidenceFormatter::timeToString( todo->dtStart(), false );
01635     }
01636     if ( !oldtodo->doesFloat() ) {
01637       oldstartTimeStr = IncidenceFormatter::timeToString( oldtodo->dtStart(), false );
01638     }
01639     html += htmlRow( i18n( "Start Time:" ), startTimeStr, oldstartTimeStr );
01640   }
01641 
01642   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01643     html += htmlRow( i18n( "Due Date:" ),
01644                      IncidenceFormatter::dateToString( todo->dtDue(), false ),
01645                      IncidenceFormatter::dateToString( oldtodo->dtDue(), false ) );
01646     QString endTimeStr, oldendTimeStr;
01647     if ( !todo->doesFloat() ) {
01648       endTimeStr = IncidenceFormatter::timeToString( todo->dtDue(), false );
01649     }
01650     if ( !oldtodo->doesFloat() ) {
01651       oldendTimeStr = IncidenceFormatter::timeToString( oldtodo->dtDue(), false );
01652     }
01653     html += htmlRow( i18n( "Due Time:" ), endTimeStr, oldendTimeStr );
01654   } else {
01655     QString dueStr = i18n( "Due Date: None", "None" );
01656     QString olddueStr;
01657     if ( !oldtodo->hasDueDate() || !oldtodo->dtDue().isValid() ) {
01658       olddueStr = i18n( "Due Date: None", "None" );
01659     } else {
01660       olddueStr = IncidenceFormatter::dateTimeToString( oldtodo->dtDue(),
01661                                                         oldtodo->doesFloat(), false );
01662     }
01663     html += htmlRow( i18n( "Due Date:" ), dueStr, olddueStr );
01664   }
01665 
01666   html += htmlRow( i18n( "Duration:" ),
01667                    IncidenceFormatter::durationString( todo ),
01668                    IncidenceFormatter::durationString( oldtodo ) );
01669 
01670   QString recurStr, oldrecurStr;
01671   if ( todo->doesRecur() ) {
01672     recurStr = IncidenceFormatter::recurrenceString( todo );
01673   }
01674   if ( oldtodo->doesRecur() ) {
01675     oldrecurStr = IncidenceFormatter::recurrenceString( oldtodo );
01676   }
01677   html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr );
01678 
01679   html += htmlInvitationDetailsTableEnd();
01680   html += invitationDetailsIncidence( todo, noHtmlMode );
01681   html += htmlInvitationDetailsEnd();
01682 
01683   return html;
01684 }
01685 
01686 static QString invitationDetailsJournal( Journal *journal, bool noHtmlMode )
01687 {
01688   if ( !journal ) {
01689     return QString::null;
01690   }
01691 
01692   QString html = htmlInvitationDetailsBegin();
01693   html += htmlInvitationDetailsTableBegin();
01694 
01695   html += htmlRow( i18n( "Summary:" ), invitationSummary( journal, noHtmlMode ) );
01696   html += htmlRow( i18n( "Date:" ), IncidenceFormatter::dateToString( journal->dtStart(), false ) );
01697 
01698   html += htmlInvitationDetailsTableEnd();
01699   html += invitationDetailsIncidence( journal, noHtmlMode );
01700   html += htmlInvitationDetailsEnd();
01701 
01702   return html;
01703 }
01704 
01705 static QString invitationDetailsJournal( Journal *journal, Journal *oldjournal, bool noHtmlMode )
01706 {
01707   if ( !oldjournal ) {
01708     return invitationDetailsJournal( journal, noHtmlMode );
01709   }
01710 
01711   QString html = htmlInvitationDetailsBegin();
01712   html += htmlInvitationDetailsTableBegin();
01713 
01714   html += htmlRow( i18n( "What:" ),
01715                    invitationSummary( journal, noHtmlMode ),
01716                    invitationSummary( oldjournal, noHtmlMode ) );
01717 
01718   html += htmlRow( i18n( "Date:" ),
01719                    IncidenceFormatter::dateToString( journal->dtStart(), false ),
01720                    IncidenceFormatter::dateToString( oldjournal->dtStart(), false ) );
01721 
01722   html += htmlInvitationDetailsTableEnd();
01723   html += invitationDetailsIncidence( journal, noHtmlMode );
01724   html += htmlInvitationDetailsEnd();
01725 
01726   return html;
01727 }
01728 
01729 static QString invitationDetailsFreeBusy( FreeBusy *fb, bool /*noHtmlMode*/ )
01730 {
01731   if ( !fb ) {
01732     return QString::null;
01733   }
01734 
01735   QString html = htmlInvitationDetailsTableBegin();
01736 
01737   html += htmlRow( i18n("Person:"), fb->organizer().fullName() );
01738   html += htmlRow( i18n("Start date:"),
01739                    IncidenceFormatter::dateToString( fb->dtStart(), true ) );
01740   html += htmlRow( i18n("End date:"),
01741                    KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) );
01742   html += "<tr><td colspan=2><hr></td></tr>\n";
01743   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
01744 
01745   QValueList<Period> periods = fb->busyPeriods();
01746 
01747   QValueList<Period>::iterator it;
01748   for ( it = periods.begin(); it != periods.end(); ++it ) {
01749     Period per = *it;
01750     if ( per.hasDuration() ) {
01751       int dur = per.duration().asSeconds();
01752       QString cont;
01753       if ( dur >= 3600 ) {
01754         cont += i18n("1 hour ", "%n hours ", dur / 3600);
01755         dur %= 3600;
01756       }
01757       if ( dur >= 60 ) {
01758         cont += i18n("1 minute", "%n minutes ", dur / 60);
01759         dur %= 60;
01760       }
01761       if ( dur > 0 ) {
01762         cont += i18n("1 second", "%n seconds", dur);
01763       }
01764       html += htmlRow( QString::null, i18n("startDate for duration", "%1 for %2").
01765                        arg( KGlobal::locale()->formatDateTime( per.start(), false ) ).
01766                        arg(cont) );
01767     } else {
01768       QString cont;
01769       if ( per.start().date() == per.end().date() ) {
01770         cont = i18n("date, fromTime - toTime ", "%1, %2 - %3")
01771             .arg( KGlobal::locale()->formatDate( per.start().date() ) )
01772             .arg( KGlobal::locale()->formatTime( per.start().time() ) )
01773             .arg( KGlobal::locale()->formatTime( per.end().time() ) );
01774       } else {
01775         cont = i18n("fromDateTime - toDateTime", "%1 - %2")
01776           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
01777           .arg( KGlobal::locale()->formatDateTime( per.end(), false ) );
01778       }
01779 
01780       html += htmlRow( QString::null, cont );
01781     }
01782   }
01783 
01784   html += htmlInvitationDetailsTableEnd();
01785   return html;
01786 }
01787 
01788 static QString invitationDetailsFreeBusy( FreeBusy *freebusy, FreeBusy *oldfreebusy, bool noHtmlMode )
01789 {
01790   Q_UNUSED( oldfreebusy );
01791   return invitationDetailsFreeBusy( freebusy, noHtmlMode );
01792 }
01793 
01794 static bool replyMeansCounter( Incidence */*incidence*/ )
01795 {
01796   return false;
01811 }
01812 
01813 static QString invitationHeaderEvent( Event *event, Incidence *existingIncidence,
01814                                       ScheduleMessage *msg, const QString &sender )
01815 {
01816   if ( !msg || !event )
01817     return QString::null;
01818 
01819   switch ( msg->method() ) {
01820   case Scheduler::Publish:
01821     return i18n( "This invitation has been published" );
01822   case Scheduler::Request:
01823     if ( existingIncidence && event->revision() > 0 ) {
01824       return i18n( "This invitation has been updated by the organizer %1" ).
01825         arg( event->organizer().fullName() );
01826     }
01827     if ( iamOrganizer( event ) ) {
01828       return i18n( "I created this invitation" );
01829     } else {
01830       QString orgStr;
01831       if ( !event->organizer().fullName().isEmpty() ) {
01832         orgStr = event->organizer().fullName();
01833       } else if ( !event->organizer().email().isEmpty() ) {
01834         orgStr = event->organizer().email();
01835       }
01836       if ( senderIsOrganizer( event, sender ) ) {
01837         if ( !orgStr.isEmpty() ) {
01838           return i18n( "You received an invitation from %1" ).arg( orgStr );
01839         } else {
01840           return i18n( "You received an invitation" );
01841         }
01842       } else {
01843         if ( !orgStr.isEmpty() ) {
01844           return i18n( "You received an invitation from %1 as a representative of %2" ).
01845             arg( sender, orgStr );
01846         } else {
01847           return i18n( "You received an invitation from %1 as the organizer's representative" ).
01848             arg( sender );
01849         }
01850       }
01851     }
01852   case Scheduler::Refresh:
01853     return i18n( "This invitation was refreshed" );
01854   case Scheduler::Cancel:
01855     return i18n( "This invitation has been canceled" );
01856   case Scheduler::Add:
01857     return i18n( "Addition to the invitation" );
01858   case Scheduler::Reply:
01859   {
01860     if ( replyMeansCounter( event ) ) {
01861       return i18n( "%1 makes this counter proposal" ).
01862         arg( firstAttendeeName( event, i18n( "Sender" ) ) );
01863     }
01864 
01865     Attendee::List attendees = event->attendees();
01866     if( attendees.count() == 0 ) {
01867       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01868       return QString::null;
01869     }
01870     if( attendees.count() != 1 ) {
01871       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01872                     << "but is " << attendees.count() << endl;
01873     }
01874     QString attendeeName = firstAttendeeName( event, i18n( "Sender" ) );
01875 
01876     QString delegatorName, dummy;
01877     Attendee* attendee = *attendees.begin();
01878     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
01879     if ( delegatorName.isEmpty() ) {
01880       delegatorName = attendee->delegator();
01881     }
01882 
01883     switch( attendee->status() ) {
01884     case Attendee::NeedsAction:
01885       return i18n( "%1 indicates this invitation still needs some action" ).arg( attendeeName );
01886     case Attendee::Accepted:
01887       if ( event->revision() > 0 ) {
01888         if ( !sender.isEmpty() ) {
01889           return i18n( "This invitation has been updated by attendee %1" ).arg( sender );
01890         } else {
01891           return i18n( "This invitation has been updated by an attendee" );
01892         }
01893       } else {
01894         if ( delegatorName.isEmpty() ) {
01895           return i18n( "%1 accepts this invitation" ).arg( attendeeName );
01896         } else {
01897           return i18n( "%1 accepts this invitation on behalf of %2" ).
01898             arg( attendeeName ).arg( delegatorName );
01899         }
01900       }
01901     case Attendee::Tentative:
01902       if ( delegatorName.isEmpty() ) {
01903         return i18n( "%1 tentatively accepts this invitation" ).
01904           arg( attendeeName );
01905       } else {
01906         return i18n( "%1 tentatively accepts this invitation on behalf of %2" ).
01907           arg( attendeeName ).arg( delegatorName );
01908       }
01909     case Attendee::Declined:
01910       if ( delegatorName.isEmpty() ) {
01911         return i18n( "%1 declines this invitation" ).arg( attendeeName );
01912       } else {
01913         return i18n( "%1 declines this invitation on behalf of %2" ).
01914           arg( attendeeName ).arg( delegatorName );
01915       }
01916     case Attendee::Delegated: {
01917       QString delegate, dummy;
01918       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
01919       if ( delegate.isEmpty() ) {
01920         delegate = attendee->delegate();
01921       }
01922       if ( !delegate.isEmpty() ) {
01923         return i18n( "%1 has delegated this invitation to %2" ).
01924           arg( attendeeName ) .arg( delegate );
01925       } else {
01926         return i18n( "%1 has delegated this invitation" ).arg( attendeeName );
01927       }
01928     }
01929     case Attendee::Completed:
01930       return i18n( "This invitation is now completed" );
01931     case Attendee::InProcess:
01932       return i18n( "%1 is still processing the invitation" ).
01933         arg( attendeeName );
01934     default:
01935       return i18n( "Unknown response to this invitation" );
01936     }
01937     break;
01938   }
01939 
01940   case Scheduler::Counter:
01941     return i18n( "%1 makes this counter proposal" ).
01942       arg( firstAttendeeName( event, i18n( "Sender" ) ) );
01943 
01944   case Scheduler::Declinecounter:
01945     return i18n( "%1 declines the counter proposal" ).
01946       arg( firstAttendeeName( event, i18n( "Sender" ) ) );
01947 
01948   case Scheduler::NoMethod:
01949     return i18n("Error: iMIP message with unknown method: '%1'").
01950       arg( msg->method() );
01951   }
01952   return QString::null;
01953 }
01954 
01955 static QString invitationHeaderTodo( Todo *todo, Incidence *existingIncidence,
01956                                      ScheduleMessage *msg, const QString &sender )
01957 {
01958   if ( !msg || !todo ) {
01959     return QString::null;
01960   }
01961 
01962   switch ( msg->method() ) {
01963   case Scheduler::Publish:
01964     return i18n("This task has been published");
01965   case Scheduler::Request:
01966     if ( existingIncidence && todo->revision() > 0 ) {
01967       return i18n( "This task has been updated by the organizer %1" ).
01968         arg( todo->organizer().fullName() );
01969     } else {
01970       if ( iamOrganizer( todo ) ) {
01971         return i18n( "I created this task" );
01972       } else {
01973         QString orgStr;
01974         if ( !todo->organizer().fullName().isEmpty() ) {
01975           orgStr = todo->organizer().fullName();
01976         } else if ( !todo->organizer().email().isEmpty() ) {
01977           orgStr = todo->organizer().email();
01978         }
01979         if ( senderIsOrganizer( todo, sender ) ) {
01980           if ( !orgStr.isEmpty() ) {
01981             return i18n( "You have been assigned this task by %1" ).arg( orgStr );
01982           } else {
01983             return i18n( "You have been assigned this task" );
01984           }
01985         } else {
01986           if ( !orgStr.isEmpty() ) {
01987             return i18n( "You have been assigned this task by %1 as a representative of %2" ).
01988               arg( sender, orgStr );
01989           } else {
01990             return i18n( "You have been assigned this task by %1 as the organizer's representative" ).
01991               arg( sender );
01992           }
01993         }
01994       }
01995     }
01996   case Scheduler::Refresh:
01997     return i18n( "This task was refreshed" );
01998   case Scheduler::Cancel:
01999     return i18n( "This task was canceled" );
02000   case Scheduler::Add:
02001     return i18n( "Addition to the task" );
02002   case Scheduler::Reply:
02003   {
02004     if ( replyMeansCounter( todo ) ) {
02005       return i18n( "%1 makes this counter proposal" ).
02006         arg( firstAttendeeName( todo, i18n( "Sender" ) ) );
02007     }
02008 
02009     Attendee::List attendees = todo->attendees();
02010     if( attendees.count() == 0 ) {
02011       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
02012       return QString::null;
02013     }
02014     if( attendees.count() != 1 ) {
02015       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
02016                     << "but is " << attendees.count() << endl;
02017     }
02018     QString attendeeName = firstAttendeeName( todo, i18n( "Sender" ) );
02019 
02020     QString delegatorName, dummy;
02021     Attendee* attendee = *attendees.begin();
02022     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
02023     if ( delegatorName.isEmpty() ) {
02024       delegatorName = attendee->delegator();
02025     }
02026 
02027     switch( attendee->status() ) {
02028     case Attendee::NeedsAction:
02029       return i18n( "%1 indicates this task assignment still needs some action" ).arg( attendeeName );
02030     case Attendee::Accepted:
02031       if ( todo->revision() > 0 ) {
02032         if ( !sender.isEmpty() ) {
02033           if ( todo->isCompleted() ) {
02034             return i18n( "This task has been completed by assignee %1" ).arg( sender );
02035           } else {
02036             return i18n( "This task has been updated by assignee %1" ).arg( sender );
02037           }
02038         } else {
02039           if ( todo->isCompleted() ) {
02040             return i18n( "This task has been completed by an assignee" );
02041           } else {
02042             return i18n( "This task has been updated by an assignee" );
02043           }
02044         }
02045       } else {
02046         if ( delegatorName.isEmpty() ) {
02047           return i18n( "%1 accepts this task" ).arg( attendeeName );
02048         } else {
02049           return i18n( "%1 accepts this task on behalf of %2" ).
02050             arg( attendeeName ).arg( delegatorName );
02051         }
02052       }
02053     case Attendee::Tentative:
02054       if ( delegatorName.isEmpty() ) {
02055         return i18n( "%1 tentatively accepts this task" ).
02056           arg( attendeeName );
02057       } else {
02058         return i18n( "%1 tentatively accepts this task on behalf of %2" ).
02059           arg( attendeeName ).arg( delegatorName );
02060       }
02061     case Attendee::Declined:
02062       if ( delegatorName.isEmpty() ) {
02063         return i18n( "%1 declines this task" ).arg( attendeeName );
02064       } else {
02065         return i18n( "%1 declines this task on behalf of %2" ).
02066           arg( attendeeName ).arg( delegatorName );
02067       }
02068     case Attendee::Delegated: {
02069       QString delegate, dummy;
02070       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
02071       if ( delegate.isEmpty() ) {
02072         delegate = attendee->delegate();
02073       }
02074       if ( !delegate.isEmpty() ) {
02075         return i18n( "%1 has delegated this request for the task to %2" ).
02076           arg( attendeeName ).arg( delegate );
02077       } else {
02078         return i18n( "%1 has delegated this request for the task" ).
02079           arg( attendeeName );
02080       }
02081     }
02082     case Attendee::Completed:
02083       return i18n( "The request for this task is now completed" );
02084     case Attendee::InProcess:
02085       return i18n( "%1 is still processing the task" ).
02086         arg( attendeeName );
02087     default:
02088       return i18n( "Unknown response to this task" );
02089     }
02090     break;
02091   }
02092 
02093   case Scheduler::Counter:
02094     return i18n( "%1 makes this counter proposal" ).
02095       arg( firstAttendeeName( todo, i18n( "Sender" ) ) );
02096 
02097   case Scheduler::Declinecounter:
02098     return i18n( "%1 declines the counter proposal" ).
02099       arg( firstAttendeeName( todo, i18n( "Sender" ) ) );
02100 
02101   case Scheduler::NoMethod:
02102     return i18n( "Error: iMIP message with unknown method: '%1'" ).
02103       arg( msg->method() );
02104   }
02105   return QString::null;
02106 }
02107 
02108 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
02109 {
02110   if ( !msg || !journal ) {
02111     return QString::null;
02112   }
02113 
02114   switch ( msg->method() ) {
02115   case Scheduler::Publish:
02116     return i18n("This journal has been published");
02117   case Scheduler::Request:
02118     return i18n( "You have been assigned this journal" );
02119   case Scheduler::Refresh:
02120     return i18n( "This journal was refreshed" );
02121   case Scheduler::Cancel:
02122     return i18n( "This journal was canceled" );
02123   case Scheduler::Add:
02124     return i18n( "Addition to the journal" );
02125   case Scheduler::Reply:
02126   {
02127     if ( replyMeansCounter( journal ) ) {
02128       return i18n( "Sender makes this counter proposal" );
02129     }
02130 
02131     Attendee::List attendees = journal->attendees();
02132     if( attendees.count() == 0 ) {
02133       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
02134       return QString::null;
02135     }
02136     if( attendees.count() != 1 ) {
02137       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
02138                     << "but is " << attendees.count() << endl;
02139     }
02140     Attendee* attendee = *attendees.begin();
02141 
02142     switch( attendee->status() ) {
02143     case Attendee::NeedsAction:
02144       return i18n( "Sender indicates this journal assignment still needs some action" );
02145     case Attendee::Accepted:
02146       return i18n( "Sender accepts this journal" );
02147     case Attendee::Tentative:
02148       return i18n( "Sender tentatively accepts this journal" );
02149     case Attendee::Declined:
02150       return i18n( "Sender declines this journal" );
02151     case Attendee::Delegated:
02152       return i18n( "Sender has delegated this request for the journal" );
02153     case Attendee::Completed:
02154       return i18n( "The request for this journal is now completed" );
02155     case Attendee::InProcess:
02156       return i18n( "Sender is still processing the invitation" );
02157     default:
02158       return i18n( "Unknown response to this journal" );
02159     }
02160     break;
02161   }
02162   case Scheduler::Counter:
02163     return i18n( "Sender makes this counter proposal" );
02164   case Scheduler::Declinecounter:
02165     return i18n( "Sender declines the counter proposal" );
02166   case Scheduler::NoMethod:
02167     return i18n("Error: iMIP message with unknown method: '%1'").
02168       arg( msg->method() );
02169   }
02170   return QString::null;
02171 }
02172 
02173 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
02174 {
02175   if ( !msg || !fb ) {
02176     return QString::null;
02177   }
02178 
02179   switch ( msg->method() ) {
02180   case Scheduler::Publish:
02181     return i18n("This free/busy list has been published");
02182   case Scheduler::Request:
02183     return i18n( "The free/busy list has been requested" );
02184   case Scheduler::Refresh:
02185     return i18n( "This free/busy list was refreshed" );
02186   case Scheduler::Cancel:
02187     return i18n( "This free/busy list was canceled" );
02188   case Scheduler::Add:
02189     return i18n( "Addition to the free/busy list" );
02190   case Scheduler::NoMethod:
02191   default:
02192     return i18n("Error: Free/Busy iMIP message with unknown method: '%1'").
02193       arg( msg->method() );
02194   }
02195 }
02196 
02197 static QString invitationAttendees( Incidence *incidence, Attendee *sender )
02198 {
02199   QString tmpStr;
02200   if ( !incidence ) {
02201     return tmpStr;
02202   }
02203 
02204   if ( incidence->type() == "Todo" ) {
02205     tmpStr += htmlAddTag( "u", i18n( "Assignees" ) );
02206   } else {
02207     tmpStr += htmlAddTag( "u", i18n( "Attendees" ) );
02208   }
02209   tmpStr += "<br/>";
02210 
02211   int count=0;
02212   Attendee::List attendees = incidence->attendees();
02213   if ( !attendees.isEmpty() ) {
02214 
02215     Attendee::List::ConstIterator it;
02216     for( it = attendees.begin(); it != attendees.end(); ++it ) {
02217       Attendee *a = *it;
02218       if ( !iamAttendee( a ) ) {
02219         QString statusStr = a->statusStr();
02220         if ( sender && a->email() == sender->email() ) {
02221           // use the attendee taken from the response incidence,
02222           // rather than the attendee from the calendar incidence.
02223           if ( a->status() != sender->status() ) {
02224             statusStr = i18n( "%1 (<i>unrecorded</i>)" ).arg( sender->statusStr() );
02225           }
02226           a = sender;
02227         }
02228         count++;
02229         if ( count == 1 ) {
02230           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\" columns=\"2\">";
02231         }
02232         tmpStr += "<tr>";
02233         tmpStr += "<td>";
02234         tmpStr += invitationPerson( a->email(), a->name(), QString::null );
02235         if ( !a->delegator().isEmpty() ) {
02236           tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
02237         }
02238         if ( !a->delegate().isEmpty() ) {
02239           tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
02240         }
02241         tmpStr += "</td>";
02242         tmpStr += "<td>" + statusStr + "</td>";
02243         tmpStr += "</tr>";
02244       }
02245     }
02246   }
02247   if ( count ) {
02248     tmpStr += "</table>";
02249   } else {
02250     tmpStr += "<i>" + i18n( "No attendee", "None" ) + "</i>";
02251   }
02252 
02253   return tmpStr;
02254 }
02255 
02256 static QString invitationAttachments( InvitationFormatterHelper *helper, Incidence *incidence )
02257 {
02258   QString tmpStr;
02259   if ( !incidence ) {
02260     return tmpStr;
02261   }
02262 
02263   Attachment::List attachments = incidence->attachments();
02264   if ( !attachments.isEmpty() ) {
02265     tmpStr += i18n( "Attached Documents:" ) + "<ol>";
02266 
02267     Attachment::List::ConstIterator it;
02268     for( it = attachments.begin(); it != attachments.end(); ++it ) {
02269       Attachment *a = *it;
02270       tmpStr += "<li>";
02271       // Attachment icon
02272       KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
02273       const QString iconStr = mimeType ? mimeType->icon( a->uri(), false ) : QString( "application-octet-stream" );
02274       const QString iconPath = KGlobal::iconLoader()->iconPath( iconStr, KIcon::Small );
02275       if ( !iconPath.isEmpty() ) {
02276         tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
02277       }
02278       tmpStr += helper->makeLink( "ATTACH:" + a->label(), a->label() );
02279       tmpStr += "</li>";
02280     }
02281     tmpStr += "</ol>";
02282   }
02283 
02284   return tmpStr;
02285 }
02286 
02287 class IncidenceFormatter::ScheduleMessageVisitor
02288   : public IncidenceBase::Visitor
02289 {
02290   public:
02291     ScheduleMessageVisitor() : mExistingIncidence( 0 ), mMessage( 0 ) { mResult = ""; }
02292     bool act( IncidenceBase *incidence, Incidence *existingIncidence, ScheduleMessage *msg,
02293               const QString &sender )
02294     {
02295       mExistingIncidence = existingIncidence;
02296       mMessage = msg;
02297       mSender = sender;
02298       return incidence->accept( *this );
02299     }
02300     QString result() const { return mResult; }
02301 
02302   protected:
02303     QString mResult;
02304     Incidence *mExistingIncidence;
02305     ScheduleMessage *mMessage;
02306     QString mSender;
02307 };
02308 
02309 class IncidenceFormatter::InvitationHeaderVisitor
02310   : public IncidenceFormatter::ScheduleMessageVisitor
02311 {
02312   protected:
02313     bool visit( Event *event )
02314     {
02315       mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender );
02316       return !mResult.isEmpty();
02317     }
02318     bool visit( Todo *todo )
02319     {
02320       mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender );
02321       return !mResult.isEmpty();
02322     }
02323     bool visit( Journal *journal )
02324     {
02325       mResult = invitationHeaderJournal( journal, mMessage );
02326       return !mResult.isEmpty();
02327     }
02328     bool visit( FreeBusy *fb )
02329     {
02330       mResult = invitationHeaderFreeBusy( fb, mMessage );
02331       return !mResult.isEmpty();
02332     }
02333 };
02334 
02335 class IncidenceFormatter::InvitationBodyVisitor
02336   : public IncidenceFormatter::ScheduleMessageVisitor
02337 {
02338   public:
02339     InvitationBodyVisitor( bool noHtmlMode )
02340       : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ) {}
02341 
02342   protected:
02343     bool visit( Event *event )
02344     {
02345       Event *oldevent = dynamic_cast<Event *>( mExistingIncidence );
02346       mResult = invitationDetailsEvent( event, oldevent, mNoHtmlMode );
02347       return !mResult.isEmpty();
02348     }
02349     bool visit( Todo *todo )
02350     {
02351       Todo *oldtodo = dynamic_cast<Todo *>( mExistingIncidence );
02352       mResult = invitationDetailsTodo( todo, oldtodo, mNoHtmlMode );
02353       return !mResult.isEmpty();
02354     }
02355     bool visit( Journal *journal )
02356     {
02357       Journal *oldjournal = dynamic_cast<Journal *>( mExistingIncidence );
02358       mResult = invitationDetailsJournal( journal, oldjournal, mNoHtmlMode );
02359       return !mResult.isEmpty();
02360     }
02361     bool visit( FreeBusy *fb )
02362     {
02363       mResult = invitationDetailsFreeBusy( fb, 0, mNoHtmlMode );
02364       return !mResult.isEmpty();
02365     }
02366 
02367   private:
02368     bool mNoHtmlMode;
02369 };
02370 
02371 class IncidenceFormatter::IncidenceCompareVisitor
02372   : public IncidenceBase::Visitor
02373 {
02374   public:
02375     IncidenceCompareVisitor() : mExistingIncidence(0) {}
02376     bool act( IncidenceBase *incidence, Incidence *existingIncidence, int method )
02377     {
02378       Incidence *inc = dynamic_cast<Incidence*>( incidence );
02379       if ( !inc || !existingIncidence || inc->revision() <= existingIncidence->revision() )
02380         return false;
02381       mExistingIncidence = existingIncidence;
02382       mMethod = method;
02383       return incidence->accept( *this );
02384     }
02385 
02386     QString result() const
02387     {
02388       if ( mChanges.isEmpty() ) {
02389         return QString::null;
02390       }
02391       QString html = "<div align=\"left\"><ul><li>";
02392       html += mChanges.join( "</li><li>" );
02393       html += "</li><ul></div>";
02394       return html;
02395     }
02396 
02397   protected:
02398     bool visit( Event *event )
02399     {
02400       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
02401       compareIncidences( event, mExistingIncidence, mMethod );
02402       return !mChanges.isEmpty();
02403     }
02404     bool visit( Todo *todo )
02405     {
02406       compareTodos( todo, dynamic_cast<Todo*>( mExistingIncidence ) );
02407       compareIncidences( todo, mExistingIncidence, mMethod );
02408       return !mChanges.isEmpty();
02409     }
02410     bool visit( Journal *journal )
02411     {
02412       compareIncidences( journal, mExistingIncidence, mMethod );
02413       return !mChanges.isEmpty();
02414     }
02415     bool visit( FreeBusy *fb )
02416     {
02417       Q_UNUSED( fb );
02418       return !mChanges.isEmpty();
02419     }
02420 
02421   private:
02422     void compareEvents( Event *newEvent, Event *oldEvent )
02423     {
02424       if ( !oldEvent || !newEvent )
02425         return;
02426       if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->doesFloat() != newEvent->doesFloat() )
02427         mChanges += i18n( "The invitation starting time has been changed from %1 to %2" )
02428                     .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) );
02429       if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->doesFloat() != newEvent->doesFloat() )
02430         mChanges += i18n( "The invitation ending time has been changed from %1 to %2" )
02431                     .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) );
02432     }
02433 
02434     void compareTodos( Todo *newTodo, Todo *oldTodo )
02435     {
02436       if ( !oldTodo || !newTodo ) {
02437         return;
02438       }
02439 
02440       if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) {
02441         mChanges += i18n( "The task has been completed" );
02442       }
02443       if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) {
02444         mChanges += i18n( "The task is no longer completed" );
02445       }
02446       if ( oldTodo->percentComplete() != newTodo->percentComplete() ) {
02447         const QString oldPer = i18n( "%1%" ).arg( oldTodo->percentComplete() );
02448         const QString newPer = i18n( "%1%" ).arg( newTodo->percentComplete() );
02449         mChanges += i18n( "The task completed percentage has changed from %1 to %2" ).
02450                     arg( oldPer, newPer );
02451       }
02452 
02453       if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) {
02454         mChanges += i18n( "A task starting time has been added" );
02455       }
02456       if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) {
02457         mChanges += i18n( "The task starting time has been removed" );
02458       }
02459       if ( oldTodo->hasStartDate() && newTodo->hasStartDate() &&
02460            oldTodo->dtStart() != newTodo->dtStart() ) {
02461         mChanges += i18n( "The task starting time has been changed from %1 to %2" ).
02462                     arg( dateTimeToString( oldTodo->dtStart(), oldTodo->doesFloat(), false ),
02463                          dateTimeToString( newTodo->dtStart(), newTodo->doesFloat(), false ) );
02464       }
02465 
02466       if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) {
02467         mChanges += i18n( "A task due time has been added" );
02468       }
02469       if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) {
02470         mChanges += i18n( "The task due time has been removed" );
02471       }
02472       if ( oldTodo->hasDueDate() && newTodo->hasDueDate() &&
02473            oldTodo->dtDue() != newTodo->dtDue() ) {
02474         mChanges += i18n( "The task due time has been changed from %1 to %2" ).
02475                     arg( dateTimeToString( oldTodo->dtDue(), oldTodo->doesFloat(), false ),
02476                          dateTimeToString( newTodo->dtDue(), newTodo->doesFloat(), false ) );
02477       }
02478     }
02479 
02480     void compareIncidences( Incidence *newInc, Incidence *oldInc, int method )
02481     {
02482       if ( !oldInc || !newInc )
02483         return;
02484       if ( oldInc->summary() != newInc->summary() )
02485         mChanges += i18n( "The summary has been changed to: \"%1\"" ).arg( newInc->summary() );
02486       if ( oldInc->location() != newInc->location() )
02487         mChanges += i18n( "The location has been changed to: \"%1\"" ).arg( newInc->location() );
02488       if ( oldInc->description() != newInc->description() )
02489         mChanges += i18n( "The description has been changed to: \"%1\"" ).arg( newInc->description() );
02490       Attendee::List oldAttendees = oldInc->attendees();
02491       Attendee::List newAttendees = newInc->attendees();
02492       for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
02493             it != newAttendees.constEnd(); ++it ) {
02494         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
02495         if ( !oldAtt ) {
02496           mChanges += i18n( "Attendee %1 has been added" ).arg( (*it)->fullName() );
02497         } else {
02498           if ( oldAtt->status() != (*it)->status() )
02499             mChanges += i18n( "The status of attendee %1 has been changed to: %2" ).
02500                         arg( (*it)->fullName() ).arg( (*it)->statusStr() );
02501         }
02502       }
02503       if ( method == Scheduler::Request ) {
02504         for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
02505               it != oldAttendees.constEnd(); ++it ) {
02506           if ( (*it)->email() != oldInc->organizer().email() ) {
02507             Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
02508             if ( !newAtt ) {
02509               mChanges += i18n( "Attendee %1 has been removed" ).arg( (*it)->fullName() );
02510             }
02511           }
02512         }
02513       }
02514     }
02515 
02516   private:
02517     Incidence *mExistingIncidence;
02518     int mMethod;
02519     QStringList mChanges;
02520 };
02521 
02522 
02523 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
02524 {
02525   if ( !id.startsWith( "ATTACH:" ) ) {
02526     QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
02527                   arg( generateLinkURL( id ), text );
02528     return res;
02529   } else {
02530     // draw the attachment links in non-bold face
02531     QString res = QString( "<a href=\"%1\">%2</a>" ).
02532                   arg( generateLinkURL( id ), text );
02533     return res;
02534   }
02535 }
02536 
02537 // Check if the given incidence is likely one that we own instead one from
02538 // a shared calendar (Kolab-specific)
02539 static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence )
02540 {
02541   CalendarResources *cal = dynamic_cast<CalendarResources*>( calendar );
02542   if ( !cal || !incidence ) {
02543     return true;
02544   }
02545   ResourceCalendar *res = cal->resource( incidence );
02546   if ( !res ) {
02547     return true;
02548   }
02549   const QString subRes = res->subresourceIdentifier( incidence );
02550   if ( !subRes.contains( "/.INBOX.directory/" ) ) {
02551     return false;
02552   }
02553   return true;
02554 }
02555 
02556 static QString responseButtons( Incidence *inc, bool rsvpReq, bool rsvpRec,
02557                                 InvitationFormatterHelper *helper )
02558 {
02559   // The spacer for the invitation buttons
02560   const QString spacer = "<td> &nbsp; </td>";
02561   // The open & close table cell tags for the invitation buttons
02562   const QString tdOpen = "<td>";
02563   const QString tdClose = "</td>" + spacer;
02564 
02565   QString html;
02566   if ( !helper ) {
02567     return html;
02568   }
02569 
02570   if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) {
02571     // Record only
02572     html += tdOpen;
02573     html += helper->makeLink( "record", i18n( "[Record]" ) );
02574     html += tdClose;
02575 
02576     // Move to trash
02577     html += tdOpen;
02578     html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) );
02579     html += tdClose;
02580 
02581   } else {
02582 
02583     // Accept
02584     html += tdOpen;
02585     html += helper->makeLink( "accept", i18n( "[Accept]" ) );
02586     html += tdClose;
02587 
02588     // Tentative
02589     html += tdOpen;
02590     html += helper->makeLink( "accept_conditionally",
02591                               i18n( "Accept conditionally", "[Accept cond.]" ) );
02592     html += tdClose;
02593 
02594     // Counter proposal
02595     html += tdOpen;
02596     html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
02597     html += tdClose;
02598 
02599     // Decline
02600     html += tdOpen;
02601     html += helper->makeLink( "decline", i18n( "[Decline]" ) );
02602     html += tdClose;
02603   }
02604 
02605   if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
02606     // Delegate
02607     html += tdOpen;
02608     html += helper->makeLink( "delegate", i18n( "[Delegate]" ) );
02609     html += tdClose;
02610 
02611     // Forward
02612     html += tdOpen;
02613     html += helper->makeLink( "forward", i18n( "[Forward]" ) );
02614     html += tdClose;
02615 
02616     // Check calendar
02617     if ( inc && inc->type() == "Event" ) {
02618       html += tdOpen;
02619       html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
02620       html += tdClose;
02621     }
02622   }
02623   return html;
02624 }
02625 
02626 static QString counterButtons( Incidence *incidence,
02627                                InvitationFormatterHelper *helper )
02628 {
02629   // The spacer for the invitation buttons
02630   const QString spacer = "<td> &nbsp; </td>";
02631   // The open & close table cell tags for the invitation buttons
02632   const QString tdOpen = "<td>";
02633   const QString tdClose = "</td>" + spacer;
02634 
02635   QString html;
02636   if ( !helper ) {
02637     return html;
02638   }
02639 
02640   // Accept proposal
02641   html += tdOpen;
02642   html += helper->makeLink( "accept_counter", i18n("[Accept]") );
02643   html += tdClose;
02644 
02645   // Decline proposal
02646   html += tdOpen;
02647   html += helper->makeLink( "decline_counter", i18n("[Decline]") );
02648   html += tdClose;
02649 
02650   // Check calendar
02651   if ( incidence && incidence->type() == "Event" ) {
02652     html += tdOpen;
02653     html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
02654     html += tdClose;
02655   }
02656   return html;
02657 }
02658 
02659 QString IncidenceFormatter::formatICalInvitationHelper( QString invitation,
02660                                                         Calendar *mCalendar,
02661                                                         InvitationFormatterHelper *helper,
02662                                                         bool noHtmlMode,
02663                                                         const QString &sender,
02664                                                         bool outlookCompareStyle )
02665 {
02666   if ( invitation.isEmpty() ) {
02667     return QString::null;
02668   }
02669 
02670   ICalFormat format;
02671   // parseScheduleMessage takes the tz from the calendar, no need to set it manually here for the format!
02672   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
02673 
02674   if( !msg ) {
02675     kdDebug( 5850 ) << "Failed to parse the scheduling message" << endl;
02676     Q_ASSERT( format.exception() );
02677     kdDebug( 5850 ) << format.exception()->message() << endl;
02678     return QString::null;
02679   }
02680 
02681   IncidenceBase *incBase = msg->event();
02682 
02683   // Determine if this incidence is in my calendar (and owned by me)
02684   Incidence *existingIncidence = 0;
02685   if ( incBase && helper->calendar() ) {
02686     existingIncidence = helper->calendar()->incidence( incBase->uid() );
02687     if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
02688       existingIncidence = 0;
02689     }
02690     if ( !existingIncidence ) {
02691       const Incidence::List list = helper->calendar()->incidences();
02692       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
02693         if ( (*it)->schedulingID() == incBase->uid() &&
02694              incidenceOwnedByMe( helper->calendar(), *it ) ) {
02695           existingIncidence = *it;
02696           break;
02697         }
02698       }
02699     }
02700   }
02701 
02702   // First make the text of the message
02703   QString html;
02704 
02705   QString tableStyle = QString::fromLatin1(
02706     "style=\"border: solid 1px; margin: 0em;\"" );
02707   QString tableHead = QString::fromLatin1(
02708     "<div align=\"center\">"
02709     "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
02710     "<tr><td>").arg(tableStyle);
02711 
02712   html += tableHead;
02713   InvitationHeaderVisitor headerVisitor;
02714   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
02715   if ( !headerVisitor.act( incBase, existingIncidence, msg, sender ) ) {
02716     return QString::null;
02717   }
02718   html += "<b>" + headerVisitor.result() + "</b>";
02719 
02720   if ( outlookCompareStyle ) {
02721     // use the Outlook 2007 Comparison Style
02722     InvitationBodyVisitor bodyVisitor( noHtmlMode );
02723     if ( msg->method() == Scheduler::Request || msg->method() == Scheduler::Reply ) {
02724       if ( !bodyVisitor.act( incBase, existingIncidence, msg, sender ) ) {
02725         return QString::null;
02726       }
02727     } else {
02728       if ( !bodyVisitor.act( incBase, 0, msg, sender ) ) {
02729         return QString::null;
02730       }
02731     }
02732     html += bodyVisitor.result();
02733   } else {
02734     // use our "Classic: Comparison Style
02735     InvitationBodyVisitor bodyVisitor( noHtmlMode );
02736     if ( !bodyVisitor.act( incBase, 0, msg, sender ) )
02737       return QString::null;
02738     html += bodyVisitor.result();
02739 
02740     if ( msg->method() == Scheduler::Request ) {
02741       IncidenceCompareVisitor compareVisitor;
02742       if ( compareVisitor.act( incBase, existingIncidence, msg->method() ) ) {
02743         html += "<p align=\"left\">";
02744         html += i18n( "The following changes have been made by the organizer:" );
02745         html += "</p>";
02746         html += compareVisitor.result();
02747       }
02748     }
02749     if ( msg->method() == Scheduler::Reply ) {
02750       IncidenceCompareVisitor compareVisitor;
02751       if ( compareVisitor.act( incBase, existingIncidence, msg->method() ) ) {
02752         html += "<p align=\"left\">";
02753         if ( !sender.isEmpty() ) {
02754           html += i18n( "The following changes have been made by %1:" ).arg( sender );
02755         } else {
02756           html += i18n( "The following changes have been made by an attendee:" );
02757         }
02758         html += "</p>";
02759         html += compareVisitor.result();
02760       }
02761     }
02762   }
02763 
02764   Incidence *inc = dynamic_cast<Incidence*>( incBase );
02765 
02766   // determine if I am the organizer for this invitation
02767   bool myInc = iamOrganizer( inc );
02768 
02769   // determine if the invitation response has already been recorded
02770   bool rsvpRec = false;
02771   Attendee *ea = 0;
02772   if ( !myInc ) {
02773     Incidence *rsvpIncidence = existingIncidence;
02774     if ( !rsvpIncidence && inc && inc->revision() > 0 ) {
02775       rsvpIncidence = inc;
02776     }
02777     if ( rsvpIncidence ) {
02778       ea = findMyAttendee( rsvpIncidence );
02779     }
02780     if ( ea &&
02781          ( ea->status() == Attendee::Accepted ||
02782            ea->status() == Attendee::Declined ||
02783            ea->status() == Attendee::Tentative ) ) {
02784       rsvpRec = true;
02785     }
02786   }
02787 
02788   // determine invitation role
02789   QString role;
02790   bool isDelegated = false;
02791   Attendee *a = findMyAttendee( inc );
02792   if ( !a && inc ) {
02793     if ( !inc->attendees().isEmpty() ) {
02794       a = inc->attendees().first();
02795     }
02796   }
02797   if ( a ) {
02798     isDelegated = ( a->status() == Attendee::Delegated );
02799     role = Attendee::roleName( a->role() );
02800   }
02801 
02802   // determine if RSVP needed, not-needed, or response already recorded
02803   bool rsvpReq = rsvpRequested( inc );
02804   if ( !myInc && a ) {
02805     html += "<br/>";
02806     html += "<i><u>";
02807     if ( rsvpRec && inc ) {
02808       if ( inc->revision() == 0 ) {
02809         html += i18n( "Your <b>%1</b> response has been recorded" ).
02810                 arg( ea->statusStr() );
02811       } else {
02812         html += i18n( "Your status for this invitation is <b>%1</b>" ).
02813                 arg( ea->statusStr() );
02814       }
02815       rsvpReq = false;
02816     } else if ( msg->method() == Scheduler::Cancel ) {
02817       html += i18n( "This invitation was canceled" );
02818     } else if ( msg->method() == Scheduler::Add ) {
02819       html += i18n( "This invitation was accepted" );
02820     } else {
02821       if ( !isDelegated ) {
02822         html += rsvpRequestedStr( rsvpReq, role );
02823       } else {
02824         html += i18n( "Awaiting delegation response" );
02825       }
02826     }
02827     html += "</u></i>";
02828   }
02829 
02830   // Print if the organizer gave you a preset status
02831   if ( !myInc ) {
02832     if ( inc && inc->revision() == 0 ) {
02833       QString statStr = myStatusStr( inc );
02834       if ( !statStr.isEmpty() ) {
02835         html += "<br/>";
02836         html += "<i>";
02837         html += statStr;
02838         html += "</i>";
02839       }
02840     }
02841   }
02842 
02843   // Add groupware links
02844 
02845   html += "<br><table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td></tr>";
02846 
02847   switch ( msg->method() ) {
02848     case Scheduler::Publish:
02849     case Scheduler::Request:
02850     case Scheduler::Refresh:
02851     case Scheduler::Add:
02852     {
02853       if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
02854         html += "<tr>";
02855         if ( inc->type() == "Todo" ) {
02856           html += "<td colspan=\"9\">";
02857           html += helper->makeLink( "reply", i18n( "[Record invitation in my task list]" ) );
02858         } else {
02859           html += "<td colspan=\"13\">";
02860           html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) );
02861         }
02862         html += "</td></tr>";
02863       }
02864 
02865       if ( !myInc && a ) {
02866         html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
02867       }
02868       break;
02869     }
02870 
02871     case Scheduler::Cancel:
02872       // Remove invitation
02873       if ( inc ) {
02874         html += "<tr>";
02875         if ( inc->type() == "Todo" ) {
02876           html += "<td colspan=\"9\">";
02877           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my task list]" ) );
02878         } else {
02879           html += "<td colspan=\"13\">";
02880           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my calendar]" ) );
02881         }
02882         html += "</td></tr>";
02883       }
02884       break;
02885 
02886     case Scheduler::Reply:
02887     {
02888       // Record invitation response
02889       Attendee *a = 0;
02890       Attendee *ea = 0;
02891       if ( inc ) {
02892         // First, determine if this reply is really a counter in disguise.
02893         if ( replyMeansCounter( inc ) ) {
02894           html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
02895           break;
02896         }
02897 
02898         // Next, maybe this is a declined reply that was delegated from me?
02899         // find first attendee who is delegated-from me
02900         // look a their PARTSTAT response, if the response is declined,
02901         // then we need to start over which means putting all the action
02902         // buttons and NOT putting on the [Record response..] button
02903         a = findDelegatedFromMyAttendee( inc );
02904         if ( a ) {
02905           if ( a->status() != Attendee::Accepted ||
02906                a->status() != Attendee::Tentative ) {
02907             html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
02908             break;
02909           }
02910         }
02911 
02912         // Finally, simply allow a Record of the reply
02913         if ( !inc->attendees().isEmpty() ) {
02914           a = inc->attendees().first();
02915         }
02916         if ( a ) {
02917           ea = findAttendee( existingIncidence, a->email() );
02918         }
02919       }
02920       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
02921         if ( inc && inc->revision() > 0 ) {
02922           html += "<br><u><i>";
02923           html += i18n( "The <b>%1</b> response has been recorded" ).arg( ea->statusStr() );
02924           html += "</i></u>";
02925         }
02926       } else {
02927         if ( inc ) {
02928           html += "<tr><td>";
02929           if ( inc->type() == "Todo" ) {
02930             html += helper->makeLink( "reply", i18n( "[Record response in my task list]" ) );
02931           } else {
02932             html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
02933           }
02934           html += "</td></tr>";
02935         }
02936       }
02937       break;
02938     }
02939 
02940     case Scheduler::Counter:
02941       // Counter proposal
02942       html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
02943       break;
02944 
02945     case Scheduler::Declinecounter:
02946     case Scheduler::NoMethod:
02947       break;
02948   }
02949 
02950   // close the groupware table
02951   html += "</td></tr></table>";
02952 
02953   // Add the attendee list if I am the organizer
02954   if ( myInc && helper->calendar() ) {
02955     html += invitationAttendees( helper->calendar()->incidence( inc->uid() ), a );
02956   }
02957 
02958   // close the top-level table
02959   html += "</td></tr></table><br></div>";
02960 
02961   // Add the attachment list
02962   html += invitationAttachments( helper, inc );
02963 
02964   return html;
02965 }
02966 
02967 QString IncidenceFormatter::formatICalInvitation( QString invitation,
02968                                                   Calendar *mCalendar,
02969                                                   InvitationFormatterHelper *helper )
02970 {
02971   return formatICalInvitationHelper( invitation, mCalendar, helper, false, QString(), true );
02972 }
02973 
02974 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
02975                                                         Calendar *mCalendar,
02976                                                         InvitationFormatterHelper *helper )
02977 {
02978   return formatICalInvitationHelper( invitation, mCalendar, helper, true, QString(), true );
02979 }
02980 
02981 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
02982                                                         Calendar *mCalendar,
02983                                                         InvitationFormatterHelper *helper,
02984                                                         const QString &sender )
02985 {
02986   return formatICalInvitationHelper( invitation, mCalendar, helper, true, sender, true );
02987 }
02988 
02989 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
02990                                                         Calendar *mCalendar,
02991                                                         InvitationFormatterHelper *helper,
02992                                                         const QString &sender,
02993                                                         bool outlookCompareStyle )
02994 {
02995   return formatICalInvitationHelper( invitation, mCalendar, helper, true, sender, outlookCompareStyle );
02996 }
02997 
02998 /*******************************************************************
02999  *  Helper functions for the msTNEF -> VPart converter
03000  *******************************************************************/
03001 
03002 
03003 //-----------------------------------------------------------------------------
03004 
03005 static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
03006                            const QString& fallback = QString::null)
03007 {
03008   return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
03009                             fallback );
03010 }
03011 
03012 static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
03013                            const QString& fallback = QString::null )
03014 {
03015   return tnefMsg->findNamedProp( name, fallback );
03016 }
03017 
03018 struct save_tz { char* old_tz; char* tz_env_str; };
03019 
03020 /* temporarily go to a different timezone */
03021 static struct save_tz set_tz( const char* _tc )
03022 {
03023   const char *tc = _tc?_tc:"UTC";
03024 
03025   struct save_tz rv;
03026 
03027   rv.old_tz = 0;
03028   rv.tz_env_str = 0;
03029 
03030   //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;
03031 
03032   char* tz_env = 0;
03033   if( getenv( "TZ" ) ) {
03034     tz_env = strdup( getenv( "TZ" ) );
03035     rv.old_tz = tz_env;
03036   }
03037   char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
03038   strcpy( tmp_env, "TZ=" );
03039   strcpy( tmp_env+3, tc );
03040   putenv( tmp_env );
03041 
03042   rv.tz_env_str = tmp_env;
03043 
03044   /* tmp_env is not free'ed -- it is part of the environment */
03045 
03046   tzset();
03047   //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;
03048 
03049   return rv;
03050 }
03051 
03052 /* restore previous timezone */
03053 static void unset_tz( struct save_tz old_tz )
03054 {
03055   if( old_tz.old_tz ) {
03056     char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
03057     strcpy( tmp_env, "TZ=" );
03058     strcpy( tmp_env+3, old_tz.old_tz );
03059     putenv( tmp_env );
03060     /* tmp_env is not free'ed -- it is part of the environment */
03061     free( old_tz.old_tz );
03062   } else {
03063     /* clear TZ from env */
03064     putenv( strdup("TZ") );
03065   }
03066   tzset();
03067 
03068   /* is this OK? */
03069   if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
03070 }
03071 
03072 static QDateTime utc2Local( const QDateTime& utcdt )
03073 {
03074   struct tm tmL;
03075 
03076   save_tz tmp_tz = set_tz("UTC");
03077   time_t utc = utcdt.toTime_t();
03078   unset_tz( tmp_tz );
03079 
03080   localtime_r( &utc, &tmL );
03081   return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
03082                     QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
03083 }
03084 
03085 
03086 static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
03087                                           bool bDateOnly = false )
03088 {
03089   QDate tmpDate;
03090   QTime tmpTime;
03091   int year, month, day, hour, minute, second;
03092 
03093   if( bDateOnly ) {
03094     year = dtStr.left( 4 ).toInt();
03095     month = dtStr.mid( 4, 2 ).toInt();
03096     day = dtStr.mid( 6, 2 ).toInt();
03097     hour = 0;
03098     minute = 0;
03099     second = 0;
03100   } else {
03101     year = dtStr.left( 4 ).toInt();
03102     month = dtStr.mid( 4, 2 ).toInt();
03103     day = dtStr.mid( 6, 2 ).toInt();
03104     hour = dtStr.mid( 9, 2 ).toInt();
03105     minute = dtStr.mid( 11, 2 ).toInt();
03106     second = dtStr.mid( 13, 2 ).toInt();
03107   }
03108   tmpDate.setYMD( year, month, day );
03109   tmpTime.setHMS( hour, minute, second );
03110 
03111   if( tmpDate.isValid() && tmpTime.isValid() ) {
03112     QDateTime dT = QDateTime( tmpDate, tmpTime );
03113 
03114     if( !bDateOnly ) {
03115       // correct for GMT ( == Zulu time == UTC )
03116       if (dtStr.at(dtStr.length()-1) == 'Z') {
03117         //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
03118         //localUTCOffset( dT ) );
03119         dT = utc2Local( dT );
03120       }
03121     }
03122     return dT;
03123   } else
03124     return QDateTime();
03125 }
03126 
03127 
03128 
03129 QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef )
03130 {
03131   bool bOk = false;
03132 
03133   KTNEFParser parser;
03134   QBuffer buf( tnef );
03135   CalendarLocal cal ( QString::fromLatin1( "UTC" ) );
03136   KABC::Addressee addressee;
03137   KABC::VCardConverter cardConv;
03138   ICalFormat calFormat;
03139   Event* event = new Event();
03140 
03141   if( parser.openDevice( &buf ) ) {
03142     KTNEFMessage* tnefMsg = parser.message();
03143     //QMap<int,KTNEFProperty*> props = parser.message()->properties();
03144 
03145     // Everything depends from property PR_MESSAGE_CLASS
03146     // (this is added by KTNEFParser):
03147     QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
03148       .upper();
03149     if( !msgClass.isEmpty() ) {
03150       // Match the old class names that might be used by Outlook for
03151       // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
03152       bool bCompatClassAppointment = false;
03153       bool bCompatMethodRequest = false;
03154       bool bCompatMethodCancled = false;
03155       bool bCompatMethodAccepted = false;
03156       bool bCompatMethodAcceptedCond = false;
03157       bool bCompatMethodDeclined = false;
03158       if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
03159         bCompatClassAppointment = true;
03160         if( msgClass.endsWith( ".MTGREQ" ) )
03161           bCompatMethodRequest = true;
03162         if( msgClass.endsWith( ".MTGCNCL" ) )
03163           bCompatMethodCancled = true;
03164         if( msgClass.endsWith( ".MTGRESPP" ) )
03165           bCompatMethodAccepted = true;
03166         if( msgClass.endsWith( ".MTGRESPA" ) )
03167           bCompatMethodAcceptedCond = true;
03168         if( msgClass.endsWith( ".MTGRESPN" ) )
03169           bCompatMethodDeclined = true;
03170       }
03171       bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );
03172 
03173       if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
03174         // Compose a vCal
03175         bool bIsReply = false;
03176         QString prodID = "-//Microsoft Corporation//Outlook ";
03177         prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
03178         prodID += "MIMEDIR/EN\n";
03179         prodID += "VERSION:2.0\n";
03180         calFormat.setApplication( "Outlook", prodID );
03181 
03182         Scheduler::Method method;
03183         if( bCompatMethodRequest )
03184           method = Scheduler::Request;
03185         else if( bCompatMethodCancled )
03186           method = Scheduler::Cancel;
03187         else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
03188                  bCompatMethodDeclined ) {
03189           method = Scheduler::Reply;
03190           bIsReply = true;
03191         } else {
03192           // pending(khz): verify whether "0x0c17" is the right tag ???
03193           //
03194           // at the moment we think there are REQUESTS and UPDATES
03195           //
03196           // but WHAT ABOUT REPLIES ???
03197           //
03198           //
03199 
03200           if( tnefMsg->findProp(0x0c17) == "1" )
03201             bIsReply = true;
03202           method = Scheduler::Request;
03203         }
03204 
03206         ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );
03207 
03208         QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );
03209 
03210         if( !sSenderSearchKeyEmail.isEmpty() ) {
03211           int colon = sSenderSearchKeyEmail.find( ':' );
03212           // May be e.g. "SMTP:KHZ@KDE.ORG"
03213           if( sSenderSearchKeyEmail.find( ':' ) == -1 )
03214             sSenderSearchKeyEmail.remove( 0, colon+1 );
03215         }
03216 
03217         QString s( tnefMsg->findProp( 0x0e04 ) );
03218         QStringList attendees = QStringList::split( ';', s );
03219         if( attendees.count() ) {
03220           for( QStringList::Iterator it = attendees.begin();
03221                it != attendees.end(); ++it ) {
03222             // Skip all entries that have no '@' since these are
03223             // no mail addresses
03224             if( (*it).find('@') == -1 ) {
03225               s = (*it).stripWhiteSpace();
03226 
03227               Attendee *attendee = new Attendee( s, s, true );
03228               if( bIsReply ) {
03229                 if( bCompatMethodAccepted )
03230                   attendee->setStatus( Attendee::Accepted );
03231                 if( bCompatMethodDeclined )
03232                   attendee->setStatus( Attendee::Declined );
03233                 if( bCompatMethodAcceptedCond )
03234                   attendee->setStatus(Attendee::Tentative);
03235               } else {
03236                 attendee->setStatus( Attendee::NeedsAction );
03237                 attendee->setRole( Attendee::ReqParticipant );
03238               }
03239               event->addAttendee(attendee);
03240             }
03241           }
03242         } else {
03243           // Oops, no attendees?
03244           // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
03245           s = sSenderSearchKeyEmail;
03246           if( !s.isEmpty() ) {
03247             Attendee *attendee = new Attendee( QString::null, QString::null,
03248                                                true );
03249             if( bIsReply ) {
03250               if( bCompatMethodAccepted )
03251                 attendee->setStatus( Attendee::Accepted );
03252               if( bCompatMethodAcceptedCond )
03253                 attendee->setStatus( Attendee::Declined );
03254               if( bCompatMethodDeclined )
03255                 attendee->setStatus( Attendee::Tentative );
03256             } else {
03257               attendee->setStatus(Attendee::NeedsAction);
03258               attendee->setRole(Attendee::ReqParticipant);
03259             }
03260             event->addAttendee(attendee);
03261           }
03262         }
03263         s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
03264         if( s.isEmpty() && !bIsReply )
03265           s = sSenderSearchKeyEmail;
03266         // TODO: Use the common name?
03267         if( !s.isEmpty() )
03268           event->setOrganizer( s );
03269 
03270         s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
03271           .replace( QChar( ':' ), QString::null );
03272         event->setDtStart( QDateTime::fromString( s ) ); // ## Format??
03273 
03274         s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
03275           .replace( QChar( ':' ), QString::null );
03276         event->setDtEnd( QDateTime::fromString( s ) );
03277 
03278         s = tnefMsg->findProp( 0x8208 );
03279         event->setLocation( s );
03280 
03281         // is it OK to set this to OPAQUE always ??
03282         //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
03283         //vPart += "SEQUENCE:0\n";
03284 
03285         // is "0x0023" OK  -  or should we look for "0x0003" ??
03286         s = tnefMsg->findProp( 0x0023 );
03287         event->setUid( s );
03288 
03289         // PENDING(khz): is this value in local timezone? Must it be
03290         // adjusted? Most likely this is a bug in the server or in
03291         // Outlook - we ignore it for now.
03292         s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
03293           .replace( QChar( ':' ), QString::null );
03294         // ### libkcal always uses currentDateTime()
03295         // event->setDtStamp(QDateTime::fromString(s));
03296 
03297         s = tnefMsg->findNamedProp( "Keywords" );
03298         event->setCategories( s );
03299 
03300         s = tnefMsg->findProp( 0x1000 );
03301         event->setDescription( s );
03302 
03303         s = tnefMsg->findProp( 0x0070 );
03304         event->setSummary( s );
03305 
03306         s = tnefMsg->findProp( 0x0026 );
03307         event->setPriority( s.toInt() );
03308 
03309         // is reminder flag set ?
03310         if(!tnefMsg->findProp(0x8503).isEmpty()) {
03311           Alarm *alarm = new Alarm(event);
03312           QDateTime highNoonTime =
03313             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
03314                                      .replace( QChar( '-' ), "" )
03315                                      .replace( QChar( ':' ), "" ) );
03316           QDateTime wakeMeUpTime =
03317             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
03318                                      .replace( QChar( '-' ), "" )
03319                                      .replace( QChar( ':' ), "" ) );
03320           alarm->setTime(wakeMeUpTime);
03321 
03322           if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
03323             alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
03324           else
03325             // default: wake them up 15 minutes before the appointment
03326             alarm->setStartOffset( Duration( 15*60 ) );
03327           alarm->setDisplayAlarm( i18n( "Reminder" ) );
03328 
03329           // Sorry: the different action types are not known (yet)
03330           //        so we always set 'DISPLAY' (no sounds, no images...)
03331           event->addAlarm( alarm );
03332         }
03333         cal.addEvent( event );
03334         bOk = true;
03335         // we finished composing a vCal
03336       } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
03337         addressee.setUid( stringProp( tnefMsg, attMSGID ) );
03338         addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) );
03339         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
03340         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
03341         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
03342         addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) );
03343         addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) );
03344         addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
03345         addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
03346         addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) );
03347         addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) );
03348         addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) );
03349 
03350         QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
03351           .replace( QChar( '-' ), QString::null )
03352           .replace( QChar( ':' ), QString::null );
03353         if( !s.isEmpty() )
03354           addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );
03355 
03356         addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );
03357 
03358         // collect parts of Name entry
03359         addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
03360         addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
03361         addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) );
03362         addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) );
03363         addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );
03364 
03365         addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
03366         addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
03367         addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
03368         /*
03369         the MAPI property ID of this (multiline) )field is unknown:
03370         vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
03371         */
03372 
03373         KABC::Address adr;
03374         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) );
03375         adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
03376         adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
03377         adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
03378         adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) );
03379         adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
03380         adr.setType(KABC::Address::Home);
03381         addressee.insertAddress(adr);
03382 
03383         adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
03384         adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) );
03385         adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) );
03386         adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) );
03387         adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
03388         adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) );
03389         adr.setType( KABC::Address::Work );
03390         addressee.insertAddress( adr );
03391 
03392         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) );
03393         adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
03394         adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
03395         adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
03396         adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) );
03397         adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
03398         adr.setType( KABC::Address::Dom );
03399         addressee.insertAddress(adr);
03400 
03401         // problem: the 'other' address was stored by KOrganizer in
03402         //          a line looking like the following one:
03403         // 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
03404 
03405         QString nr;
03406         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
03407         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) );
03408         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
03409         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) );
03410         nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
03411         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) );
03412         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
03413         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) );
03414         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
03415         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) );
03416 
03417         s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
03418           .replace( QChar( '-' ), QString::null )
03419           .replace( QChar( ':' ), QString::null );
03420         if( !s.isEmpty() )
03421           addressee.setBirthday( QDateTime::fromString( s ) );
03422 
03423         bOk = ( !addressee.isEmpty() );
03424       } else if( "IPM.NOTE" == msgClass ) {
03425 
03426       } // else if ... and so on ...
03427     }
03428   }
03429 
03430   // Compose return string
03431   QString iCal = calFormat.toString( &cal );
03432   if( !iCal.isEmpty() )
03433     // This was an iCal
03434     return iCal;
03435 
03436   // Not an iCal - try a vCard
03437   KABC::VCardConverter converter;
03438   return converter.createVCard( addressee );
03439 }
03440 
03441 
03442 QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef,
03443         Calendar *mCalendar, InvitationFormatterHelper *helper )
03444 {
03445   QString vPart = IncidenceFormatter::msTNEFToVPart( tnef );
03446   QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper );
03447   if( !iCal.isEmpty() )
03448     return iCal;
03449   return vPart;
03450 }
03451 
03452 
03453 
03454 
03455 /*******************************************************************
03456  *  Helper functions for the Incidence tooltips
03457  *******************************************************************/
03458 
03459 class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
03460 {
03461   public:
03462     ToolTipVisitor()
03463       : mCalendar( 0 ), mRichText( true ), mResult( "" ) {}
03464 
03465     bool act( Calendar *calendar, IncidenceBase *incidence,
03466               const QDate &date=QDate(), bool richText=true )
03467     {
03468       mCalendar = calendar;
03469       mDate = date;
03470       mRichText = richText;
03471       mResult = "";
03472       return incidence ? incidence->accept( *this ) : false;
03473     }
03474     QString result() const { return mResult; }
03475 
03476   protected:
03477     bool visit( Event *event );
03478     bool visit( Todo *todo );
03479     bool visit( Journal *journal );
03480     bool visit( FreeBusy *fb );
03481 
03482     QString dateRangeText( Event *event, const QDate &date );
03483     QString dateRangeText( Todo *todo, const QDate &date );
03484     QString dateRangeText( Journal *journal );
03485     QString dateRangeText( FreeBusy *fb );
03486 
03487     QString generateToolTip( Incidence* incidence, QString dtRangeText );
03488 
03489   protected:
03490     Calendar *mCalendar;
03491     QDate mDate;
03492     bool mRichText;
03493     QString mResult;
03494 };
03495 
03496 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const QDate &date )
03497 {
03498   QString ret;
03499   QString tmp;
03500 
03501   QDateTime startDt = event->dtStart();
03502   QDateTime endDt = event->dtEnd();
03503   if ( event->doesRecur() ) {
03504     if ( date.isValid() ) {
03505       QDateTime dt( date, QTime( 0, 0, 0 ) );
03506       int diffDays = startDt.daysTo( dt );
03507       dt = dt.addSecs( -1 );
03508       startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() );
03509       if ( event->hasEndDate() ) {
03510         endDt = endDt.addDays( diffDays );
03511         if ( startDt > endDt ) {
03512           startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() );
03513           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
03514         }
03515       }
03516     }
03517   }
03518   if ( event->isMultiDay() ) {
03519 
03520     tmp = "<br>" + i18n("Event start", "<i>From:</i>&nbsp;%1");
03521     if (event->doesFloat())
03522       ret += tmp.arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03523     else
03524       ret += tmp.arg( IncidenceFormatter::dateToString( startDt ).replace(" ", "&nbsp;") );
03525 
03526     tmp = "<br>" + i18n("Event end","<i>To:</i>&nbsp;%1");
03527     if (event->doesFloat())
03528       ret += tmp.arg( IncidenceFormatter::dateToString( endDt, false ).replace(" ", "&nbsp;") );
03529     else
03530       ret += tmp.arg( IncidenceFormatter::dateToString( endDt ).replace(" ", "&nbsp;") );
03531 
03532   } else {
03533 
03534     ret += "<br>"+i18n("<i>Date:</i>&nbsp;%1").
03535            arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03536     if ( !event->doesFloat() ) {
03537       const QString dtStartTime =
03538         IncidenceFormatter::timeToString( startDt, true ).replace( " ", "&nbsp;" );
03539       const QString dtEndTime =
03540         IncidenceFormatter::timeToString( endDt, true ).replace( " ", "&nbsp;" );
03541       if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00'
03542         tmp = "<br>" + i18n("time for event, &nbsp; to prevent ugly line breaks",
03543         "<i>Time:</i>&nbsp;%1").
03544         arg( dtStartTime );
03545       } else {
03546         tmp = "<br>" + i18n("time range for event, &nbsp; to prevent ugly line breaks",
03547         "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2").
03548         arg( dtStartTime, dtEndTime );
03549       }
03550       ret += tmp;
03551     }
03552 
03553   }
03554   return ret;
03555 }
03556 
03557 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const QDate &date )
03558 {
03559   QString ret;
03560   bool floats( todo->doesFloat() );
03561 
03562   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03563     QDateTime startDt = todo->dtStart();
03564     if ( todo->doesRecur() ) {
03565       if ( date.isValid() ) {
03566         startDt.setDate( date );
03567       }
03568     }
03569     ret += "<br>" +
03570            i18n("<i>Start:</i>&nbsp;%1").
03571            arg( IncidenceFormatter::dateTimeToString( startDt, floats, false ).
03572                 replace( " ", "&nbsp;" ) );
03573   }
03574 
03575   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03576     QDateTime dueDt = todo->dtDue();
03577     if ( todo->doesRecur() ) {
03578       if ( date.isValid() ) {
03579         QDateTime dt( date, QTime( 0, 0, 0 ) );
03580         dt = dt.addSecs( -1 );
03581         dueDt.setDate( todo->recurrence()->getNextDateTime( dt ).date() );
03582       }
03583     }
03584     ret += "<br>" +
03585            i18n("<i>Due:</i>&nbsp;%1").
03586            arg( IncidenceFormatter::dateTimeToString( dueDt, floats, false ).
03587                 replace( " ", "&nbsp;" ) );
03588   }
03589 
03590   // Print priority and completed info here, for lack of a better place
03591 
03592   if ( todo->priority() > 0 ) {
03593     ret += "<br>";
03594     ret += "<i>" + i18n( "Priority:" ) + "</i>" + "&nbsp;";
03595     ret += QString::number( todo->priority() );
03596   }
03597 
03598   ret += "<br>";
03599   if ( todo->isCompleted() ) {
03600     ret += "<i>" + i18n( "Completed:" ) + "</i>" + "&nbsp;";
03601     ret += todo->completedStr().replace( " ", "&nbsp;" );
03602   } else {
03603     ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + "&nbsp;";
03604     ret += i18n( "%1%" ).arg( todo->percentComplete() );
03605   }
03606 
03607   return ret;
03608 }
03609 
03610 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal )
03611 {
03612   QString ret;
03613   if (journal->dtStart().isValid() ) {
03614     ret += "<br>" +
03615            i18n("<i>Date:</i>&nbsp;%1").
03616            arg( IncidenceFormatter::dateToString( journal->dtStart(), false ) );
03617   }
03618   return ret;
03619 }
03620 
03621 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
03622 {
03623   QString tmp( "<br>" + i18n("<i>Period start:</i>&nbsp;%1") );
03624   QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) );
03625   tmp = "<br>" + i18n("<i>Period start:</i>&nbsp;%1");
03626   ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) );
03627   return ret;
03628 }
03629 
03630 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
03631 {
03632   mResult = generateToolTip( event, dateRangeText( event, mDate ) );
03633   return !mResult.isEmpty();
03634 }
03635 
03636 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
03637 {
03638   mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
03639   return !mResult.isEmpty();
03640 }
03641 
03642 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
03643 {
03644   mResult = generateToolTip( journal, dateRangeText( journal ) );
03645   return !mResult.isEmpty();
03646 }
03647 
03648 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
03649 {
03650   mResult = "<qt><b>" + i18n("Free/Busy information for %1")
03651         .arg(fb->organizer().fullName()) + "</b>";
03652   mResult += dateRangeText( fb );
03653   mResult += "</qt>";
03654   return !mResult.isEmpty();
03655 }
03656 
03657 static QString tooltipPerson( const QString &email, const QString &name,  Attendee::PartStat status )
03658 {
03659   // Search for a new print name, if needed.
03660   const QString printName = searchName( email, name );
03661 
03662   // Get the icon corresponding to the attendee participation status.
03663   const QString iconPath = attendeeStatusIconPath( status );
03664 
03665   // Make the return string.
03666   QString personString;
03667   if ( !iconPath.isEmpty() ) {
03668     personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
03669   }
03670   personString += i18n( "attendee name (attendee status)", "%1 (%2)" ).
03671                   arg( printName.isEmpty() ? email : printName ).
03672                   arg( Attendee::statusName( status ) );
03673   return personString;
03674 }
03675 
03676 static QString tooltipFormatOrganizer( const QString &email, const QString &name )
03677 {
03678   // Search for a new print name, if needed
03679   const QString printName = searchName( email, name );
03680 
03681   // Get the icon for organizer
03682   const QString iconPath = KGlobal::iconLoader()->iconPath( "organizer", KIcon::Small );
03683 
03684   // Make the return string.
03685   QString personString;
03686   personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
03687   personString += ( printName.isEmpty() ? email : printName );
03688   return personString;
03689 }
03690 
03691 static QString tooltipFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role )
03692 {
03693   const int maxNumAtts = 8; // maximum number of people to print per attendee role
03694   const QString etc = i18n( "elipsis", "..." );
03695 
03696   int i = 0;
03697   QString tmpStr;
03698   Attendee::List::ConstIterator it;
03699   Attendee::List attendees = incidence->attendees();
03700 
03701   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
03702     Attendee *a = *it;
03703     if ( a->role() != role ) {
03704       // skip not this role
03705       continue;
03706     }
03707     if ( a->email() == incidence->organizer().email() ) {
03708       // skip attendee that is also the organizer
03709       continue;
03710     }
03711     if ( i == maxNumAtts ) {
03712       tmpStr += "&nbsp;&nbsp;" + etc;
03713       break;
03714     }
03715     tmpStr += "&nbsp;&nbsp;" + tooltipPerson( a->email(), a->name(), a->status() );
03716     if ( !a->delegator().isEmpty() ) {
03717       tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
03718     }
03719     if ( !a->delegate().isEmpty() ) {
03720       tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
03721     }
03722     tmpStr += "<br>";
03723     i++;
03724   }
03725 
03726   if ( tmpStr.endsWith( "<br>" ) ) {
03727     tmpStr.truncate( tmpStr.length() - 4 );
03728   }
03729   return tmpStr;
03730 }
03731 
03732 static QString tooltipFormatAttendees( Incidence *incidence )
03733 {
03734   QString tmpStr, str;
03735 
03736   // Add organizer link
03737   int attendeeCount = incidence->attendees().count();
03738   if ( attendeeCount > 1 ||
03739        ( attendeeCount == 1 &&
03740          incidence->organizer().email() != incidence->attendees().first()->email() ) ) {
03741     tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "<br>";
03742     tmpStr += "&nbsp;&nbsp;" + tooltipFormatOrganizer( incidence->organizer().email(),
03743                                                        incidence->organizer().name() );
03744   }
03745 
03746   // Add "chair"
03747   str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair );
03748   if ( !str.isEmpty() ) {
03749     tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "<br>";
03750     tmpStr += str;
03751   }
03752 
03753   // Add required participants
03754   str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant );
03755   if ( !str.isEmpty() ) {
03756     tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "<br>";
03757     tmpStr += str;
03758   }
03759 
03760   // Add optional participants
03761   str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant );
03762   if ( !str.isEmpty() ) {
03763     tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "<br>";
03764     tmpStr += str;
03765   }
03766 
03767   // Add observers
03768   str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant );
03769   if ( !str.isEmpty() ) {
03770     tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "<br>";
03771     tmpStr += str;
03772   }
03773 
03774   return tmpStr;
03775 }
03776 
03777 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence, QString dtRangeText )
03778 {
03779   const QString etc = i18n( "elipsis", "..." );
03780   const uint maxDescLen = 120; // maximum description chars to print (before elipsis)
03781 
03782   if ( !incidence ) {
03783     return QString::null;
03784   }
03785 
03786   QString tmp = "<qt>";
03787 
03788   // header
03789   tmp += "<b>" + incidence->summary().replace( "\n", "<br>" ) + "</b>";
03790   //NOTE: using <hr> seems to confuse Qt3 tooltips in some cases so use "-----"
03791   tmp += "<br>----------<br>";
03792 
03793   if ( mCalendar ) {
03794     QString calStr = IncidenceFormatter::resourceString( mCalendar, incidence );
03795     if ( !calStr.isEmpty() ) {
03796       tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + "&nbsp;";
03797       tmp += calStr;
03798     }
03799   }
03800 
03801   tmp += dtRangeText;
03802 
03803   if ( !incidence->location().isEmpty() ) {
03804     tmp += "<br>";
03805     tmp += "<i>" + i18n( "Location:" ) + "</i>" + "&nbsp;";
03806     tmp += incidence->location().replace( "\n", "<br>" );
03807   }
03808 
03809   QString durStr = IncidenceFormatter::durationString( incidence );
03810   if ( !durStr.isEmpty() ) {
03811     tmp += "<br>";
03812     tmp += "<i>" + i18n( "Duration:" ) + "</i>" + "&nbsp;";
03813     tmp += durStr;
03814   }
03815 
03816   if ( incidence->doesRecur() ) {
03817     tmp += "<br>";
03818     tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + "&nbsp;";
03819     tmp += IncidenceFormatter::recurrenceString( incidence );
03820   }
03821 
03822   if ( !incidence->description().isEmpty() ) {
03823     QString desc( incidence->description() );
03824     if ( desc.length() > maxDescLen ) {
03825       desc = desc.left( maxDescLen ) + etc;
03826     }
03827     tmp += "<br>----------<br>";
03828     tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
03829     tmp += desc.replace( "\n", "<br>" );
03830     tmp += "<br>----------";
03831   }
03832 
03833   int reminderCount = incidence->alarms().count();
03834   if ( reminderCount > 0 && incidence->isAlarmEnabled() ) {
03835     tmp += "<br>";
03836     tmp += "<i>" + i18n( "Reminder:", "%n Reminders:", reminderCount ) + "</i>" + "&nbsp;";
03837     tmp += IncidenceFormatter::reminderStringList( incidence ).join( ", " );
03838   }
03839 
03840   tmp += "<br>";
03841   tmp += tooltipFormatAttendees( incidence );
03842 
03843   int categoryCount = incidence->categories().count();
03844   if ( categoryCount > 0 ) {
03845     tmp += "<br>";
03846     tmp += "<i>" + i18n( "Category:", "%n Categories:", categoryCount ) + "</i>" + "&nbsp;";
03847     tmp += incidence->categories().join( ", " );
03848   }
03849 
03850   tmp += "</qt>";
03851   return tmp;
03852 }
03853 
03854 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
03855 {
03856   return toolTipStr( 0, incidence, QDate(), richText );
03857 }
03858 
03859 QString IncidenceFormatter::toolTipStr( Calendar *calendar,
03860                                         IncidenceBase *incidence,
03861                                         const QDate &date,
03862                                         bool richText )
03863 {
03864   ToolTipVisitor v;
03865   if ( v.act( calendar, incidence, date, richText ) ) {
03866     return v.result();
03867   } else {
03868     return QString::null;
03869   }
03870 }
03871 
03872 /*******************************************************************
03873  *  Helper functions for the Incidence tooltips
03874  *******************************************************************/
03875 
03876 class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
03877 {
03878   public:
03879     MailBodyVisitor() : mResult( "" ) {}
03880 
03881     bool act( IncidenceBase *incidence )
03882     {
03883       mResult = "";
03884       return incidence ? incidence->accept( *this ) : false;
03885     }
03886     QString result() const { return mResult; }
03887 
03888   protected:
03889     bool visit( Event *event );
03890     bool visit( Todo *todo );
03891     bool visit( Journal *journal );
03892     bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); }
03893   protected:
03894     QString mResult;
03895 };
03896 
03897 
03898 static QString mailBodyIncidence( Incidence *incidence )
03899 {
03900   QString body;
03901   if ( !incidence->summary().isEmpty() ) {
03902     body += i18n("Summary: %1\n").arg( incidence->summary() );
03903   }
03904   if ( !incidence->organizer().isEmpty() ) {
03905     body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() );
03906   }
03907   if ( !incidence->location().isEmpty() ) {
03908     body += i18n("Location: %1\n").arg( incidence->location() );
03909   }
03910   return body;
03911 }
03912 
03913 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
03914 {
03915   QString recurrence[]= {i18n("no recurrence", "None"),
03916     i18n("Minutely"), i18n("Hourly"), i18n("Daily"),
03917     i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"),
03918     i18n("Yearly"), i18n("Yearly"), i18n("Yearly")};
03919 
03920   mResult = mailBodyIncidence( event );
03921   mResult += i18n("Start Date: %1\n").
03922              arg( IncidenceFormatter::dateToString( event->dtStart(), true ) );
03923   if ( !event->doesFloat() ) {
03924     mResult += i18n("Start Time: %1\n").
03925                arg( IncidenceFormatter::timeToString( event->dtStart(), true ) );
03926   }
03927   if ( event->dtStart() != event->dtEnd() ) {
03928     mResult += i18n("End Date: %1\n").
03929                arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) );
03930   }
03931   if ( !event->doesFloat() ) {
03932     mResult += i18n("End Time: %1\n").
03933                arg( IncidenceFormatter::timeToString( event->dtEnd(), true ) );
03934   }
03935   if ( event->doesRecur() ) {
03936     Recurrence *recur = event->recurrence();
03937     // TODO: Merge these two to one of the form "Recurs every 3 days"
03938     mResult += i18n("Recurs: %1\n")
03939              .arg( recurrence[ recur->recurrenceType() ] );
03940     mResult += i18n("Frequency: %1\n")
03941              .arg( event->recurrence()->frequency() );
03942 
03943     if ( recur->duration() > 0 ) {
03944       mResult += i18n ("Repeats once", "Repeats %n times", recur->duration());
03945       mResult += '\n';
03946     } else {
03947       if ( recur->duration() != -1 ) {
03948 // TODO_Recurrence: What to do with floating
03949         QString endstr;
03950         if ( event->doesFloat() ) {
03951           endstr = KGlobal::locale()->formatDate( recur->endDate() );
03952         } else {
03953           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() );
03954         }
03955         mResult += i18n("Repeat until: %1\n").arg( endstr );
03956       } else {
03957         mResult += i18n("Repeats forever\n");
03958       }
03959     }
03960   }
03961   QString details = event->description();
03962   if ( !details.isEmpty() ) {
03963     mResult += i18n("Details:\n%1\n").arg( details );
03964   }
03965   return !mResult.isEmpty();
03966 }
03967 
03968 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
03969 {
03970   mResult = mailBodyIncidence( todo );
03971 
03972   if ( todo->hasStartDate() ) {
03973     mResult += i18n("Start Date: %1\n").
03974                arg( IncidenceFormatter::dateToString( todo->dtStart( false ), true ) );
03975     if ( !todo->doesFloat() ) {
03976       mResult += i18n("Start Time: %1\n").
03977                  arg( IncidenceFormatter::timeToString( todo->dtStart( false ),true ) );
03978     }
03979   }
03980   if ( todo->hasDueDate() ) {
03981     mResult += i18n("Due Date: %1\n").
03982                arg( IncidenceFormatter::dateToString( todo->dtDue(), true ) );
03983     if ( !todo->doesFloat() ) {
03984       mResult += i18n("Due Time: %1\n").
03985                  arg( IncidenceFormatter::timeToString( todo->dtDue(), true ) );
03986     }
03987   }
03988   QString details = todo->description();
03989   if ( !details.isEmpty() ) {
03990     mResult += i18n("Details:\n%1\n").arg( details );
03991   }
03992   return !mResult.isEmpty();
03993 }
03994 
03995 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
03996 {
03997   mResult = mailBodyIncidence( journal );
03998   mResult += i18n("Date: %1\n").
03999              arg( IncidenceFormatter::dateToString( journal->dtStart(), true ) );
04000   if ( !journal->doesFloat() ) {
04001     mResult += i18n("Time: %1\n").
04002                arg( IncidenceFormatter::timeToString( journal->dtStart(), true ) );
04003   }
04004   if ( !journal->description().isEmpty() )
04005     mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() );
04006   return !mResult.isEmpty();
04007 }
04008 
04009 
04010 
04011 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
04012 {
04013   if ( !incidence )
04014     return QString::null;
04015 
04016   MailBodyVisitor v;
04017   if ( v.act( incidence ) ) {
04018     return v.result();
04019   }
04020   return QString::null;
04021 }
04022 
04023 static QString recurEnd( Incidence *incidence )
04024 {
04025   QString endstr;
04026   if ( incidence->doesFloat() ) {
04027     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
04028   } else {
04029     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
04030   }
04031   return endstr;
04032 }
04033 
04034 /************************************
04035  *  More static formatting functions
04036  ************************************/
04037 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
04038 {
04039   if ( !incidence->doesRecur() ) {
04040     return i18n( "No recurrence" );
04041   }
04042   QStringList dayList;
04043   dayList.append( i18n( "31st Last" ) );
04044   dayList.append( i18n( "30th Last" ) );
04045   dayList.append( i18n( "29th Last" ) );
04046   dayList.append( i18n( "28th Last" ) );
04047   dayList.append( i18n( "27th Last" ) );
04048   dayList.append( i18n( "26th Last" ) );
04049   dayList.append( i18n( "25th Last" ) );
04050   dayList.append( i18n( "24th Last" ) );
04051   dayList.append( i18n( "23rd Last" ) );
04052   dayList.append( i18n( "22nd Last" ) );
04053   dayList.append( i18n( "21st Last" ) );
04054   dayList.append( i18n( "20th Last" ) );
04055   dayList.append( i18n( "19th Last" ) );
04056   dayList.append( i18n( "18th Last" ) );
04057   dayList.append( i18n( "17th Last" ) );
04058   dayList.append( i18n( "16th Last" ) );
04059   dayList.append( i18n( "15th Last" ) );
04060   dayList.append( i18n( "14th Last" ) );
04061   dayList.append( i18n( "13th Last" ) );
04062   dayList.append( i18n( "12th Last" ) );
04063   dayList.append( i18n( "11th Last" ) );
04064   dayList.append( i18n( "10th Last" ) );
04065   dayList.append( i18n( "9th Last" ) );
04066   dayList.append( i18n( "8th Last" ) );
04067   dayList.append( i18n( "7th Last" ) );
04068   dayList.append( i18n( "6th Last" ) );
04069   dayList.append( i18n( "5th Last" ) );
04070   dayList.append( i18n( "4th Last" ) );
04071   dayList.append( i18n( "3rd Last" ) );
04072   dayList.append( i18n( "2nd Last" ) );
04073   dayList.append( i18n( "last day of the month", "Last" ) );
04074   dayList.append( i18n( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
04075   dayList.append( i18n( "1st" ) );
04076   dayList.append( i18n( "2nd" ) );
04077   dayList.append( i18n( "3rd" ) );
04078   dayList.append( i18n( "4th" ) );
04079   dayList.append( i18n( "5th" ) );
04080   dayList.append( i18n( "6th" ) );
04081   dayList.append( i18n( "7th" ) );
04082   dayList.append( i18n( "8th" ) );
04083   dayList.append( i18n( "9th" ) );
04084   dayList.append( i18n( "10th" ) );
04085   dayList.append( i18n( "11th" ) );
04086   dayList.append( i18n( "12th" ) );
04087   dayList.append( i18n( "13th" ) );
04088   dayList.append( i18n( "14th" ) );
04089   dayList.append( i18n( "15th" ) );
04090   dayList.append( i18n( "16th" ) );
04091   dayList.append( i18n( "17th" ) );
04092   dayList.append( i18n( "18th" ) );
04093   dayList.append( i18n( "19th" ) );
04094   dayList.append( i18n( "20th" ) );
04095   dayList.append( i18n( "21st" ) );
04096   dayList.append( i18n( "22nd" ) );
04097   dayList.append( i18n( "23rd" ) );
04098   dayList.append( i18n( "24th" ) );
04099   dayList.append( i18n( "25th" ) );
04100   dayList.append( i18n( "26th" ) );
04101   dayList.append( i18n( "27th" ) );
04102   dayList.append( i18n( "28th" ) );
04103   dayList.append( i18n( "29th" ) );
04104   dayList.append( i18n( "30th" ) );
04105   dayList.append( i18n( "31st" ) );
04106 
04107   int weekStart = KGlobal::locale()->weekStartDay();
04108   QString dayNames;
04109 
04110   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
04111 
04112   Recurrence *recur = incidence->recurrence();
04113 
04114   QString recurStr;
04115   switch ( recur->recurrenceType() ) {
04116   case Recurrence::rNone:
04117     return i18n( "No recurrence" );
04118 
04119   case Recurrence::rMinutely:
04120     recurStr = i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() );
04121     if ( recur->duration() != -1 ) {
04122       recurStr = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
04123       if ( recur->duration() >  0 ) {
04124         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04125       }
04126     }
04127     break;
04128 
04129   case Recurrence::rHourly:
04130     recurStr = i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() );
04131     if ( recur->duration() != -1 ) {
04132       recurStr = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
04133       if ( recur->duration() >  0 ) {
04134         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04135       }
04136     }
04137     break;
04138 
04139   case Recurrence::rDaily:
04140     recurStr = i18n( "Recurs daily", "Recurs every %n days", recur->frequency() );
04141     if ( recur->duration() != -1 ) {
04142       recurStr = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
04143       if ( recur->duration() >  0 ) {
04144         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04145       }
04146     }
04147     break;
04148 
04149   case Recurrence::rWeekly:
04150   {
04151     recurStr = i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() );
04152 
04153     bool addSpace = false;
04154     for ( int i = 0; i < 7; ++i ) {
04155       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
04156         if ( addSpace ) {
04157           dayNames.append( i18n( "separator for list of days", ", " ) );
04158         }
04159         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, true ) );
04160         addSpace = true;
04161       }
04162     }
04163     if ( dayNames.isEmpty() ) {
04164       dayNames = i18n( "Recurs weekly on no days", "no days" );
04165     }
04166     if ( recur->duration() != -1 ) {
04167       recurStr = i18n( "%1 on %2 until %3" ).
04168                  arg( recurStr ).arg( dayNames ).arg( recurEnd( incidence ) );
04169       if ( recur->duration() >  0 ) {
04170         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04171       }
04172     } else {
04173       recurStr = i18n( "%1 on %2" ).arg( recurStr ).arg( dayNames );
04174     }
04175     break;
04176   }
04177 
04178   case Recurrence::rMonthlyPos:
04179   {
04180     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
04181 
04182     if ( !recur->monthPositions().isEmpty() ) {
04183       KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
04184       if ( recur->duration() != -1 ) {
04185         recurStr = i18n( "%1 on the %2 %3 until %4" ).
04186                    arg( recurStr ).
04187                    arg( dayList[rule.pos() + 31] ).
04188                    arg( calSys->weekDayName( rule.day(), false ) ).
04189                    arg( recurEnd( incidence ) );
04190         if ( recur->duration() >  0 ) {
04191           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04192         }
04193       } else {
04194         recurStr = i18n( "%1 on the %2 %3" ).
04195                    arg( recurStr ).
04196                    arg( dayList[rule.pos() + 31] ).
04197                    arg( calSys->weekDayName( rule.day(), false ) );
04198       }
04199     }
04200     break;
04201   }
04202 
04203   case Recurrence::rMonthlyDay:
04204   {
04205     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
04206 
04207     if ( !recur->monthDays().isEmpty() ) {
04208       int days = recur->monthDays()[0];
04209       if ( recur->duration() != -1 ) {
04210         recurStr = i18n( "%1 on the %2 day until %3" ).
04211                    arg( recurStr ).
04212                    arg( dayList[days + 31] ).
04213                    arg( recurEnd( incidence ) );
04214         if ( recur->duration() >  0 ) {
04215           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04216         }
04217       } else {
04218         recurStr = i18n( "%1 on the %2 day" ).arg( recurStr ).arg( dayList[days + 31] );
04219       }
04220     }
04221     break;
04222   }
04223   case Recurrence::rYearlyMonth:
04224   {
04225     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
04226 
04227     if ( recur->duration() != -1 ) {
04228       if ( !recur->yearDates().isEmpty() ) {
04229         recurStr = i18n( "%1 on %2 %3 until %4" ).
04230                    arg( recurStr ).
04231                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
04232                    arg( dayList[ recur->yearDates()[0] + 31 ] ).
04233                    arg( recurEnd( incidence ) );
04234         if ( recur->duration() >  0 ) {
04235           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04236         }
04237       }
04238     } else {
04239       if ( !recur->yearDates().isEmpty() ) {
04240         recurStr = i18n( "%1 on %2 %3" ).
04241                    arg( recurStr ).
04242                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
04243                    arg( dayList[ recur->yearDates()[0] + 31 ] );
04244       } else {
04245         if ( !recur->yearMonths().isEmpty() ) {
04246           recurStr = i18n( "Recurs yearly on %1 %2" ).
04247                      arg( calSys->monthName( recur->yearMonths()[0],
04248                                              recur->startDate().year() ) ).
04249                      arg( dayList[ recur->startDate().day() + 31 ] );
04250         } else {
04251           recurStr = i18n( "Recurs yearly on %1 %2" ).
04252                      arg( calSys->monthName( recur->startDate().month(),
04253                                              recur->startDate().year() ) ).
04254                      arg( dayList[ recur->startDate().day() + 31 ] );
04255         }
04256       }
04257     }
04258     break;
04259   }
04260   case Recurrence::rYearlyDay:
04261   {
04262     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
04263     if ( !recur->yearDays().isEmpty() ) {
04264       if ( recur->duration() != -1 ) {
04265         recurStr = i18n( "%1 on day %2 until %3" ).
04266                    arg( recurStr ).
04267                    arg( recur->yearDays()[0] ).
04268                    arg( recurEnd( incidence ) );
04269         if ( recur->duration() >  0 ) {
04270           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04271         }
04272       } else {
04273         recurStr = i18n( "%1 on day %2" ).arg( recurStr ).arg( recur->yearDays()[0] );
04274       }
04275     }
04276     break;
04277   }
04278   case Recurrence::rYearlyPos:
04279   {
04280     recurStr = i18n( "Every year", "Every %n years", recur->frequency() );
04281     if ( !recur->yearPositions().isEmpty() && !recur->yearMonths().isEmpty() ) {
04282       KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
04283       if ( recur->duration() != -1 ) {
04284         recurStr = i18n( "%1 on the %2 %3 of %4 until %5" ).
04285                    arg( recurStr ).
04286                    arg( dayList[rule.pos() + 31] ).
04287                    arg( calSys->weekDayName( rule.day(), false ) ).
04288                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
04289                    arg( recurEnd( incidence ) );
04290         if ( recur->duration() >  0 ) {
04291           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04292         }
04293       } else {
04294         recurStr = i18n( "%1 on the %2 %3 of %4" ).
04295                    arg( recurStr ).
04296                    arg( dayList[rule.pos() + 31] ).
04297                    arg( calSys->weekDayName( rule.day(), false ) ).
04298                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
04299       }
04300     }
04301    break;
04302   }
04303   }
04304 
04305   if ( recurStr.isEmpty() ) {
04306     recurStr = i18n( "Incidence recurs" );
04307   }
04308   // Now, append the EXDATES
04309   DateTimeList l = recur->exDateTimes();
04310   DateTimeList::ConstIterator il;
04311   QStringList exStr;
04312   for ( il = l.constBegin(); il != l.constEnd(); ++il ) {
04313     switch ( recur->recurrenceType() ) {
04314     case Recurrence::rMinutely:
04315       exStr << i18n( "minute %1" ).arg( (*il).time().minute() );
04316       break;
04317     case Recurrence::rHourly:
04318       exStr << KGlobal::locale()->formatTime( (*il).time() );
04319       break;
04320     case Recurrence::rDaily:
04321       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04322       break;
04323     case Recurrence::rWeekly:
04324       exStr << calSys->weekDayName( (*il).date(), true );
04325       break;
04326     case Recurrence::rMonthlyPos:
04327       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04328       break;
04329     case Recurrence::rMonthlyDay:
04330       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04331       break;
04332     case Recurrence::rYearlyMonth:
04333       exStr << calSys->monthName( (*il).date(), false );
04334       break;
04335     case Recurrence::rYearlyDay:
04336       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04337       break;
04338     case Recurrence::rYearlyPos:
04339       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04340       break;
04341     }
04342   }
04343 
04344   if ( !exStr.isEmpty() ) {
04345     recurStr = i18n( "%1 (excluding %2)" ).arg( recurStr, exStr.join( "," ) );
04346   }
04347 
04348   return recurStr;
04349 }
04350 
04351 QString IncidenceFormatter::timeToString( const QDateTime &date, bool shortfmt )
04352 {
04353   return KGlobal::locale()->formatTime( date.time(), !shortfmt );
04354 }
04355 
04356 QString IncidenceFormatter::dateToString( const QDateTime &date, bool shortfmt )
04357 {
04358   return
04359     KGlobal::locale()->formatDate( date.date(), shortfmt );
04360 }
04361 
04362 QString IncidenceFormatter::dateTimeToString( const QDateTime &date,
04363                                               bool allDay, bool shortfmt )
04364 {
04365   if ( allDay ) {
04366     return dateToString( date, shortfmt );
04367   }
04368 
04369   return  KGlobal::locale()->formatDateTime( date, shortfmt );
04370 }
04371 
04372 QString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence )
04373 {
04374   if ( !calendar || !incidence ) {
04375     return QString::null;
04376   }
04377 
04378   CalendarResources *calendarResource = dynamic_cast<CalendarResources*>( calendar );
04379   if ( !calendarResource ) {
04380     return QString::null;
04381   }
04382 
04383   ResourceCalendar *resourceCalendar = calendarResource->resource( incidence );
04384   if ( resourceCalendar ) {
04385     if ( !resourceCalendar->subresources().isEmpty() ) {
04386       QString subRes = resourceCalendar->subresourceIdentifier( incidence );
04387       if ( subRes.isEmpty() ) {
04388         return resourceCalendar->resourceName();
04389       } else {
04390         return resourceCalendar->labelForSubresource( subRes );
04391       }
04392     }
04393     return resourceCalendar->resourceName();
04394   }
04395 
04396   return QString::null;
04397 }
04398 
04399 static QString secs2Duration( int secs )
04400 {
04401   QString tmp;
04402   int days = secs / 86400;
04403   if ( days > 0 ) {
04404     tmp += i18n( "1 day", "%n days", days );
04405     tmp += ' ';
04406     secs -= ( days * 86400 );
04407   }
04408   int hours = secs / 3600;
04409   if ( hours > 0 ) {
04410     tmp += i18n( "1 hour", "%n hours", hours );
04411     tmp += ' ';
04412     secs -= ( hours * 3600 );
04413   }
04414   int mins = secs / 60;
04415   if ( mins > 0 ) {
04416     tmp += i18n( "1 minute", "%n minutes",  mins );
04417   }
04418   return tmp;
04419 }
04420 
04421 QString IncidenceFormatter::durationString( Incidence *incidence )
04422 {
04423   QString tmp;
04424   if ( incidence->type() == "Event" ) {
04425     Event *event = static_cast<Event *>( incidence );
04426     if ( event->hasEndDate() ) {
04427       if ( !event->doesFloat() ) {
04428         tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
04429       } else {
04430         tmp = i18n( "1 day", "%n days",
04431                     event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
04432       }
04433     } else {
04434       tmp = i18n( "forever" );
04435     }
04436   } else if ( incidence->type() == "Todo" ) {
04437     Todo *todo = static_cast<Todo *>( incidence );
04438     if ( todo->hasDueDate() ) {
04439       if ( todo->hasStartDate() ) {
04440         if ( !todo->doesFloat() ) {
04441           tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
04442         } else {
04443           tmp = i18n( "1 day", "%n days",
04444                       todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
04445         }
04446       }
04447     }
04448   }
04449   return tmp;
04450 }
04451 
04452 QStringList IncidenceFormatter::reminderStringList( Incidence *incidence, bool shortfmt )
04453 {
04454   //TODO: implement shortfmt=false
04455   Q_UNUSED( shortfmt );
04456 
04457   QStringList reminderStringList;
04458 
04459   if ( incidence ) {
04460     Alarm::List alarms = incidence->alarms();
04461     Alarm::List::ConstIterator it;
04462     for ( it = alarms.begin(); it != alarms.end(); ++it ) {
04463       Alarm *alarm = *it;
04464       int offset = 0;
04465       QString remStr, atStr, offsetStr;
04466       if ( alarm->hasTime() ) {
04467         offset = 0;
04468         if ( alarm->time().isValid() ) {
04469           atStr = KGlobal::locale()->formatDateTime( alarm->time() );
04470         }
04471       } else if ( alarm->hasStartOffset() ) {
04472         offset = alarm->startOffset().asSeconds();
04473         if ( offset < 0 ) {
04474           offset = -offset;
04475           offsetStr = i18n( "N days/hours/minutes before the start datetime",
04476                             "%1 before the start" );
04477         } else if ( offset > 0 ) {
04478           offsetStr = i18n( "N days/hours/minutes after the start datetime",
04479                             "%1 after the start" );
04480         } else { //offset is 0
04481           if ( incidence->dtStart().isValid() ) {
04482             atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() );
04483           }
04484         }
04485       } else if ( alarm->hasEndOffset() ) {
04486         offset = alarm->endOffset().asSeconds();
04487         if ( offset < 0 ) {
04488           offset = -offset;
04489           if ( incidence->type() == "Todo" ) {
04490             offsetStr = i18n( "N days/hours/minutes before the due datetime",
04491                               "%1 before the to-do is due" );
04492           } else {
04493             offsetStr = i18n( "N days/hours/minutes before the end datetime",
04494                               "%1 before the end" );
04495           }
04496         } else if ( offset > 0 ) {
04497           if ( incidence->type() == "Todo" ) {
04498             offsetStr = i18n( "N days/hours/minutes after the due datetime",
04499                               "%1 after the to-do is due" );
04500           } else {
04501             offsetStr = i18n( "N days/hours/minutes after the end datetime",
04502                               "%1 after the end" );
04503           }
04504         } else { //offset is 0
04505           if ( incidence->type() == "Todo" ) {
04506             Todo *t = static_cast<Todo *>( incidence );
04507             if ( t->dtDue().isValid() ) {
04508               atStr = KGlobal::locale()->formatDateTime( t->dtDue() );
04509             }
04510           } else {
04511             Event *e = static_cast<Event *>( incidence );
04512             if ( e->dtEnd().isValid() ) {
04513               atStr = KGlobal::locale()->formatDateTime( e->dtEnd() );
04514             }
04515           }
04516         }
04517       }
04518       if ( offset == 0 ) {
04519         if ( !atStr.isEmpty() ) {
04520           remStr = i18n( "reminder occurs at datetime", "at %1" ).arg( atStr );
04521         }
04522       } else {
04523         remStr = offsetStr.arg( secs2Duration( offset ) );
04524       }
04525 
04526       if ( alarm->repeatCount() > 0 ) {
04527         QString countStr = i18n( "repeats once", "repeats %n times", alarm->repeatCount() );
04528         QString intervalStr = i18n( "interval is N days/hours/minutes", "interval is %1" ).
04529                               arg( secs2Duration( alarm->snoozeTime().asSeconds() ) );
04530         QString repeatStr = i18n( "(repeat string, interval string)", "(%1, %2)" ).
04531                             arg( countStr, intervalStr );
04532         remStr = remStr + ' ' + repeatStr;
04533 
04534       }
04535       reminderStringList << remStr;
04536     }
04537   }
04538 
04539   return reminderStringList;
04540 }
KDE Home | KDE Accessibility Home | Description of Access Keys