libkcal

incidenceformatter.cpp

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