libkcal

incidenceformatter.cpp

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