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       const Event *existingEvent = dynamic_cast<Event*>( existingIncidence );
01989       if ( existingEvent ) {
01990         // We check if the CANCEL message contains all attendees from the original
01991         // message, in this case we assume that the event has been canceled, otherwise
01992         // only a subset of the attendees (including us) has been disinvited.
01993         const Attendee::List oldAttendees = existingEvent->attendees();
01994         const Attendee::List newAttendees = event->attendees();
01995 
01996         bool hasAllAttendees = true;
01997         for ( unsigned int i = 0; i < oldAttendees.count(); ++i ) {
01998           bool containsAttendee = false;
01999           for ( unsigned int j = 0; j < newAttendees.count(); j++ ) {
02000             if ( oldAttendees[i]->email() == newAttendees[j]->email() ) {
02001               containsAttendee = true;
02002               break;
02003             }
02004           }
02005 
02006           if ( !containsAttendee ) {
02007             hasAllAttendees = false;
02008             break;
02009           }
02010         }
02011 
02012         if ( oldAttendees.count() != newAttendees.count() )
02013           hasAllAttendees = false;
02014 
02015         if ( hasAllAttendees )
02016           return i18n( "The organizer has canceled the event" );
02017       }
02018 
02019       return i18n( "The organizer has removed you from the invitation" );
02020     }
02021   case Scheduler::Add:
02022     return i18n( "Addition to the invitation" );
02023   case Scheduler::Reply:
02024   {
02025     if ( replyMeansCounter( event ) ) {
02026       return i18n( "%1 makes this counter proposal" ).arg( firstAttendeeName( event, sender ) );
02027     }
02028 
02029     Attendee::List attendees = event->attendees();
02030     if( attendees.count() == 0 ) {
02031       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
02032       return QString::null;
02033     }
02034     if( attendees.count() != 1 ) {
02035       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
02036                     << "but is " << attendees.count() << endl;
02037     }
02038     QString attendeeName = firstAttendeeName( event, sender );
02039 
02040     QString delegatorName, dummy;
02041     Attendee* attendee = *attendees.begin();
02042     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
02043     if ( delegatorName.isEmpty() ) {
02044       delegatorName = attendee->delegator();
02045     }
02046 
02047     switch( attendee->status() ) {
02048     case Attendee::NeedsAction:
02049       return i18n( "%1 indicates this invitation still needs some action" ).arg( attendeeName );
02050     case Attendee::Accepted:
02051       if ( event->revision() > 0 ) {
02052         if ( !sender.isEmpty() ) {
02053           return i18n( "This invitation has been updated by attendee %1" ).arg( sender );
02054         } else {
02055           return i18n( "This invitation has been updated by an attendee" );
02056         }
02057       } else {
02058         if ( delegatorName.isEmpty() ) {
02059           return i18n( "%1 accepts this invitation" ).arg( attendeeName );
02060         } else {
02061           return i18n( "%1 accepts this invitation on behalf of %2" ).
02062             arg( attendeeName ).arg( delegatorName );
02063         }
02064       }
02065     case Attendee::Tentative:
02066       if ( delegatorName.isEmpty() ) {
02067         return i18n( "%1 tentatively accepts this invitation" ).
02068           arg( attendeeName );
02069       } else {
02070         return i18n( "%1 tentatively accepts this invitation on behalf of %2" ).
02071           arg( attendeeName ).arg( delegatorName );
02072       }
02073     case Attendee::Declined:
02074       if ( delegatorName.isEmpty() ) {
02075         return i18n( "%1 declines this invitation" ).arg( attendeeName );
02076       } else {
02077         return i18n( "%1 declines this invitation on behalf of %2" ).
02078           arg( attendeeName ).arg( delegatorName );
02079       }
02080     case Attendee::Delegated: {
02081       QString delegate, dummy;
02082       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
02083       if ( delegate.isEmpty() ) {
02084         delegate = attendee->delegate();
02085       }
02086       if ( !delegate.isEmpty() ) {
02087         return i18n( "%1 has delegated this invitation to %2" ).
02088           arg( attendeeName ) .arg( delegate );
02089       } else {
02090         return i18n( "%1 has delegated this invitation" ).arg( attendeeName );
02091       }
02092     }
02093     case Attendee::Completed:
02094       return i18n( "This invitation is now completed" );
02095     case Attendee::InProcess:
02096       return i18n( "%1 is still processing the invitation" ).
02097         arg( attendeeName );
02098     default:
02099       return i18n( "Unknown response to this invitation" );
02100     }
02101     break;
02102   }
02103 
02104   case Scheduler::Counter:
02105     return i18n( "%1 makes this counter proposal" ).arg( firstAttendeeName( event, sender ) );
02106 
02107   case Scheduler::Declinecounter:
02108   {
02109     QString orgStr = organizerName( event, sender );
02110     if ( senderIsOrganizer( event, sender ) ) {
02111       return i18n( "%1 declines your counter proposal" ).arg( orgStr );
02112     } else {
02113       return i18n( "%1 declines your counter proposal on behalf of %2" ).arg( sender, orgStr );
02114     }
02115   }
02116 
02117   case Scheduler::NoMethod:
02118     return i18n("Error: iTIP message with unknown method: '%1'").
02119       arg( msg->method() );
02120   }
02121   return QString::null;
02122 }
02123 
02124 static QString invitationHeaderTodo( Todo *todo, Incidence *existingIncidence,
02125                                      ScheduleMessage *msg, const QString &sender )
02126 {
02127   if ( !msg || !todo ) {
02128     return QString::null;
02129   }
02130 
02131   switch ( msg->method() ) {
02132   case Scheduler::Publish:
02133     return i18n("This task has been published");
02134   case Scheduler::Request:
02135     if ( existingIncidence && todo->revision() > 0 ) {
02136       QString orgStr = organizerName( todo, sender );
02137       if ( senderIsOrganizer( todo, sender ) ) {
02138         return i18n( "This task has been updated by the organizer %1" ).arg( orgStr );
02139       } else {
02140         return i18n( "This task has been updated by %1 as a representative of %2" ).
02141           arg( sender, orgStr );
02142       }
02143     } else {
02144       if ( iamOrganizer( todo ) ) {
02145         return i18n( "I created this task" );
02146       } else {
02147         QString orgStr = organizerName( todo, sender );
02148         if ( senderIsOrganizer( todo, sender ) ) {
02149           return i18n( "You have been assigned this task by %1" ).arg( orgStr );
02150         } else {
02151           return i18n( "You have been assigned this task by %1 as a representative of %2" ).
02152             arg( sender, orgStr );
02153         }
02154       }
02155     }
02156   case Scheduler::Refresh:
02157     return i18n( "This task was refreshed" );
02158   case Scheduler::Cancel:
02159     if ( iamOrganizer( todo ) ) {
02160       return i18n( "This task was canceled" );
02161     } else {
02162       return i18n( "The organizer has removed you from this task" );
02163     }
02164   case Scheduler::Add:
02165     return i18n( "Addition to the task" );
02166   case Scheduler::Reply:
02167   {
02168     if ( replyMeansCounter( todo ) ) {
02169       return i18n( "%1 makes this counter proposal" ).arg( firstAttendeeName( todo, sender ) );
02170     }
02171 
02172     Attendee::List attendees = todo->attendees();
02173     if( attendees.count() == 0 ) {
02174       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
02175       return QString::null;
02176     }
02177     if( attendees.count() != 1 ) {
02178       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
02179                     << "but is " << attendees.count() << endl;
02180     }
02181     QString attendeeName = firstAttendeeName( todo, sender );
02182 
02183     QString delegatorName, dummy;
02184     Attendee* attendee = *attendees.begin();
02185     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
02186     if ( delegatorName.isEmpty() ) {
02187       delegatorName = attendee->delegator();
02188     }
02189 
02190     switch( attendee->status() ) {
02191     case Attendee::NeedsAction:
02192       return i18n( "%1 indicates this task assignment still needs some action" ).arg( attendeeName );
02193     case Attendee::Accepted:
02194       if ( todo->revision() > 0 ) {
02195         if ( !sender.isEmpty() ) {
02196           if ( todo->isCompleted() ) {
02197             return i18n( "This task has been completed by assignee %1" ).arg( sender );
02198           } else {
02199             return i18n( "This task has been updated by assignee %1" ).arg( sender );
02200           }
02201         } else {
02202           if ( todo->isCompleted() ) {
02203             return i18n( "This task has been completed by an assignee" );
02204           } else {
02205             return i18n( "This task has been updated by an assignee" );
02206           }
02207         }
02208       } else {
02209         if ( delegatorName.isEmpty() ) {
02210           return i18n( "%1 accepts this task" ).arg( attendeeName );
02211         } else {
02212           return i18n( "%1 accepts this task on behalf of %2" ).
02213             arg( attendeeName ).arg( delegatorName );
02214         }
02215       }
02216     case Attendee::Tentative:
02217       if ( delegatorName.isEmpty() ) {
02218         return i18n( "%1 tentatively accepts this task" ).
02219           arg( attendeeName );
02220       } else {
02221         return i18n( "%1 tentatively accepts this task on behalf of %2" ).
02222           arg( attendeeName ).arg( delegatorName );
02223       }
02224     case Attendee::Declined:
02225       if ( delegatorName.isEmpty() ) {
02226         return i18n( "%1 declines this task" ).arg( attendeeName );
02227       } else {
02228         return i18n( "%1 declines this task on behalf of %2" ).
02229           arg( attendeeName ).arg( delegatorName );
02230       }
02231     case Attendee::Delegated: {
02232       QString delegate, dummy;
02233       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
02234       if ( delegate.isEmpty() ) {
02235         delegate = attendee->delegate();
02236       }
02237       if ( !delegate.isEmpty() ) {
02238         return i18n( "%1 has delegated this request for the task to %2" ).
02239           arg( attendeeName ).arg( delegate );
02240       } else {
02241         return i18n( "%1 has delegated this request for the task" ).
02242           arg( attendeeName );
02243       }
02244     }
02245     case Attendee::Completed:
02246       return i18n( "The request for this task is now completed" );
02247     case Attendee::InProcess:
02248       return i18n( "%1 is still processing the task" ).
02249         arg( attendeeName );
02250     default:
02251       return i18n( "Unknown response to this task" );
02252     }
02253     break;
02254   }
02255 
02256   case Scheduler::Counter:
02257     return i18n( "%1 makes this counter proposal" ).arg( firstAttendeeName( todo, sender ) );
02258 
02259   case Scheduler::Declinecounter:
02260   {
02261     QString orgStr = organizerName( todo, sender );
02262     if ( senderIsOrganizer( todo, sender ) ) {
02263       return i18n( "%1 declines the counter proposal" ).arg( orgStr );
02264     } else {
02265       return i18n( "%1 declines the counter proposal on behalf of %2" ).arg( sender, orgStr );
02266     }
02267   }
02268 
02269   case Scheduler::NoMethod:
02270     return i18n( "Error: iTIP message with unknown method: '%1'" ).
02271       arg( msg->method() );
02272   }
02273   return QString::null;
02274 }
02275 
02276 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
02277 {
02278   if ( !msg || !journal ) {
02279     return QString::null;
02280   }
02281 
02282   switch ( msg->method() ) {
02283   case Scheduler::Publish:
02284     return i18n("This journal has been published");
02285   case Scheduler::Request:
02286     return i18n( "You have been assigned this journal" );
02287   case Scheduler::Refresh:
02288     return i18n( "This journal was refreshed" );
02289   case Scheduler::Cancel:
02290     return i18n( "This journal was canceled" );
02291   case Scheduler::Add:
02292     return i18n( "Addition to the journal" );
02293   case Scheduler::Reply:
02294   {
02295     if ( replyMeansCounter( journal ) ) {
02296       return i18n( "Sender makes this counter proposal" );
02297     }
02298 
02299     Attendee::List attendees = journal->attendees();
02300     if( attendees.count() == 0 ) {
02301       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
02302       return QString::null;
02303     }
02304     if( attendees.count() != 1 ) {
02305       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
02306                     << "but is " << attendees.count() << endl;
02307     }
02308     Attendee* attendee = *attendees.begin();
02309 
02310     switch( attendee->status() ) {
02311     case Attendee::NeedsAction:
02312       return i18n( "Sender indicates this journal assignment still needs some action" );
02313     case Attendee::Accepted:
02314       return i18n( "Sender accepts this journal" );
02315     case Attendee::Tentative:
02316       return i18n( "Sender tentatively accepts this journal" );
02317     case Attendee::Declined:
02318       return i18n( "Sender declines this journal" );
02319     case Attendee::Delegated:
02320       return i18n( "Sender has delegated this request for the journal" );
02321     case Attendee::Completed:
02322       return i18n( "The request for this journal is now completed" );
02323     case Attendee::InProcess:
02324       return i18n( "Sender is still processing the invitation" );
02325     default:
02326       return i18n( "Unknown response to this journal" );
02327     }
02328     break;
02329   }
02330   case Scheduler::Counter:
02331     return i18n( "Sender makes this counter proposal" );
02332   case Scheduler::Declinecounter:
02333     return i18n( "Sender declines the counter proposal" );
02334   case Scheduler::NoMethod:
02335     return i18n("Error: iTIP message with unknown method: '%1'").
02336       arg( msg->method() );
02337   }
02338   return QString::null;
02339 }
02340 
02341 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
02342 {
02343   if ( !msg || !fb ) {
02344     return QString::null;
02345   }
02346 
02347   switch ( msg->method() ) {
02348   case Scheduler::Publish:
02349     return i18n("This free/busy list has been published");
02350   case Scheduler::Request:
02351     return i18n( "The free/busy list has been requested" );
02352   case Scheduler::Refresh:
02353     return i18n( "This free/busy list was refreshed" );
02354   case Scheduler::Cancel:
02355     return i18n( "This free/busy list was canceled" );
02356   case Scheduler::Add:
02357     return i18n( "Addition to the free/busy list" );
02358   case Scheduler::NoMethod:
02359   default:
02360     return i18n("Error: Free/Busy iTIP message with unknown method: '%1'").
02361       arg( msg->method() );
02362   }
02363 }
02364 
02365 static QString invitationAttendeeList( Incidence *incidence )
02366 {
02367   QString tmpStr;
02368   if ( !incidence ) {
02369     return tmpStr;
02370   }
02371 
02372   if ( incidence->type() == "Todo" ) {
02373     tmpStr += i18n( "Assignees" );
02374   } else {
02375     tmpStr += i18n( "Invitation List" );
02376   }
02377   tmpStr += "<br/>";
02378 
02379   int count=0;
02380   Attendee::List attendees = incidence->attendees();
02381   if ( !attendees.isEmpty() ) {
02382     QStringList comments;
02383     Attendee::List::ConstIterator it;
02384     for( it = attendees.begin(); it != attendees.end(); ++it ) {
02385       Attendee *a = *it;
02386       if ( !iamAttendee( a ) ) {
02387         count++;
02388         if ( count == 1 ) {
02389           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\" columns=\"1\">";
02390         }
02391         tmpStr += "<tr>";
02392         tmpStr += "<td>";
02393         comments.clear();
02394         if ( attendeeIsOrganizer( incidence, a ) ) {
02395           comments << i18n( "organizer" );
02396         }
02397         if ( !a->delegator().isEmpty() ) {
02398           comments << i18n( "delegated by %1" ).arg( a->delegator() );
02399         }
02400         if ( !a->delegate().isEmpty() ) {
02401           comments << i18n( "delegated to %1" ).arg( a->delegate() );
02402         }
02403         tmpStr += invitationPerson( a->email(), a->name(), QString::null, comments.join( "," ) );
02404         tmpStr += "</td>";
02405         tmpStr += "</tr>";
02406       }
02407     }
02408   }
02409   if ( count ) {
02410     tmpStr += "</table>";
02411   } else {
02412     tmpStr = QString::null;
02413   }
02414 
02415   return tmpStr;
02416 }
02417 
02418 static QString invitationRsvpList( Incidence *incidence, Attendee *sender )
02419 {
02420   QString tmpStr;
02421   if ( !incidence ) {
02422     return tmpStr;
02423   }
02424 
02425   if ( incidence->type() == "Todo" ) {
02426     tmpStr += i18n( "Assignees" );
02427   } else {
02428     tmpStr += i18n( "Invitation List" );
02429   }
02430   tmpStr += "<br/>";
02431 
02432   int count=0;
02433   Attendee::List attendees = incidence->attendees();
02434   if ( !attendees.isEmpty() ) {
02435     QStringList comments;
02436     Attendee::List::ConstIterator it;
02437     for( it = attendees.begin(); it != attendees.end(); ++it ) {
02438       Attendee *a = *it;
02439       if ( !attendeeIsOrganizer( incidence, a ) ) {
02440         QString statusStr = a->statusStr();
02441         if ( sender && ( a->email() == sender->email() ) ) {
02442           // use the attendee taken from the response incidence,
02443           // rather than the attendee from the calendar incidence.
02444           if ( a->status() != sender->status() ) {
02445             statusStr = i18n( "%1 (<i>unrecorded</i>)" ).arg( sender->statusStr() );
02446           }
02447           a = sender;
02448         }
02449         count++;
02450         if ( count == 1 ) {
02451           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\" columns=\"2\">";
02452         }
02453         tmpStr += "<tr>";
02454         tmpStr += "<td>";
02455         comments.clear();
02456         if ( iamAttendee( a ) ) {
02457           comments << i18n( "myself" );
02458         }
02459         if ( !a->delegator().isEmpty() ) {
02460           comments << i18n( "delegated by %1" ).arg( a->delegator() );
02461         }
02462         if ( !a->delegate().isEmpty() ) {
02463           comments << i18n( "delegated to %1" ).arg( a->delegate() );
02464         }
02465         tmpStr += invitationPerson( a->email(), a->name(), QString::null, comments.join( "," ) );
02466         tmpStr += "</td>";
02467         tmpStr += "<td>" + statusStr + "</td>";
02468         tmpStr += "</tr>";
02469       }
02470     }
02471   }
02472   if ( count ) {
02473     tmpStr += "</table>";
02474   } else {
02475     tmpStr += "<i>" + i18n( "No attendee", "None" ) + "</i>";
02476   }
02477 
02478   return tmpStr;
02479 }
02480 
02481 static QString invitationAttachments( InvitationFormatterHelper *helper, Incidence *incidence )
02482 {
02483   QString tmpStr;
02484   if ( !incidence ) {
02485     return tmpStr;
02486   }
02487 
02488   Attachment::List attachments = incidence->attachments();
02489   if ( !attachments.isEmpty() ) {
02490     tmpStr += i18n( "Attached Documents:" ) + "<ol>";
02491 
02492     Attachment::List::ConstIterator it;
02493     for( it = attachments.begin(); it != attachments.end(); ++it ) {
02494       Attachment *a = *it;
02495       tmpStr += "<li>";
02496       // Attachment icon
02497       KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
02498       const QString iconStr = mimeType ? mimeType->icon( a->uri(), false ) : QString( "application-octet-stream" );
02499       const QString iconPath = KGlobal::iconLoader()->iconPath( iconStr, KIcon::Small );
02500       if ( !iconPath.isEmpty() ) {
02501         tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
02502       }
02503       const QCString encodedLabel = KCodecs::base64Encode(a->label().utf8());
02504       tmpStr += helper->makeLink( "ATTACH:" + QString::fromUtf8(encodedLabel.data(), encodedLabel.length()), a->label() );
02505       tmpStr += "</li>";
02506     }
02507     tmpStr += "</ol>";
02508   }
02509 
02510   return tmpStr;
02511 }
02512 
02513 class IncidenceFormatter::ScheduleMessageVisitor
02514   : public IncidenceBase::Visitor
02515 {
02516   public:
02517     ScheduleMessageVisitor() : mExistingIncidence( 0 ), mMessage( 0 ) { mResult = ""; }
02518     bool act( IncidenceBase *incidence, Incidence *existingIncidence, ScheduleMessage *msg,
02519               const QString &sender )
02520     {
02521       mExistingIncidence = existingIncidence;
02522       mMessage = msg;
02523       mSender = sender;
02524       return incidence->accept( *this );
02525     }
02526     QString result() const { return mResult; }
02527 
02528   protected:
02529     QString mResult;
02530     Incidence *mExistingIncidence;
02531     ScheduleMessage *mMessage;
02532     QString mSender;
02533 };
02534 
02535 class IncidenceFormatter::InvitationHeaderVisitor
02536   : public IncidenceFormatter::ScheduleMessageVisitor
02537 {
02538   protected:
02539     bool visit( Event *event )
02540     {
02541       mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender );
02542       return !mResult.isEmpty();
02543     }
02544     bool visit( Todo *todo )
02545     {
02546       mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender );
02547       return !mResult.isEmpty();
02548     }
02549     bool visit( Journal *journal )
02550     {
02551       mResult = invitationHeaderJournal( journal, mMessage );
02552       return !mResult.isEmpty();
02553     }
02554     bool visit( FreeBusy *fb )
02555     {
02556       mResult = invitationHeaderFreeBusy( fb, mMessage );
02557       return !mResult.isEmpty();
02558     }
02559 };
02560 
02561 class IncidenceFormatter::InvitationBodyVisitor
02562   : public IncidenceFormatter::ScheduleMessageVisitor
02563 {
02564   public:
02565     InvitationBodyVisitor( bool noHtmlMode )
02566       : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ) {}
02567 
02568   protected:
02569     bool visit( Event *event )
02570     {
02571       Event *oldevent = dynamic_cast<Event *>( mExistingIncidence );
02572       mResult = invitationDetailsEvent( event, oldevent, mMessage, mNoHtmlMode );
02573       return !mResult.isEmpty();
02574     }
02575     bool visit( Todo *todo )
02576     {
02577       Todo *oldtodo = dynamic_cast<Todo *>( mExistingIncidence );
02578       mResult = invitationDetailsTodo( todo, oldtodo, mMessage, mNoHtmlMode );
02579       return !mResult.isEmpty();
02580     }
02581     bool visit( Journal *journal )
02582     {
02583       Journal *oldjournal = dynamic_cast<Journal *>( mExistingIncidence );
02584       mResult = invitationDetailsJournal( journal, oldjournal, mNoHtmlMode );
02585       return !mResult.isEmpty();
02586     }
02587     bool visit( FreeBusy *fb )
02588     {
02589       mResult = invitationDetailsFreeBusy( fb, 0, mNoHtmlMode );
02590       return !mResult.isEmpty();
02591     }
02592 
02593   private:
02594     bool mNoHtmlMode;
02595 };
02596 
02597 class IncidenceFormatter::IncidenceCompareVisitor
02598   : public IncidenceBase::Visitor
02599 {
02600   public:
02601     IncidenceCompareVisitor() : mExistingIncidence(0) {}
02602     bool act( IncidenceBase *incidence, Incidence *existingIncidence, int method )
02603     {
02604       Incidence *inc = dynamic_cast<Incidence*>( incidence );
02605       if ( !inc || !existingIncidence  || inc->revision() <= existingIncidence->revision() )
02606         return false;
02607       mExistingIncidence = existingIncidence;
02608       mMethod = method;
02609       return incidence->accept( *this );
02610     }
02611 
02612     QString result() const
02613     {
02614       if ( mChanges.isEmpty() ) {
02615         return QString::null;
02616       }
02617       QString html = "<div align=\"left\"><ul><li>";
02618       html += mChanges.join( "</li><li>" );
02619       html += "</li><ul></div>";
02620       return html;
02621     }
02622 
02623   protected:
02624     bool visit( Event *event )
02625     {
02626       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
02627       compareIncidences( event, mExistingIncidence, mMethod );
02628       return !mChanges.isEmpty();
02629     }
02630     bool visit( Todo *todo )
02631     {
02632       compareTodos( todo, dynamic_cast<Todo*>( mExistingIncidence ) );
02633       compareIncidences( todo, mExistingIncidence, mMethod );
02634       return !mChanges.isEmpty();
02635     }
02636     bool visit( Journal *journal )
02637     {
02638       compareIncidences( journal, mExistingIncidence, mMethod );
02639       return !mChanges.isEmpty();
02640     }
02641     bool visit( FreeBusy *fb )
02642     {
02643       Q_UNUSED( fb );
02644       return !mChanges.isEmpty();
02645     }
02646 
02647   private:
02648     void compareEvents( Event *newEvent, Event *oldEvent )
02649     {
02650       if ( !oldEvent || !newEvent )
02651         return;
02652       if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->doesFloat() != newEvent->doesFloat() )
02653         mChanges += i18n( "The invitation starting time has been changed from %1 to %2" )
02654                     .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) );
02655       if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->doesFloat() != newEvent->doesFloat() )
02656         mChanges += i18n( "The invitation ending time has been changed from %1 to %2" )
02657                     .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) );
02658     }
02659 
02660     void compareTodos( Todo *newTodo, Todo *oldTodo )
02661     {
02662       if ( !oldTodo || !newTodo ) {
02663         return;
02664       }
02665 
02666       if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) {
02667         mChanges += i18n( "The task has been completed" );
02668       }
02669       if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) {
02670         mChanges += i18n( "The task is no longer completed" );
02671       }
02672       if ( oldTodo->percentComplete() != newTodo->percentComplete() ) {
02673         const QString oldPer = i18n( "%1%" ).arg( oldTodo->percentComplete() );
02674         const QString newPer = i18n( "%1%" ).arg( newTodo->percentComplete() );
02675         mChanges += i18n( "The task completed percentage has changed from %1 to %2" ).
02676                     arg( oldPer ).arg( newPer );
02677       }
02678 
02679       if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) {
02680         mChanges += i18n( "A task starting time has been added" );
02681       }
02682       if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) {
02683         mChanges += i18n( "The task starting time has been removed" );
02684       }
02685       if ( oldTodo->hasStartDate() && newTodo->hasStartDate() &&
02686            oldTodo->dtStart() != newTodo->dtStart() ) {
02687         mChanges += i18n( "The task starting time has been changed from %1 to %2" ).
02688                     arg( dateTimeToString( oldTodo->dtStart(), oldTodo->doesFloat(), false ) ).
02689                     arg( dateTimeToString( newTodo->dtStart(), newTodo->doesFloat(), false ) );
02690       }
02691 
02692       if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) {
02693         mChanges += i18n( "A task due time has been added" );
02694       }
02695       if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) {
02696         mChanges += i18n( "The task due time has been removed" );
02697       }
02698       if ( ( oldTodo->hasDueDate() && newTodo->hasDueDate() ) &&
02699            ( oldTodo->dtDue() != newTodo->dtDue() ) ) {
02700         mChanges += i18n( "The task due time has been changed from %1 to %2" ).
02701                     arg( dateTimeToString( oldTodo->dtDue(), oldTodo->doesFloat(), false ) ).
02702                     arg( dateTimeToString( newTodo->dtDue(), newTodo->doesFloat(), false ) );
02703       }
02704     }
02705 
02706     void compareIncidences( Incidence *newInc, Incidence *oldInc, int method )
02707     {
02708       if ( !oldInc || !newInc )
02709         return;
02710       if ( oldInc->summary() != newInc->summary() )
02711         mChanges += i18n( "The summary has been changed to: \"%1\"" ).arg( newInc->summary() );
02712       if ( oldInc->location() != newInc->location() )
02713         mChanges += i18n( "The location has been changed to: \"%1\"" ).arg( newInc->location() );
02714       if ( oldInc->description() != newInc->description() )
02715         mChanges += i18n( "The description has been changed to: \"%1\"" ).arg( newInc->description() );
02716       Attendee::List oldAttendees = oldInc->attendees();
02717       Attendee::List newAttendees = newInc->attendees();
02718       for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
02719             it != newAttendees.constEnd(); ++it ) {
02720         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
02721         if ( !oldAtt ) {
02722           mChanges += i18n( "Attendee %1 has been added" ).arg( (*it)->fullName() );
02723         } else {
02724           if ( oldAtt->status() != (*it)->status() )
02725             mChanges += i18n( "The status of attendee %1 has been changed to: %2" ).
02726                         arg( (*it)->fullName() ).arg( (*it)->statusStr() );
02727         }
02728       }
02729       if ( method == Scheduler::Request ) {
02730         for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
02731               it != oldAttendees.constEnd(); ++it ) {
02732           if ( !attendeeIsOrganizer( oldInc, (*it) ) ) {
02733             Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
02734             if ( !newAtt ) {
02735               mChanges += i18n( "Attendee %1 has been removed" ).arg( (*it)->fullName() );
02736             }
02737           }
02738         }
02739       }
02740     }
02741 
02742   private:
02743     Incidence *mExistingIncidence;
02744     int mMethod;
02745     QStringList mChanges;
02746 };
02747 
02748 
02749 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
02750 {
02751   if ( !id.startsWith( "ATTACH:" ) ) {
02752     QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
02753                   arg( generateLinkURL( id ), text );
02754     return res;
02755   } else {
02756     // draw the attachment links in non-bold face
02757     QString res = QString( "<a href=\"%1\">%2</a>" ).
02758                   arg( generateLinkURL( id ), text );
02759     return res;
02760   }
02761 }
02762 
02763 static QString responseButtons( Incidence *inc, bool rsvpReq, bool rsvpRec,
02764                                 InvitationFormatterHelper *helper )
02765 {
02766   // The spacer for the invitation buttons
02767   const QString spacer = "<td> &nbsp; </td>";
02768   // The open & close table cell tags for the invitation buttons
02769   const QString tdOpen = "<td>";
02770   const QString tdClose = "</td>" + spacer;
02771 
02772   QString html;
02773   if ( !helper ) {
02774     return html;
02775   }
02776 
02777   if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) {
02778     // Record only
02779     html += tdOpen;
02780     html += helper->makeLink( "record", i18n( "[Record]" ) );
02781     html += tdClose;
02782 
02783     // Move to trash
02784     html += tdOpen;
02785     html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) );
02786     html += tdClose;
02787 
02788   } else {
02789 
02790     // Accept
02791     html += tdOpen;
02792     html += helper->makeLink( "accept", i18n( "[Accept]" ) );
02793     html += tdClose;
02794 
02795     // Tentative
02796     html += tdOpen;
02797     html += helper->makeLink( "accept_conditionally",
02798                               i18n( "Accept conditionally", "[Accept cond.]" ) );
02799     html += tdClose;
02800 
02801     // Counter proposal
02802     html += tdOpen;
02803     html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
02804     html += tdClose;
02805 
02806     // Decline
02807     html += tdOpen;
02808     html += helper->makeLink( "decline", i18n( "[Decline]" ) );
02809     html += tdClose;
02810   }
02811 
02812   if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
02813     // Delegate
02814     html += tdOpen;
02815     html += helper->makeLink( "delegate", i18n( "[Delegate]" ) );
02816     html += tdClose;
02817 
02818     // Forward
02819     html += tdOpen;
02820     html += helper->makeLink( "forward", i18n( "[Forward]" ) );
02821     html += tdClose;
02822 
02823     // Check calendar
02824     if ( inc && inc->type() == "Event" ) {
02825       html += tdOpen;
02826       html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
02827       html += tdClose;
02828     }
02829   }
02830   return html;
02831 }
02832 
02833 static QString counterButtons( Incidence *incidence,
02834                                InvitationFormatterHelper *helper )
02835 {
02836   // The spacer for the invitation buttons
02837   const QString spacer = "<td> &nbsp; </td>";
02838   // The open & close table cell tags for the invitation buttons
02839   const QString tdOpen = "<td>";
02840   const QString tdClose = "</td>" + spacer;
02841 
02842   QString html;
02843   if ( !helper ) {
02844     return html;
02845   }
02846 
02847   // Accept proposal
02848   html += tdOpen;
02849   html += helper->makeLink( "accept_counter", i18n("[Accept]") );
02850   html += tdClose;
02851 
02852   // Decline proposal
02853   html += tdOpen;
02854   html += helper->makeLink( "decline_counter", i18n("[Decline]") );
02855   html += tdClose;
02856 
02857   // Check calendar
02858   if ( incidence && incidence->type() == "Event" ) {
02859     html += tdOpen;
02860     html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
02861     html += tdClose;
02862   }
02863   return html;
02864 }
02865 
02866 QString IncidenceFormatter::formatICalInvitationHelper( QString invitation,
02867                                                         Calendar *mCalendar,
02868                                                         InvitationFormatterHelper *helper,
02869                                                         bool noHtmlMode,
02870                                                         const QString &sender,
02871                                                         bool outlookCompareStyle )
02872 {
02873   if ( invitation.isEmpty() ) {
02874     return QString::null;
02875   }
02876 
02877   ICalFormat format;
02878   // parseScheduleMessage takes the tz from the calendar, no need to set it manually here for the format!
02879   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
02880 
02881   if( !msg ) {
02882     kdDebug( 5850 ) << "Failed to parse the scheduling message" << endl;
02883     Q_ASSERT( format.exception() );
02884     kdDebug( 5850 ) << format.exception()->message() << endl;
02885     return QString::null;
02886   }
02887 
02888   IncidenceBase *incBase = msg->event();
02889 
02890   // Determine if this incidence is in my calendar (and owned by me)
02891   Incidence *existingIncidence = 0;
02892   if ( incBase && helper->calendar() ) {
02893     existingIncidence = helper->calendar()->incidence( incBase->uid() );
02894     if ( !CalHelper::isMyCalendarIncidence( helper->calendar(), existingIncidence ) ) {
02895       //sergio, 03/2011: This never happens I think. If the incidence is in a shared calendar
02896       //then it's uid will be a new one, and won't match incBase->uid().
02897       existingIncidence = 0;
02898     }
02899     if ( !existingIncidence ) {
02900       const Incidence::List list = helper->calendar()->incidences();
02901       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
02902         if ( (*it)->schedulingID() == incBase->uid() &&
02903              CalHelper::isMyCalendarIncidence( helper->calendar(), *it ) ) {
02904           existingIncidence = *it;
02905           break;
02906         }
02907       }
02908     }
02909   }
02910 
02911   Incidence *inc = dynamic_cast<Incidence*>( incBase ); // the incidence in the invitation email
02912 
02913   // First make the text of the message
02914   QString html;
02915 
02916   QString tableStyle = QString::fromLatin1(
02917     "style=\"border: solid 1px; margin: 0em;\"" );
02918   QString tableHead = QString::fromLatin1(
02919     "<div align=\"center\">"
02920     "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
02921     "<tr><td>").arg(tableStyle);
02922 
02923   html += tableHead;
02924   InvitationHeaderVisitor headerVisitor;
02925   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
02926   if ( !headerVisitor.act( inc, existingIncidence, msg, sender ) ) {
02927     return QString::null;
02928   }
02929   html += "<b>" + headerVisitor.result() + "</b>";
02930 
02931   if ( outlookCompareStyle ||
02932        msg->method() == Scheduler::Declinecounter ) { //use Outlook style for decline counters
02933     // use the Outlook 2007 Comparison Style
02934     InvitationBodyVisitor bodyVisitor( noHtmlMode );
02935     bool bodyOk;
02936     if ( msg->method() == Scheduler::Request || msg->method() == Scheduler::Reply ||
02937          msg->method() == Scheduler::Declinecounter ) {
02938       if ( inc && existingIncidence &&
02939            inc->lastModified() < existingIncidence->lastModified() ) {
02940         bodyOk = bodyVisitor.act( existingIncidence, inc, msg, sender );
02941       } else {
02942         bodyOk = bodyVisitor.act( inc, existingIncidence, msg, sender );
02943       }
02944     } else {
02945       bodyOk = bodyVisitor.act( inc, 0, msg, sender );
02946     }
02947     if ( bodyOk ) {
02948       html += bodyVisitor.result();
02949     } else {
02950       return QString::null;
02951     }
02952   } else {
02953     // use our "Classic: Comparison Style
02954     InvitationBodyVisitor bodyVisitor( noHtmlMode );
02955     if ( !bodyVisitor.act( inc, 0, msg, sender ) )
02956       return QString::null;
02957     html += bodyVisitor.result();
02958 
02959     if ( msg->method() == Scheduler::Request ) {
02960       IncidenceCompareVisitor compareVisitor;
02961       if ( compareVisitor.act( inc, existingIncidence, msg->method() ) ) {
02962         html += "<p align=\"left\">";
02963         if ( senderIsOrganizer( inc, sender ) ) {
02964           html += i18n( "The following changes have been made by the organizer:" );
02965         } else if ( !sender.isEmpty() ) {
02966           html += i18n( "The following changes have been made by %1:" ).arg( sender );
02967         } else {
02968           html += i18n( "The following changes have been made:" );
02969         }
02970         html += "</p>";
02971         html += compareVisitor.result();
02972       }
02973     }
02974     if ( msg->method() == Scheduler::Reply ) {
02975       IncidenceCompareVisitor compareVisitor;
02976       if ( compareVisitor.act( inc, existingIncidence, msg->method() ) ) {
02977         html += "<p align=\"left\">";
02978         if ( !sender.isEmpty() ) {
02979           html += i18n( "The following changes have been made by %1:" ).arg( sender );
02980         } else {
02981           html += i18n( "The following changes have been made by an attendee:" );
02982         }
02983         html += "</p>";
02984         html += compareVisitor.result();
02985       }
02986     }
02987   }
02988 
02989   // determine if I am the organizer for this invitation
02990   bool myInc = iamOrganizer( inc );
02991 
02992   // determine if the invitation response has already been recorded
02993   bool rsvpRec = false;
02994   Attendee *ea = 0;
02995   if ( !myInc ) {
02996     Incidence *rsvpIncidence = existingIncidence;
02997     if ( !rsvpIncidence && inc && inc->revision() > 0 ) {
02998       rsvpIncidence = inc;
02999     }
03000     if ( rsvpIncidence ) {
03001       ea = findMyAttendee( rsvpIncidence );
03002     }
03003     if ( ea &&
03004          ( ea->status() == Attendee::Accepted ||
03005            ea->status() == Attendee::Declined ||
03006            ea->status() == Attendee::Tentative ) ) {
03007       rsvpRec = true;
03008     }
03009   }
03010 
03011   // determine invitation role
03012   QString role;
03013   bool isDelegated = false;
03014   Attendee *a = findMyAttendee( inc );
03015   if ( !a && inc ) {
03016     if ( !inc->attendees().isEmpty() ) {
03017       a = inc->attendees().first();
03018     }
03019   }
03020   if ( a ) {
03021     isDelegated = ( a->status() == Attendee::Delegated );
03022     role = Attendee::roleName( a->role() );
03023   }
03024 
03025   // determine if RSVP needed, not-needed, or response already recorded
03026   bool rsvpReq = rsvpRequested( inc );
03027   if ( !myInc && a ) {
03028     html += "<br/>";
03029     html += "<i><u>";
03030     if ( rsvpRec && inc ) {
03031       if ( inc->revision() == 0 ) {
03032         html += i18n( "Your <b>%1</b> response has been recorded" ).
03033                 arg( ea->statusStr() );
03034       } else {
03035         html += i18n( "Your status for this invitation is <b>%1</b>" ).
03036                 arg( ea->statusStr() );
03037       }
03038       rsvpReq = false;
03039     } else if ( msg->method() == Scheduler::Cancel ) {
03040       html += i18n( "This invitation was canceled" );
03041     } else if ( msg->method() == Scheduler::Add ) {
03042       html += i18n( "This invitation was accepted" );
03043     } else if ( msg->method() == Scheduler::Declinecounter ) {
03044       rsvpReq = true;
03045       html += rsvpRequestedStr( rsvpReq, role );
03046     } else {
03047       if ( !isDelegated ) {
03048         html += rsvpRequestedStr( rsvpReq, role );
03049       } else {
03050         html += i18n( "Awaiting delegation response" );
03051       }
03052     }
03053     html += "</u></i>";
03054   }
03055 
03056   // Print if the organizer gave you a preset status
03057   if ( !myInc ) {
03058     if ( inc && inc->revision() == 0 ) {
03059       QString statStr = myStatusStr( inc );
03060       if ( !statStr.isEmpty() ) {
03061         html += "<br/>";
03062         html += "<i>";
03063         html += statStr;
03064         html += "</i>";
03065       }
03066     }
03067   }
03068 
03069   // Add groupware links
03070 
03071   html += "<br><table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td></tr>";
03072 
03073   switch ( msg->method() ) {
03074     case Scheduler::Publish:
03075     case Scheduler::Request:
03076     case Scheduler::Refresh:
03077     case Scheduler::Add:
03078     {
03079       if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
03080         html += "<tr><td colspan=\"13\">";
03081         if ( inc->type() == "Todo" ) {
03082           html += helper->makeLink( "reply", i18n( "[Record in my task list]" ) );
03083         } else {
03084           html += helper->makeLink( "reply", i18n( "[Record in my calendar]" ) );
03085         }
03086         html += "</td></tr>";
03087       }
03088 
03089       if ( !myInc && a ) {
03090         html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
03091       }
03092       break;
03093     }
03094 
03095     case Scheduler::Cancel:
03096       // Remove invitation
03097       if ( inc ) {
03098         html += "<tr><td colspan=\"13\">";
03099         if ( inc->type() == "Todo" ) {
03100           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my task list]" ) );
03101         } else {
03102           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my calendar]" ) );
03103         }
03104         html += "</td></tr>";
03105       }
03106       break;
03107 
03108     case Scheduler::Reply:
03109     {
03110       // Record invitation response
03111       Attendee *a = 0;
03112       Attendee *ea = 0;
03113       if ( inc ) {
03114         // First, determine if this reply is really a counter in disguise.
03115         if ( replyMeansCounter( inc ) ) {
03116           html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
03117           break;
03118         }
03119 
03120         // Next, maybe this is a declined reply that was delegated from me?
03121         // find first attendee who is delegated-from me
03122         // look a their PARTSTAT response, if the response is declined,
03123         // then we need to start over which means putting all the action
03124         // buttons and NOT putting on the [Record response..] button
03125         a = findDelegatedFromMyAttendee( inc );
03126         if ( a ) {
03127           if ( a->status() != Attendee::Accepted ||
03128                a->status() != Attendee::Tentative ) {
03129             html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
03130             break;
03131           }
03132         }
03133 
03134         // Finally, simply allow a Record of the reply
03135         if ( !inc->attendees().isEmpty() ) {
03136           a = inc->attendees().first();
03137         }
03138         if ( a ) {
03139           ea = findAttendee( existingIncidence, a->email() );
03140         }
03141       }
03142       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
03143         // we have seen this invitation and recorded a response
03144         if ( inc->revision() > 0 &&
03145              ( inc->lastModified() > existingIncidence->lastModified() ) ) {
03146           // newer than we have recorded, so an update
03147           html += "<tr><td>";
03148           if ( inc->type() == "Todo" ) {
03149             html += helper->makeLink( "reply", i18n( "[Record update in my task list]" ) );
03150           } else {
03151             html += helper->makeLink( "reply", i18n( "[Record update in my calendar]" ) );
03152           }
03153           html += "</td></tr>";
03154         } else {
03155           // not newer than we have recorded
03156           html += "<br><u><i>";
03157           if ( inc->revision() > 0 ) {
03158             // an update we already have recorded
03159             html += i18n( "This update has been recorded" );
03160           } else {
03161             // not an update
03162             html += i18n( "This <b>%1</b> response has been recorded" ).arg( ea->statusStr() );
03163           }
03164           html += "</i></u>";
03165         }
03166       } else {
03167         // Not seen or recorded with a response yet
03168         if ( inc ) {
03169           html += "<tr><td>";
03170           if ( inc->type() == "Todo" ) {
03171             html += helper->makeLink( "reply", i18n( "[Record response in my task list]" ) );
03172           } else {
03173             html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
03174           }
03175           html += "</td></tr>";
03176         }
03177       }
03178       break;
03179     }
03180 
03181     case Scheduler::Counter:
03182       // Counter proposal
03183       html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
03184       break;
03185 
03186     case Scheduler::Declinecounter:
03187       html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
03188       break;
03189 
03190     case Scheduler::NoMethod:
03191       break;
03192   }
03193 
03194   // close the groupware table
03195   html += "</td></tr></table>";
03196 
03197   // Add the attendee list
03198   if ( myInc ) {
03199     html += invitationRsvpList( existingIncidence, a );
03200   } else {
03201     html += invitationAttendeeList( inc );
03202   }
03203 
03204   // close the top-level table
03205   html += "</td></tr></table><br></div>";
03206 
03207   // Add the attachment list
03208   html += invitationAttachments( helper, inc );
03209 
03210   return html;
03211 }
03212 
03213 QString IncidenceFormatter::formatICalInvitation( QString invitation,
03214                                                   Calendar *mCalendar,
03215                                                   InvitationFormatterHelper *helper )
03216 {
03217   return formatICalInvitationHelper( invitation, mCalendar, helper, false, QString(), true );
03218 }
03219 
03220 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
03221                                                         Calendar *mCalendar,
03222                                                         InvitationFormatterHelper *helper )
03223 {
03224   return formatICalInvitationHelper( invitation, mCalendar, helper, true, QString(), true );
03225 }
03226 
03227 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
03228                                                         Calendar *mCalendar,
03229                                                         InvitationFormatterHelper *helper,
03230                                                         const QString &sender )
03231 {
03232   return formatICalInvitationHelper( invitation, mCalendar, helper, true, sender, true );
03233 }
03234 
03235 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
03236                                                         Calendar *mCalendar,
03237                                                         InvitationFormatterHelper *helper,
03238                                                         const QString &sender,
03239                                                         bool outlookCompareStyle )
03240 {
03241   return formatICalInvitationHelper( invitation, mCalendar, helper, true, sender, outlookCompareStyle );
03242 }
03243 
03244 /*******************************************************************
03245  *  Helper functions for the msTNEF -> VPart converter
03246  *******************************************************************/
03247 
03248 
03249 //-----------------------------------------------------------------------------
03250 
03251 static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
03252                            const QString& fallback = QString::null)
03253 {
03254   return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
03255                             fallback );
03256 }
03257 
03258 static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
03259                            const QString& fallback = QString::null )
03260 {
03261   return tnefMsg->findNamedProp( name, fallback );
03262 }
03263 
03264 struct save_tz { char* old_tz; char* tz_env_str; };
03265 
03266 /* temporarily go to a different timezone */
03267 static struct save_tz set_tz( const char* _tc )
03268 {
03269   const char *tc = _tc?_tc:"UTC";
03270 
03271   struct save_tz rv;
03272 
03273   rv.old_tz = 0;
03274   rv.tz_env_str = 0;
03275 
03276   //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;
03277 
03278   char* tz_env = 0;
03279   if( getenv( "TZ" ) ) {
03280     tz_env = strdup( getenv( "TZ" ) );
03281     rv.old_tz = tz_env;
03282   }
03283   char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
03284   strcpy( tmp_env, "TZ=" );
03285   strcpy( tmp_env+3, tc );
03286   putenv( tmp_env );
03287 
03288   rv.tz_env_str = tmp_env;
03289 
03290   /* tmp_env is not free'ed -- it is part of the environment */
03291 
03292   tzset();
03293   //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;
03294 
03295   return rv;
03296 }
03297 
03298 /* restore previous timezone */
03299 static void unset_tz( struct save_tz old_tz )
03300 {
03301   if( old_tz.old_tz ) {
03302     char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
03303     strcpy( tmp_env, "TZ=" );
03304     strcpy( tmp_env+3, old_tz.old_tz );
03305     putenv( tmp_env );
03306     /* tmp_env is not free'ed -- it is part of the environment */
03307     free( old_tz.old_tz );
03308   } else {
03309     /* clear TZ from env */
03310     putenv( strdup("TZ") );
03311   }
03312   tzset();
03313 
03314   /* is this OK? */
03315   if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
03316 }
03317 
03318 static QDateTime utc2Local( const QDateTime& utcdt )
03319 {
03320   struct tm tmL;
03321 
03322   save_tz tmp_tz = set_tz("UTC");
03323   time_t utc = utcdt.toTime_t();
03324   unset_tz( tmp_tz );
03325 
03326   localtime_r( &utc, &tmL );
03327   return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
03328                     QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
03329 }
03330 
03331 
03332 static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
03333                                           bool bDateOnly = false )
03334 {
03335   QDate tmpDate;
03336   QTime tmpTime;
03337   int year, month, day, hour, minute, second;
03338 
03339   if( bDateOnly ) {
03340     year = dtStr.left( 4 ).toInt();
03341     month = dtStr.mid( 4, 2 ).toInt();
03342     day = dtStr.mid( 6, 2 ).toInt();
03343     hour = 0;
03344     minute = 0;
03345     second = 0;
03346   } else {
03347     year = dtStr.left( 4 ).toInt();
03348     month = dtStr.mid( 4, 2 ).toInt();
03349     day = dtStr.mid( 6, 2 ).toInt();
03350     hour = dtStr.mid( 9, 2 ).toInt();
03351     minute = dtStr.mid( 11, 2 ).toInt();
03352     second = dtStr.mid( 13, 2 ).toInt();
03353   }
03354   tmpDate.setYMD( year, month, day );
03355   tmpTime.setHMS( hour, minute, second );
03356 
03357   if( tmpDate.isValid() && tmpTime.isValid() ) {
03358     QDateTime dT = QDateTime( tmpDate, tmpTime );
03359 
03360     if( !bDateOnly ) {
03361       // correct for GMT ( == Zulu time == UTC )
03362       if (dtStr.at(dtStr.length()-1) == 'Z') {
03363         //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
03364         //localUTCOffset( dT ) );
03365         dT = utc2Local( dT );
03366       }
03367     }
03368     return dT;
03369   } else
03370     return QDateTime();
03371 }
03372 
03373 
03374 
03375 QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef )
03376 {
03377   KTNEFParser parser;
03378   QBuffer buf( tnef );
03379   CalendarLocal cal ( QString::fromLatin1( "UTC" ) );
03380   KABC::Addressee addressee;
03381   KABC::VCardConverter cardConv;
03382   ICalFormat calFormat;
03383   Event* event = new Event();
03384 
03385   if( parser.openDevice( &buf ) ) {
03386     KTNEFMessage* tnefMsg = parser.message();
03387     //QMap<int,KTNEFProperty*> props = parser.message()->properties();
03388 
03389     // Everything depends from property PR_MESSAGE_CLASS
03390     // (this is added by KTNEFParser):
03391     QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
03392       .upper();
03393     if( !msgClass.isEmpty() ) {
03394       // Match the old class names that might be used by Outlook for
03395       // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
03396       bool bCompatClassAppointment = false;
03397       bool bCompatMethodRequest = false;
03398       bool bCompatMethodCancled = false;
03399       bool bCompatMethodAccepted = false;
03400       bool bCompatMethodAcceptedCond = false;
03401       bool bCompatMethodDeclined = false;
03402       if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
03403         bCompatClassAppointment = true;
03404         if( msgClass.endsWith( ".MTGREQ" ) )
03405           bCompatMethodRequest = true;
03406         if( msgClass.endsWith( ".MTGCNCL" ) )
03407           bCompatMethodCancled = true;
03408         if( msgClass.endsWith( ".MTGRESPP" ) )
03409           bCompatMethodAccepted = true;
03410         if( msgClass.endsWith( ".MTGRESPA" ) )
03411           bCompatMethodAcceptedCond = true;
03412         if( msgClass.endsWith( ".MTGRESPN" ) )
03413           bCompatMethodDeclined = true;
03414       }
03415       bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );
03416 
03417       if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
03418         // Compose a vCal
03419         bool bIsReply = false;
03420         QString prodID = "-//Microsoft Corporation//Outlook ";
03421         prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
03422         prodID += "MIMEDIR/EN\n";
03423         prodID += "VERSION:2.0\n";
03424         calFormat.setApplication( "Outlook", prodID );
03425 
03426         Scheduler::Method method;
03427         if( bCompatMethodRequest )
03428           method = Scheduler::Request;
03429         else if( bCompatMethodCancled )
03430           method = Scheduler::Cancel;
03431         else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
03432                  bCompatMethodDeclined ) {
03433           method = Scheduler::Reply;
03434           bIsReply = true;
03435         } else {
03436           // pending(khz): verify whether "0x0c17" is the right tag ???
03437           //
03438           // at the moment we think there are REQUESTS and UPDATES
03439           //
03440           // but WHAT ABOUT REPLIES ???
03441           //
03442           //
03443 
03444           if( tnefMsg->findProp(0x0c17) == "1" )
03445             bIsReply = true;
03446           method = Scheduler::Request;
03447         }
03448 
03450         ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );
03451 
03452         QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );
03453 
03454         if( !sSenderSearchKeyEmail.isEmpty() ) {
03455           int colon = sSenderSearchKeyEmail.find( ':' );
03456           // May be e.g. "SMTP:KHZ@KDE.ORG"
03457           if( sSenderSearchKeyEmail.find( ':' ) == -1 )
03458             sSenderSearchKeyEmail.remove( 0, colon+1 );
03459         }
03460 
03461         QString s( tnefMsg->findProp( 0x0e04 ) );
03462         QStringList attendees = QStringList::split( ';', s );
03463         if( attendees.count() ) {
03464           for( QStringList::Iterator it = attendees.begin();
03465                it != attendees.end(); ++it ) {
03466             // Skip all entries that have no '@' since these are
03467             // no mail addresses
03468             if( (*it).find('@') == -1 ) {
03469               s = (*it).stripWhiteSpace();
03470 
03471               Attendee *attendee = new Attendee( s, s, true );
03472               if( bIsReply ) {
03473                 if( bCompatMethodAccepted )
03474                   attendee->setStatus( Attendee::Accepted );
03475                 if( bCompatMethodDeclined )
03476                   attendee->setStatus( Attendee::Declined );
03477                 if( bCompatMethodAcceptedCond )
03478                   attendee->setStatus(Attendee::Tentative);
03479               } else {
03480                 attendee->setStatus( Attendee::NeedsAction );
03481                 attendee->setRole( Attendee::ReqParticipant );
03482               }
03483               event->addAttendee(attendee);
03484             }
03485           }
03486         } else {
03487           // Oops, no attendees?
03488           // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
03489           s = sSenderSearchKeyEmail;
03490           if( !s.isEmpty() ) {
03491             Attendee *attendee = new Attendee( QString::null, QString::null,
03492                                                true );
03493             if( bIsReply ) {
03494               if( bCompatMethodAccepted )
03495                 attendee->setStatus( Attendee::Accepted );
03496               if( bCompatMethodAcceptedCond )
03497                 attendee->setStatus( Attendee::Declined );
03498               if( bCompatMethodDeclined )
03499                 attendee->setStatus( Attendee::Tentative );
03500             } else {
03501               attendee->setStatus(Attendee::NeedsAction);
03502               attendee->setRole(Attendee::ReqParticipant);
03503             }
03504             event->addAttendee(attendee);
03505           }
03506         }
03507         s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
03508         if( s.isEmpty() && !bIsReply )
03509           s = sSenderSearchKeyEmail;
03510         // TODO: Use the common name?
03511         if( !s.isEmpty() )
03512           event->setOrganizer( s );
03513 
03514         s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
03515           .replace( QChar( ':' ), QString::null );
03516         event->setDtStart( QDateTime::fromString( s ) ); // ## Format??
03517 
03518         s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
03519           .replace( QChar( ':' ), QString::null );
03520         event->setDtEnd( QDateTime::fromString( s ) );
03521 
03522         s = tnefMsg->findProp( 0x8208 );
03523         event->setLocation( s );
03524 
03525         // is it OK to set this to OPAQUE always ??
03526         //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
03527         //vPart += "SEQUENCE:0\n";
03528 
03529         // is "0x0023" OK  -  or should we look for "0x0003" ??
03530         s = tnefMsg->findProp( 0x0023 );
03531         event->setUid( s );
03532 
03533         // PENDING(khz): is this value in local timezone? Must it be
03534         // adjusted? Most likely this is a bug in the server or in
03535         // Outlook - we ignore it for now.
03536         s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
03537           .replace( QChar( ':' ), QString::null );
03538         // ### libkcal always uses currentDateTime()
03539         // event->setDtStamp(QDateTime::fromString(s));
03540 
03541         s = tnefMsg->findNamedProp( "Keywords" );
03542         event->setCategories( s );
03543 
03544         s = tnefMsg->findProp( 0x1000 );
03545         event->setDescription( s );
03546 
03547         s = tnefMsg->findProp( 0x0070 );
03548         event->setSummary( s );
03549 
03550         s = tnefMsg->findProp( 0x0026 );
03551         event->setPriority( s.toInt() );
03552 
03553         // is reminder flag set ?
03554         if(!tnefMsg->findProp(0x8503).isEmpty()) {
03555           Alarm *alarm = new Alarm(event);
03556           QDateTime highNoonTime =
03557             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
03558                                      .replace( QChar( '-' ), "" )
03559                                      .replace( QChar( ':' ), "" ) );
03560           QDateTime wakeMeUpTime =
03561             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
03562                                      .replace( QChar( '-' ), "" )
03563                                      .replace( QChar( ':' ), "" ) );
03564           alarm->setTime(wakeMeUpTime);
03565 
03566           if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
03567             alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
03568           else
03569             // default: wake them up 15 minutes before the appointment
03570             alarm->setStartOffset( Duration( 15*60 ) );
03571           alarm->setDisplayAlarm( i18n( "Reminder" ) );
03572 
03573           // Sorry: the different action types are not known (yet)
03574           //        so we always set 'DISPLAY' (no sounds, no images...)
03575           event->addAlarm( alarm );
03576         }
03577         cal.addEvent( event );
03578         // we finished composing a vCal
03579       } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
03580         addressee.setUid( stringProp( tnefMsg, attMSGID ) );
03581         addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) );
03582         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
03583         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
03584         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
03585         addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) );
03586         addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) );
03587         addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
03588         addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
03589         addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) );
03590         addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) );
03591         addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) );
03592 
03593         QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
03594           .replace( QChar( '-' ), QString::null )
03595           .replace( QChar( ':' ), QString::null );
03596         if( !s.isEmpty() )
03597           addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );
03598 
03599         addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );
03600 
03601         // collect parts of Name entry
03602         addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
03603         addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
03604         addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) );
03605         addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) );
03606         addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );
03607 
03608         addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
03609         addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
03610         addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
03611         /*
03612         the MAPI property ID of this (multiline) )field is unknown:
03613         vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
03614         */
03615 
03616         KABC::Address adr;
03617         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) );
03618         adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
03619         adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
03620         adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
03621         adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) );
03622         adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
03623         adr.setType(KABC::Address::Home);
03624         addressee.insertAddress(adr);
03625 
03626         adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
03627         adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) );
03628         adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) );
03629         adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) );
03630         adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
03631         adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) );
03632         adr.setType( KABC::Address::Work );
03633         addressee.insertAddress( adr );
03634 
03635         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) );
03636         adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
03637         adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
03638         adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
03639         adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) );
03640         adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
03641         adr.setType( KABC::Address::Dom );
03642         addressee.insertAddress(adr);
03643 
03644         // problem: the 'other' address was stored by KOrganizer in
03645         //          a line looking like the following one:
03646         // 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
03647 
03648         QString nr;
03649         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
03650         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) );
03651         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
03652         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) );
03653         nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
03654         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) );
03655         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
03656         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) );
03657         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
03658         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) );
03659 
03660         s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
03661           .replace( QChar( '-' ), QString::null )
03662           .replace( QChar( ':' ), QString::null );
03663         if( !s.isEmpty() )
03664           addressee.setBirthday( QDateTime::fromString( s ) );
03665 
03666       } else if( "IPM.NOTE" == msgClass ) {
03667 
03668       } // else if ... and so on ...
03669     }
03670   }
03671 
03672   // Compose return string
03673   QString iCal = calFormat.toString( &cal );
03674   if( !iCal.isEmpty() )
03675     // This was an iCal
03676     return iCal;
03677 
03678   // Not an iCal - try a vCard
03679   KABC::VCardConverter converter;
03680   return converter.createVCard( addressee );
03681 }
03682 
03683 
03684 QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef,
03685         Calendar *mCalendar, InvitationFormatterHelper *helper )
03686 {
03687   QString vPart = IncidenceFormatter::msTNEFToVPart( tnef );
03688   QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper );
03689   if( !iCal.isEmpty() )
03690     return iCal;
03691   return vPart;
03692 }
03693 
03694 
03695 
03696 
03697 /*******************************************************************
03698  *  Helper functions for the Incidence tooltips
03699  *******************************************************************/
03700 
03701 class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
03702 {
03703   public:
03704     ToolTipVisitor()
03705       : mCalendar( 0 ), mRichText( true ), mResult( "" ) {}
03706 
03707     bool act( Calendar *calendar, IncidenceBase *incidence,
03708               const QDate &date=QDate(), bool richText=true )
03709     {
03710       mCalendar = calendar;
03711       mDate = date;
03712       mRichText = richText;
03713       mResult = "";
03714       return incidence ? incidence->accept( *this ) : false;
03715     }
03716     QString result() const { return mResult; }
03717 
03718   protected:
03719     bool visit( Event *event );
03720     bool visit( Todo *todo );
03721     bool visit( Journal *journal );
03722     bool visit( FreeBusy *fb );
03723 
03724     QString dateRangeText( Event *event, const QDate &date );
03725     QString dateRangeText( Todo *todo, const QDate &date );
03726     QString dateRangeText( Journal *journal );
03727     QString dateRangeText( FreeBusy *fb );
03728 
03729     QString generateToolTip( Incidence* incidence, QString dtRangeText );
03730 
03731   protected:
03732     Calendar *mCalendar;
03733     QDate mDate;
03734     bool mRichText;
03735     QString mResult;
03736 };
03737 
03738 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const QDate &date )
03739 {
03740   QString ret;
03741   QString tmp;
03742 
03743   QDateTime startDt = event->dtStart();
03744   QDateTime endDt = event->dtEnd();
03745   if ( event->doesRecur() ) {
03746     if ( date.isValid() ) {
03747       QDateTime dt( date, QTime( 0, 0, 0 ) );
03748       int diffDays = startDt.daysTo( dt );
03749       dt = dt.addSecs( -1 );
03750       startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() );
03751       if ( event->hasEndDate() ) {
03752         endDt = endDt.addDays( diffDays );
03753         if ( startDt > endDt ) {
03754           startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() );
03755           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
03756         }
03757       }
03758     }
03759   }
03760   if ( event->isMultiDay() ) {
03761 
03762     tmp = "<br>" + i18n("Event start", "<i>From:</i>&nbsp;%1");
03763     if (event->doesFloat())
03764       ret += tmp.arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03765     else
03766       ret += tmp.arg( IncidenceFormatter::dateToString( startDt ).replace(" ", "&nbsp;") );
03767 
03768     tmp = "<br>" + i18n("Event end","<i>To:</i>&nbsp;%1");
03769     if (event->doesFloat())
03770       ret += tmp.arg( IncidenceFormatter::dateToString( endDt, false ).replace(" ", "&nbsp;") );
03771     else
03772       ret += tmp.arg( IncidenceFormatter::dateToString( endDt ).replace(" ", "&nbsp;") );
03773 
03774   } else {
03775 
03776     ret += "<br>"+i18n("<i>Date:</i>&nbsp;%1").
03777            arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03778     if ( !event->doesFloat() ) {
03779       const QString dtStartTime =
03780         IncidenceFormatter::timeToString( startDt, true ).replace( " ", "&nbsp;" );
03781       const QString dtEndTime =
03782         IncidenceFormatter::timeToString( endDt, true ).replace( " ", "&nbsp;" );
03783       if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00'
03784         tmp = "<br>" + i18n("time for event, &nbsp; to prevent ugly line breaks",
03785         "<i>Time:</i>&nbsp;%1").
03786         arg( dtStartTime );
03787       } else {
03788         tmp = "<br>" + i18n("time range for event, &nbsp; to prevent ugly line breaks",
03789         "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2").
03790         arg( dtStartTime, dtEndTime );
03791       }
03792       ret += tmp;
03793     }
03794 
03795   }
03796   return ret;
03797 }
03798 
03799 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const QDate &date )
03800 {
03801   QString ret;
03802   bool floats( todo->doesFloat() );
03803 
03804   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03805     QDateTime startDt = todo->dtStart();
03806     if ( todo->doesRecur() ) {
03807       if ( date.isValid() ) {
03808         startDt.setDate( date );
03809       }
03810     }
03811     ret += "<br>" +
03812            i18n("<i>Start:</i>&nbsp;%1").
03813            arg( IncidenceFormatter::dateTimeToString( startDt, floats, false ).
03814                 replace( " ", "&nbsp;" ) );
03815   }
03816 
03817   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03818     QDateTime dueDt = todo->dtDue();
03819     if ( todo->doesRecur() ) {
03820       if ( date.isValid() ) {
03821         QDateTime dt( date, QTime( 0, 0, 0 ) );
03822         dt = dt.addSecs( -1 );
03823         dueDt.setDate( todo->recurrence()->getNextDateTime( dt ).date() );
03824       }
03825     }
03826     ret += "<br>" +
03827            i18n("<i>Due:</i>&nbsp;%1").
03828            arg( IncidenceFormatter::dateTimeToString( dueDt, floats, false ).
03829                 replace( " ", "&nbsp;" ) );
03830   }
03831 
03832   // Print priority and completed info here, for lack of a better place
03833 
03834   if ( todo->priority() > 0 ) {
03835     ret += "<br>";
03836     ret += "<i>" + i18n( "Priority:" ) + "</i>" + "&nbsp;";
03837     ret += QString::number( todo->priority() );
03838   }
03839 
03840   ret += "<br>";
03841   if ( todo->isCompleted() ) {
03842     ret += "<i>" + i18n( "Completed:" ) + "</i>" + "&nbsp;";
03843     ret += todo->completedStr().replace( " ", "&nbsp;" );
03844   } else {
03845     ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + "&nbsp;";
03846     ret += i18n( "%1%" ).arg( todo->percentComplete() );
03847   }
03848 
03849   return ret;
03850 }
03851 
03852 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal )
03853 {
03854   QString ret;
03855   if (journal->dtStart().isValid() ) {
03856     ret += "<br>" +
03857            i18n("<i>Date:</i>&nbsp;%1").
03858            arg( IncidenceFormatter::dateToString( journal->dtStart(), false ) );
03859   }
03860   return ret;
03861 }
03862 
03863 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
03864 {
03865   QString tmp( "<br>" + i18n("<i>Period start:</i>&nbsp;%1") );
03866   QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) );
03867   tmp = "<br>" + i18n("<i>Period start:</i>&nbsp;%1");
03868   ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) );
03869   return ret;
03870 }
03871 
03872 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
03873 {
03874   mResult = generateToolTip( event, dateRangeText( event, mDate ) );
03875   return !mResult.isEmpty();
03876 }
03877 
03878 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
03879 {
03880   mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
03881   return !mResult.isEmpty();
03882 }
03883 
03884 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
03885 {
03886   mResult = generateToolTip( journal, dateRangeText( journal ) );
03887   return !mResult.isEmpty();
03888 }
03889 
03890 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
03891 {
03892   mResult = "<qt><b>" + i18n("Free/Busy information for %1")
03893         .arg(fb->organizer().fullName()) + "</b>";
03894   mResult += dateRangeText( fb );
03895   mResult += "</qt>";
03896   return !mResult.isEmpty();
03897 }
03898 
03899 static QString tooltipPerson( const QString &email, const QString &name, Attendee::PartStat status )
03900 {
03901   // Search for a new print name, if needed.
03902   const QString printName = searchName( email, name );
03903 
03904   // Get the icon corresponding to the attendee participation status.
03905   const QString iconPath = rsvpStatusIconPath( status );
03906 
03907   // Make the return string.
03908   QString personString;
03909   if ( !iconPath.isEmpty() ) {
03910     personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
03911   }
03912   if ( status != Attendee::None ) {
03913     personString += i18n( "attendee name (attendee status)", "%1 (%2)" ).
03914                     arg( printName.isEmpty() ? email : printName ).
03915                     arg( Attendee::statusName( status ) );
03916   } else {
03917     personString += i18n( "%1" ).arg( printName.isEmpty() ? email : printName );
03918   }
03919   return personString;
03920 }
03921 
03922 static QString tooltipFormatOrganizer( const QString &email, const QString &name )
03923 {
03924   // Search for a new print name, if needed
03925   const QString printName = searchName( email, name );
03926 
03927   // Get the icon for organizer
03928   const QString iconPath = KGlobal::iconLoader()->iconPath( "organizer", KIcon::Small );
03929 
03930   // Make the return string.
03931   QString personString;
03932   personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
03933   personString += ( printName.isEmpty() ? email : printName );
03934   return personString;
03935 }
03936 
03937 static QString tooltipFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role,
03938                                               bool showStatus )
03939 {
03940   const int maxNumAtts = 8; // maximum number of people to print per attendee role
03941   const QString etc = i18n( "elipsis", "..." );
03942 
03943   int i = 0;
03944   QString tmpStr;
03945   Attendee::List::ConstIterator it;
03946   Attendee::List attendees = incidence->attendees();
03947 
03948   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
03949     Attendee *a = *it;
03950     if ( a->role() != role ) {
03951       // skip not this role
03952       continue;
03953     }
03954     if ( attendeeIsOrganizer( incidence, a ) ) {
03955       // skip attendee that is also the organizer
03956       continue;
03957     }
03958     if ( i == maxNumAtts ) {
03959       tmpStr += "&nbsp;&nbsp;" + etc;
03960       break;
03961     }
03962     tmpStr += "&nbsp;&nbsp;" + tooltipPerson( a->email(), a->name(),
03963                                               showStatus ? a->status() : Attendee::None );
03964     if ( !a->delegator().isEmpty() ) {
03965       tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
03966     }
03967     if ( !a->delegate().isEmpty() ) {
03968       tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
03969     }
03970     tmpStr += "<br>";
03971     i++;
03972   }
03973 
03974   if ( tmpStr.endsWith( "<br>" ) ) {
03975     tmpStr.truncate( tmpStr.length() - 4 );
03976   }
03977   return tmpStr;
03978 }
03979 
03980 static QString tooltipFormatAttendees( Calendar *calendar, Incidence *incidence )
03981 {
03982   QString tmpStr, str;
03983 
03984   // Add organizer link
03985   int attendeeCount = incidence->attendees().count();
03986   if ( attendeeCount > 1 ||
03987        ( attendeeCount == 1 &&
03988          !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) {
03989     tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "<br>";
03990     tmpStr += "&nbsp;&nbsp;" + tooltipFormatOrganizer( incidence->organizer().email(),
03991                                                        incidence->organizer().name() );
03992   }
03993 
03994   // Show the attendee status if the incidence's organizer owns the resource calendar,
03995   // which means they are running the show and have all the up-to-date response info.
03996   bool showStatus = CalHelper::incOrganizerOwnsCalendar( calendar, incidence );
03997 
03998   // Add "chair"
03999   str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus );
04000   if ( !str.isEmpty() ) {
04001     tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "<br>";
04002     tmpStr += str;
04003   }
04004 
04005   // Add required participants
04006   str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus );
04007   if ( !str.isEmpty() ) {
04008     tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "<br>";
04009     tmpStr += str;
04010   }
04011 
04012   // Add optional participants
04013   str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus );
04014   if ( !str.isEmpty() ) {
04015     tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "<br>";
04016     tmpStr += str;
04017   }
04018 
04019   // Add observers
04020   str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus );
04021   if ( !str.isEmpty() ) {
04022     tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "<br>";
04023     tmpStr += str;
04024   }
04025 
04026   return tmpStr;
04027 }
04028 
04029 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence, QString dtRangeText )
04030 {
04031   const QString etc = i18n( "elipsis", "..." );
04032   const uint maxDescLen = 120; // maximum description chars to print (before elipsis)
04033 
04034   if ( !incidence ) {
04035     return QString::null;
04036   }
04037 
04038   QString tmp = "<qt>";
04039 
04040   // header
04041   tmp += "<b>" + incidence->summary().replace( "\n", "<br>" ) + "</b>";
04042   //NOTE: using <hr> seems to confuse Qt3 tooltips in some cases so use "-----"
04043   tmp += "<br>----------<br>";
04044 
04045   if ( mCalendar ) {
04046     QString calStr = IncidenceFormatter::resourceString( mCalendar, incidence );
04047     if ( !calStr.isEmpty() ) {
04048       tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + "&nbsp;";
04049       tmp += calStr;
04050     }
04051   }
04052 
04053   tmp += dtRangeText;
04054 
04055   if ( !incidence->location().isEmpty() ) {
04056     tmp += "<br>";
04057     tmp += "<i>" + i18n( "Location:" ) + "</i>" + "&nbsp;";
04058     tmp += incidence->location().replace( "\n", "<br>" );
04059   }
04060 
04061   QString durStr = IncidenceFormatter::durationString( incidence );
04062   if ( !durStr.isEmpty() ) {
04063     tmp += "<br>";
04064     tmp += "<i>" + i18n( "Duration:" ) + "</i>" + "&nbsp;";
04065     tmp += durStr;
04066   }
04067 
04068   if ( incidence->doesRecur() ) {
04069     tmp += "<br>";
04070     tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + "&nbsp;";
04071     tmp += IncidenceFormatter::recurrenceString( incidence );
04072   }
04073 
04074   if ( !incidence->description().isEmpty() ) {
04075     QString desc( incidence->description() );
04076     if ( desc.length() > maxDescLen ) {
04077       desc = desc.left( maxDescLen ) + etc;
04078     }
04079     tmp += "<br>----------<br>";
04080     tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
04081     tmp += desc.replace( "\n", "<br>" );
04082     tmp += "<br>----------";
04083   }
04084 
04085   int reminderCount = incidence->alarms().count();
04086   if ( reminderCount > 0 && incidence->isAlarmEnabled() ) {
04087     tmp += "<br>";
04088     tmp += "<i>" + i18n( "Reminder:", "%n Reminders:", reminderCount ) + "</i>" + "&nbsp;";
04089     tmp += IncidenceFormatter::reminderStringList( incidence ).join( ", " );
04090   }
04091 
04092   tmp += "<br>";
04093   tmp += tooltipFormatAttendees( mCalendar, incidence );
04094 
04095   int categoryCount = incidence->categories().count();
04096   if ( categoryCount > 0 ) {
04097     tmp += "<br>";
04098     tmp += "<i>" + i18n( "Category:", "%n Categories:", categoryCount ) + "</i>" + "&nbsp;";
04099     tmp += incidence->categories().join( ", " );
04100   }
04101 
04102   tmp += "</qt>";
04103   return tmp;
04104 }
04105 
04106 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
04107 {
04108   return toolTipStr( 0, incidence, QDate(), richText );
04109 }
04110 
04111 QString IncidenceFormatter::toolTipStr( Calendar *calendar,
04112                                         IncidenceBase *incidence,
04113                                         const QDate &date,
04114                                         bool richText )
04115 {
04116   ToolTipVisitor v;
04117   if ( v.act( calendar, incidence, date, richText ) ) {
04118     return v.result();
04119   } else {
04120     return QString::null;
04121   }
04122 }
04123 
04124 /*******************************************************************
04125  *  Helper functions for the Incidence tooltips
04126  *******************************************************************/
04127 
04128 class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
04129 {
04130   public:
04131     MailBodyVisitor() : mResult( "" ) {}
04132 
04133     bool act( IncidenceBase *incidence )
04134     {
04135       mResult = "";
04136       return incidence ? incidence->accept( *this ) : false;
04137     }
04138     QString result() const { return mResult; }
04139 
04140   protected:
04141     bool visit( Event *event );
04142     bool visit( Todo *todo );
04143     bool visit( Journal *journal );
04144     bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); }
04145   protected:
04146     QString mResult;
04147 };
04148 
04149 
04150 static QString mailBodyIncidence( Incidence *incidence )
04151 {
04152   QString body;
04153   if ( !incidence->summary().isEmpty() ) {
04154     body += i18n("Summary: %1\n").arg( incidence->summary() );
04155   }
04156   if ( !incidence->organizer().isEmpty() ) {
04157     body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() );
04158   }
04159   if ( !incidence->location().isEmpty() ) {
04160     body += i18n("Location: %1\n").arg( incidence->location() );
04161   }
04162   return body;
04163 }
04164 
04165 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
04166 {
04167   QString recurrence[]= {i18n("no recurrence", "None"),
04168     i18n("Minutely"), i18n("Hourly"), i18n("Daily"),
04169     i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"),
04170     i18n("Yearly"), i18n("Yearly"), i18n("Yearly")};
04171 
04172   mResult = mailBodyIncidence( event );
04173   mResult += i18n("Start Date: %1\n").
04174              arg( IncidenceFormatter::dateToString( event->dtStart(), true ) );
04175   if ( !event->doesFloat() ) {
04176     mResult += i18n("Start Time: %1\n").
04177                arg( IncidenceFormatter::timeToString( event->dtStart(), true ) );
04178   }
04179   if ( event->dtStart() != event->dtEnd() ) {
04180     mResult += i18n("End Date: %1\n").
04181                arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) );
04182   }
04183   if ( !event->doesFloat() ) {
04184     mResult += i18n("End Time: %1\n").
04185                arg( IncidenceFormatter::timeToString( event->dtEnd(), true ) );
04186   }
04187   if ( event->doesRecur() ) {
04188     Recurrence *recur = event->recurrence();
04189     // TODO: Merge these two to one of the form "Recurs every 3 days"
04190     mResult += i18n("Recurs: %1\n")
04191              .arg( recurrence[ recur->recurrenceType() ] );
04192     mResult += i18n("Frequency: %1\n")
04193              .arg( event->recurrence()->frequency() );
04194 
04195     if ( recur->duration() > 0 ) {
04196       mResult += i18n ("Repeats once", "Repeats %n times", recur->duration());
04197       mResult += '\n';
04198     } else {
04199       if ( recur->duration() != -1 ) {
04200 // TODO_Recurrence: What to do with floating
04201         QString endstr;
04202         if ( event->doesFloat() ) {
04203           endstr = KGlobal::locale()->formatDate( recur->endDate() );
04204         } else {
04205           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() );
04206         }
04207         mResult += i18n("Repeat until: %1\n").arg( endstr );
04208       } else {
04209         mResult += i18n("Repeats forever\n");
04210       }
04211     }
04212   }
04213   QString details = event->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( Todo *todo )
04221 {
04222   mResult = mailBodyIncidence( todo );
04223 
04224   if ( todo->hasStartDate() ) {
04225     mResult += i18n("Start Date: %1\n").
04226                arg( IncidenceFormatter::dateToString( todo->dtStart( false ), true ) );
04227     if ( !todo->doesFloat() ) {
04228       mResult += i18n("Start Time: %1\n").
04229                  arg( IncidenceFormatter::timeToString( todo->dtStart( false ),true ) );
04230     }
04231   }
04232   if ( todo->hasDueDate() ) {
04233     mResult += i18n("Due Date: %1\n").
04234                arg( IncidenceFormatter::dateToString( todo->dtDue(), true ) );
04235     if ( !todo->doesFloat() ) {
04236       mResult += i18n("Due Time: %1\n").
04237                  arg( IncidenceFormatter::timeToString( todo->dtDue(), true ) );
04238     }
04239   }
04240   QString details = todo->description();
04241   if ( !details.isEmpty() ) {
04242     mResult += i18n("Details:\n%1\n").arg( details );
04243   }
04244   return !mResult.isEmpty();
04245 }
04246 
04247 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
04248 {
04249   mResult = mailBodyIncidence( journal );
04250   mResult += i18n("Date: %1\n").
04251              arg( IncidenceFormatter::dateToString( journal->dtStart(), true ) );
04252   if ( !journal->doesFloat() ) {
04253     mResult += i18n("Time: %1\n").
04254                arg( IncidenceFormatter::timeToString( journal->dtStart(), true ) );
04255   }
04256   if ( !journal->description().isEmpty() )
04257     mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() );
04258   return !mResult.isEmpty();
04259 }
04260 
04261 
04262 
04263 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
04264 {
04265   if ( !incidence )
04266     return QString::null;
04267 
04268   MailBodyVisitor v;
04269   if ( v.act( incidence ) ) {
04270     return v.result();
04271   }
04272   return QString::null;
04273 }
04274 
04275 static QString recurEnd( Incidence *incidence )
04276 {
04277   QString endstr;
04278   if ( incidence->doesFloat() ) {
04279     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
04280   } else {
04281     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
04282   }
04283   return endstr;
04284 }
04285 
04286 /************************************
04287  *  More static formatting functions
04288  ************************************/
04289 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
04290 {
04291   if ( !incidence->doesRecur() ) {
04292     return i18n( "No recurrence" );
04293   }
04294   QStringList dayList;
04295   dayList.append( i18n( "31st Last" ) );
04296   dayList.append( i18n( "30th Last" ) );
04297   dayList.append( i18n( "29th Last" ) );
04298   dayList.append( i18n( "28th Last" ) );
04299   dayList.append( i18n( "27th Last" ) );
04300   dayList.append( i18n( "26th Last" ) );
04301   dayList.append( i18n( "25th Last" ) );
04302   dayList.append( i18n( "24th Last" ) );
04303   dayList.append( i18n( "23rd Last" ) );
04304   dayList.append( i18n( "22nd Last" ) );
04305   dayList.append( i18n( "21st Last" ) );
04306   dayList.append( i18n( "20th Last" ) );
04307   dayList.append( i18n( "19th Last" ) );
04308   dayList.append( i18n( "18th Last" ) );
04309   dayList.append( i18n( "17th Last" ) );
04310   dayList.append( i18n( "16th Last" ) );
04311   dayList.append( i18n( "15th Last" ) );
04312   dayList.append( i18n( "14th Last" ) );
04313   dayList.append( i18n( "13th Last" ) );
04314   dayList.append( i18n( "12th Last" ) );
04315   dayList.append( i18n( "11th Last" ) );
04316   dayList.append( i18n( "10th Last" ) );
04317   dayList.append( i18n( "9th Last" ) );
04318   dayList.append( i18n( "8th Last" ) );
04319   dayList.append( i18n( "7th Last" ) );
04320   dayList.append( i18n( "6th Last" ) );
04321   dayList.append( i18n( "5th Last" ) );
04322   dayList.append( i18n( "4th Last" ) );
04323   dayList.append( i18n( "3rd Last" ) );
04324   dayList.append( i18n( "2nd Last" ) );
04325   dayList.append( i18n( "last day of the month", "Last" ) );
04326   dayList.append( i18n( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
04327   dayList.append( i18n( "1st" ) );
04328   dayList.append( i18n( "2nd" ) );
04329   dayList.append( i18n( "3rd" ) );
04330   dayList.append( i18n( "4th" ) );
04331   dayList.append( i18n( "5th" ) );
04332   dayList.append( i18n( "6th" ) );
04333   dayList.append( i18n( "7th" ) );
04334   dayList.append( i18n( "8th" ) );
04335   dayList.append( i18n( "9th" ) );
04336   dayList.append( i18n( "10th" ) );
04337   dayList.append( i18n( "11th" ) );
04338   dayList.append( i18n( "12th" ) );
04339   dayList.append( i18n( "13th" ) );
04340   dayList.append( i18n( "14th" ) );
04341   dayList.append( i18n( "15th" ) );
04342   dayList.append( i18n( "16th" ) );
04343   dayList.append( i18n( "17th" ) );
04344   dayList.append( i18n( "18th" ) );
04345   dayList.append( i18n( "19th" ) );
04346   dayList.append( i18n( "20th" ) );
04347   dayList.append( i18n( "21st" ) );
04348   dayList.append( i18n( "22nd" ) );
04349   dayList.append( i18n( "23rd" ) );
04350   dayList.append( i18n( "24th" ) );
04351   dayList.append( i18n( "25th" ) );
04352   dayList.append( i18n( "26th" ) );
04353   dayList.append( i18n( "27th" ) );
04354   dayList.append( i18n( "28th" ) );
04355   dayList.append( i18n( "29th" ) );
04356   dayList.append( i18n( "30th" ) );
04357   dayList.append( i18n( "31st" ) );
04358 
04359   int weekStart = KGlobal::locale()->weekStartDay();
04360   QString dayNames;
04361 
04362   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
04363 
04364   Recurrence *recur = incidence->recurrence();
04365 
04366   QString recurStr;
04367   switch ( recur->recurrenceType() ) {
04368   case Recurrence::rNone:
04369     return i18n( "No recurrence" );
04370 
04371   case Recurrence::rMinutely:
04372     recurStr = i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() );
04373     if ( recur->duration() != -1 ) {
04374       recurStr = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
04375       if ( recur->duration() >  0 ) {
04376         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04377       }
04378     }
04379     break;
04380 
04381   case Recurrence::rHourly:
04382     recurStr = i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() );
04383     if ( recur->duration() != -1 ) {
04384       recurStr = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
04385       if ( recur->duration() >  0 ) {
04386         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04387       }
04388     }
04389     break;
04390 
04391   case Recurrence::rDaily:
04392     recurStr = i18n( "Recurs daily", "Recurs every %n days", recur->frequency() );
04393     if ( recur->duration() != -1 ) {
04394       recurStr = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
04395       if ( recur->duration() >  0 ) {
04396         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04397       }
04398     }
04399     break;
04400 
04401   case Recurrence::rWeekly:
04402   {
04403     recurStr = i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() );
04404 
04405     bool addSpace = false;
04406     for ( int i = 0; i < 7; ++i ) {
04407       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
04408         if ( addSpace ) {
04409           dayNames.append( i18n( "separator for list of days", ", " ) );
04410         }
04411         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, true ) );
04412         addSpace = true;
04413       }
04414     }
04415     if ( dayNames.isEmpty() ) {
04416       dayNames = i18n( "Recurs weekly on no days", "no days" );
04417     }
04418     if ( recur->duration() != -1 ) {
04419       recurStr = i18n( "%1 on %2 until %3" ).
04420                  arg( recurStr ).arg( dayNames ).arg( recurEnd( incidence ) );
04421       if ( recur->duration() >  0 ) {
04422         recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04423       }
04424     } else {
04425       recurStr = i18n( "%1 on %2" ).arg( recurStr ).arg( dayNames );
04426     }
04427     break;
04428   }
04429 
04430   case Recurrence::rMonthlyPos:
04431   {
04432     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
04433 
04434     if ( !recur->monthPositions().isEmpty() ) {
04435       KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
04436       if ( recur->duration() != -1 ) {
04437         recurStr = i18n( "%1 on the %2 %3 until %4" ).
04438                    arg( recurStr ).
04439                    arg( dayList[rule.pos() + 31] ).
04440                    arg( calSys->weekDayName( rule.day(), false ) ).
04441                    arg( recurEnd( incidence ) );
04442         if ( recur->duration() >  0 ) {
04443           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04444         }
04445       } else {
04446         recurStr = i18n( "%1 on the %2 %3" ).
04447                    arg( recurStr ).
04448                    arg( dayList[rule.pos() + 31] ).
04449                    arg( calSys->weekDayName( rule.day(), false ) );
04450       }
04451     }
04452     break;
04453   }
04454 
04455   case Recurrence::rMonthlyDay:
04456   {
04457     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
04458 
04459     if ( !recur->monthDays().isEmpty() ) {
04460       int days = recur->monthDays()[0];
04461       if ( recur->duration() != -1 ) {
04462         recurStr = i18n( "%1 on the %2 day until %3" ).
04463                    arg( recurStr ).
04464                    arg( dayList[days + 31] ).
04465                    arg( recurEnd( incidence ) );
04466         if ( recur->duration() >  0 ) {
04467           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04468         }
04469       } else {
04470         recurStr = i18n( "%1 on the %2 day" ).arg( recurStr ).arg( dayList[days + 31] );
04471       }
04472     }
04473     break;
04474   }
04475   case Recurrence::rYearlyMonth:
04476   {
04477     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
04478 
04479     if ( recur->duration() != -1 ) {
04480       if ( !recur->yearDates().isEmpty() ) {
04481         recurStr = i18n( "%1 on %2 %3 until %4" ).
04482                    arg( recurStr ).
04483                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
04484                    arg( dayList[ recur->yearDates()[0] + 31 ] ).
04485                    arg( recurEnd( incidence ) );
04486         if ( recur->duration() >  0 ) {
04487           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04488         }
04489       }
04490     } else {
04491       if ( !recur->yearDates().isEmpty() ) {
04492         recurStr = i18n( "%1 on %2 %3" ).
04493                    arg( recurStr ).
04494                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
04495                    arg( dayList[ recur->yearDates()[0] + 31 ] );
04496       } else {
04497         if ( !recur->yearMonths().isEmpty() ) {
04498           recurStr = i18n( "Recurs yearly on %1 %2" ).
04499                      arg( calSys->monthName( recur->yearMonths()[0],
04500                                              recur->startDate().year() ) ).
04501                      arg( dayList[ recur->startDate().day() + 31 ] );
04502         } else {
04503           recurStr = i18n( "Recurs yearly on %1 %2" ).
04504                      arg( calSys->monthName( recur->startDate().month(),
04505                                              recur->startDate().year() ) ).
04506                      arg( dayList[ recur->startDate().day() + 31 ] );
04507         }
04508       }
04509     }
04510     break;
04511   }
04512   case Recurrence::rYearlyDay:
04513   {
04514     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
04515     if ( !recur->yearDays().isEmpty() ) {
04516       if ( recur->duration() != -1 ) {
04517         recurStr = i18n( "%1 on day %2 until %3" ).
04518                    arg( recurStr ).
04519                    arg( recur->yearDays()[0] ).
04520                    arg( recurEnd( incidence ) );
04521         if ( recur->duration() >  0 ) {
04522           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04523         }
04524       } else {
04525         recurStr = i18n( "%1 on day %2" ).arg( recurStr ).arg( recur->yearDays()[0] );
04526       }
04527     }
04528     break;
04529   }
04530   case Recurrence::rYearlyPos:
04531   {
04532     recurStr = i18n( "Every year", "Every %n years", recur->frequency() );
04533     if ( !recur->yearPositions().isEmpty() && !recur->yearMonths().isEmpty() ) {
04534       KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
04535       if ( recur->duration() != -1 ) {
04536         recurStr = i18n( "%1 on the %2 %3 of %4 until %5" ).
04537                    arg( recurStr ).
04538                    arg( dayList[rule.pos() + 31] ).
04539                    arg( calSys->weekDayName( rule.day(), false ) ).
04540                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
04541                    arg( recurEnd( incidence ) );
04542         if ( recur->duration() >  0 ) {
04543           recurStr += i18n( " (%1 occurrences)" ).arg( recur->duration() );
04544         }
04545       } else {
04546         recurStr = i18n( "%1 on the %2 %3 of %4" ).
04547                    arg( recurStr ).
04548                    arg( dayList[rule.pos() + 31] ).
04549                    arg( calSys->weekDayName( rule.day(), false ) ).
04550                    arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
04551       }
04552     }
04553    break;
04554   }
04555   }
04556 
04557   if ( recurStr.isEmpty() ) {
04558     recurStr = i18n( "Incidence recurs" );
04559   }
04560   // Now, append the EXDATEs
04561   DateTimeList l = recur->exDateTimes();
04562   DateTimeList::ConstIterator il;
04563   QStringList exStr;
04564   for ( il = l.constBegin(); il != l.constEnd(); ++il ) {
04565     switch ( recur->recurrenceType() ) {
04566     case Recurrence::rMinutely:
04567       exStr << i18n( "minute %1" ).arg( (*il).time().minute() );
04568       break;
04569     case Recurrence::rHourly:
04570       exStr << KGlobal::locale()->formatTime( (*il).time() );
04571       break;
04572     case Recurrence::rDaily:
04573       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04574       break;
04575     case Recurrence::rWeekly:
04576       exStr << calSys->weekDayName( (*il).date(), true );
04577       break;
04578     case Recurrence::rMonthlyPos:
04579       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04580       break;
04581     case Recurrence::rMonthlyDay:
04582       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04583       break;
04584     case Recurrence::rYearlyMonth:
04585       exStr << calSys->monthName( (*il).date(), false );
04586       break;
04587     case Recurrence::rYearlyDay:
04588       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04589       break;
04590     case Recurrence::rYearlyPos:
04591       exStr << KGlobal::locale()->formatDate( (*il).date(), true );
04592       break;
04593     }
04594   }
04595 
04596   DateList d = recur->exDates();
04597   DateList::ConstIterator dl;
04598   for ( dl = d.constBegin(); dl != d.constEnd(); ++dl ) {
04599     switch ( recur->recurrenceType() ) {
04600     case Recurrence::rDaily:
04601       exStr << KGlobal::locale()->formatDate( (*dl), true );
04602       break;
04603     case Recurrence::rWeekly:
04604       // exStr << calSys->weekDayName( (*dl), true );
04605       // kolab/issue4735, should be ( excluding 3 days ), instead of excluding( Fr,Fr,Fr )
04606       if ( exStr.isEmpty() )
04607         exStr << i18n( "1 day", "%n days", recur->exDates().count() );
04608       break;
04609     case Recurrence::rMonthlyPos:
04610       exStr << KGlobal::locale()->formatDate( (*dl), true );
04611       break;
04612     case Recurrence::rMonthlyDay:
04613       exStr << KGlobal::locale()->formatDate( (*dl), true );
04614       break;
04615     case Recurrence::rYearlyMonth:
04616       exStr << calSys->monthName( (*dl), false );
04617       break;
04618     case Recurrence::rYearlyDay:
04619       exStr << KGlobal::locale()->formatDate( (*dl), true );
04620       break;
04621     case Recurrence::rYearlyPos:
04622       exStr << KGlobal::locale()->formatDate( (*dl), true );
04623       break;
04624     }
04625   }
04626 
04627   if ( !exStr.isEmpty() ) {
04628     recurStr = i18n( "%1 (excluding %2)" ).arg( recurStr, exStr.join( "," ) );
04629   }
04630 
04631   return recurStr;
04632 }
04633 
04634 QString IncidenceFormatter::timeToString( const QDateTime &date, bool shortfmt )
04635 {
04636   return KGlobal::locale()->formatTime( date.time(), !shortfmt );
04637 }
04638 
04639 QString IncidenceFormatter::dateToString( const QDateTime &date, bool shortfmt )
04640 {
04641   return
04642     KGlobal::locale()->formatDate( date.date(), shortfmt );
04643 }
04644 
04645 QString IncidenceFormatter::dateTimeToString( const QDateTime &date,
04646                                               bool allDay, bool shortfmt )
04647 {
04648   if ( allDay ) {
04649     return dateToString( date, shortfmt );
04650   }
04651 
04652   return  KGlobal::locale()->formatDateTime( date, shortfmt );
04653 }
04654 
04655 QString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence )
04656 {
04657   if ( !calendar || !incidence ) {
04658     return QString::null;
04659   }
04660 
04661   CalendarResources *calendarResource = dynamic_cast<CalendarResources*>( calendar );
04662   if ( !calendarResource ) {
04663     return QString::null;
04664   }
04665 
04666   ResourceCalendar *resourceCalendar = calendarResource->resource( incidence );
04667   if ( resourceCalendar ) {
04668     if ( !resourceCalendar->subresources().isEmpty() ) {
04669       QString subRes = resourceCalendar->subresourceIdentifier( incidence );
04670       if ( subRes.isEmpty() ) {
04671         return resourceCalendar->resourceName();
04672       } else {
04673         return resourceCalendar->labelForSubresource( subRes );
04674       }
04675     }
04676     return resourceCalendar->resourceName();
04677   }
04678 
04679   return QString::null;
04680 }
04681 
04682 static QString secs2Duration( int secs )
04683 {
04684   QString tmp;
04685   int days = secs / 86400;
04686   if ( days > 0 ) {
04687     tmp += i18n( "1 day", "%n days", days );
04688     tmp += ' ';
04689     secs -= ( days * 86400 );
04690   }
04691   int hours = secs / 3600;
04692   if ( hours > 0 ) {
04693     tmp += i18n( "1 hour", "%n hours", hours );
04694     tmp += ' ';
04695     secs -= ( hours * 3600 );
04696   }
04697   int mins = secs / 60;
04698   if ( mins > 0 ) {
04699     tmp += i18n( "1 minute", "%n minutes",  mins );
04700   }
04701   return tmp;
04702 }
04703 
04704 QString IncidenceFormatter::durationString( Incidence *incidence )
04705 {
04706   QString tmp;
04707   if ( incidence->type() == "Event" ) {
04708     Event *event = static_cast<Event *>( incidence );
04709     if ( event->hasEndDate() ) {
04710       if ( !event->doesFloat() ) {
04711         tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
04712       } else {
04713         tmp = i18n( "1 day", "%n days",
04714                     event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
04715       }
04716     } else {
04717       tmp = i18n( "forever" );
04718     }
04719   } else if ( incidence->type() == "Todo" ) {
04720     Todo *todo = static_cast<Todo *>( incidence );
04721     if ( todo->hasDueDate() ) {
04722       if ( todo->hasStartDate() ) {
04723         if ( !todo->doesFloat() ) {
04724           tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
04725         } else {
04726           tmp = i18n( "1 day", "%n days",
04727                       todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
04728         }
04729       }
04730     }
04731   }
04732   return tmp;
04733 }
04734 
04735 QStringList IncidenceFormatter::reminderStringList( Incidence *incidence, bool shortfmt )
04736 {
04737   //TODO: implement shortfmt=false
04738   Q_UNUSED( shortfmt );
04739 
04740   QStringList reminderStringList;
04741 
04742   if ( incidence ) {
04743     Alarm::List alarms = incidence->alarms();
04744     Alarm::List::ConstIterator it;
04745     for ( it = alarms.begin(); it != alarms.end(); ++it ) {
04746       Alarm *alarm = *it;
04747       int offset = 0;
04748       QString remStr, atStr, offsetStr;
04749       if ( alarm->hasTime() ) {
04750         offset = 0;
04751         if ( alarm->time().isValid() ) {
04752           atStr = KGlobal::locale()->formatDateTime( alarm->time() );
04753         }
04754       } else if ( alarm->hasStartOffset() ) {
04755         offset = alarm->startOffset().asSeconds();
04756         if ( offset < 0 ) {
04757           offset = -offset;
04758           offsetStr = i18n( "N days/hours/minutes before the start datetime",
04759                             "%1 before the start" );
04760         } else if ( offset > 0 ) {
04761           offsetStr = i18n( "N days/hours/minutes after the start datetime",
04762                             "%1 after the start" );
04763         } else { //offset is 0
04764           if ( incidence->dtStart().isValid() ) {
04765             atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() );
04766           }
04767         }
04768       } else if ( alarm->hasEndOffset() ) {
04769         offset = alarm->endOffset().asSeconds();
04770         if ( offset < 0 ) {
04771           offset = -offset;
04772           if ( incidence->type() == "Todo" ) {
04773             offsetStr = i18n( "N days/hours/minutes before the due datetime",
04774                               "%1 before the to-do is due" );
04775           } else {
04776             offsetStr = i18n( "N days/hours/minutes before the end datetime",
04777                               "%1 before the end" );
04778           }
04779         } else if ( offset > 0 ) {
04780           if ( incidence->type() == "Todo" ) {
04781             offsetStr = i18n( "N days/hours/minutes after the due datetime",
04782                               "%1 after the to-do is due" );
04783           } else {
04784             offsetStr = i18n( "N days/hours/minutes after the end datetime",
04785                               "%1 after the end" );
04786           }
04787         } else { //offset is 0
04788           if ( incidence->type() == "Todo" ) {
04789             Todo *t = static_cast<Todo *>( incidence );
04790             if ( t->dtDue().isValid() ) {
04791               atStr = KGlobal::locale()->formatDateTime( t->dtDue() );
04792             }
04793           } else {
04794             Event *e = static_cast<Event *>( incidence );
04795             if ( e->dtEnd().isValid() ) {
04796               atStr = KGlobal::locale()->formatDateTime( e->dtEnd() );
04797             }
04798           }
04799         }
04800       }
04801       if ( offset == 0 ) {
04802         if ( !atStr.isEmpty() ) {
04803           remStr = i18n( "reminder occurs at datetime", "at %1" ).arg( atStr );
04804         }
04805       } else {
04806         remStr = offsetStr.arg( secs2Duration( offset ) );
04807       }
04808 
04809       if ( alarm->repeatCount() > 0 ) {
04810         QString countStr = i18n( "repeats once", "repeats %n times", alarm->repeatCount() );
04811         QString intervalStr = i18n( "interval is N days/hours/minutes", "interval is %1" ).
04812                               arg( secs2Duration( alarm->snoozeTime().asSeconds() ) );
04813         QString repeatStr = i18n( "(repeat string, interval string)", "(%1, %2)" ).
04814                             arg( countStr, intervalStr );
04815         remStr = remStr + ' ' + repeatStr;
04816 
04817       }
04818       reminderStringList << remStr;
04819     }
04820   }
04821 
04822   return reminderStringList;
04823 }
KDE Home | KDE Accessibility Home | Description of Access Keys