libkcal

incidenceformatter.cpp

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