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 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 QString firstAttendeeName( Incidence *incidence, const QString &defName )
00138 {
00139   QString name;
00140   if ( !incidence ) {
00141     return name;
00142   }
00143 
00144   Attendee::List attendees = incidence->attendees();
00145   if( attendees.count() > 0 ) {
00146     Attendee *attendee = *attendees.begin();
00147     name = attendee->name();
00148     if ( name.isEmpty() ) {
00149       name = attendee->email();
00150     }
00151     if ( name.isEmpty() ) {
00152       name = defName;
00153     }
00154   }
00155   return name;
00156 }
00157 
00158 /*******************************************************************
00159  *  Helper functions for the extensive display (display viewer)
00160  *******************************************************************/
00161 
00162 static QString displayViewLinkPerson( const QString& email, QString name, QString uid )
00163 {
00164   // Make the search, if there is an email address to search on,
00165   // and either name or uid is missing
00166   if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
00167     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
00168     KABC::Addressee::List addressList = add_book->findByEmail( email );
00169     if ( !addressList.isEmpty() ) {
00170       KABC::Addressee o = addressList.first();
00171       if ( !o.isEmpty() && addressList.size() < 2 ) {
00172         if ( name.isEmpty() ) {
00173           // No name set, so use the one from the addressbook
00174           name = o.formattedName();
00175         }
00176         uid = o.uid();
00177       } else {
00178         // Email not found in the addressbook. Don't make a link
00179         uid = QString::null;
00180       }
00181     }
00182   }
00183   kdDebug(5850) << "formatAttendees: uid = " << uid << endl;
00184 
00185   // Show the attendee
00186   QString tmpString;
00187   if ( !uid.isEmpty() ) {
00188     // There is a UID, so make a link to the addressbook
00189     if ( name.isEmpty() ) {
00190       // Use the email address for text
00191       tmpString += htmlAddLink( "uid:" + uid, email );
00192     } else {
00193       tmpString += htmlAddLink( "uid:" + uid, name );
00194     }
00195   } else {
00196     // No UID, just show some text
00197     tmpString += ( name.isEmpty() ? email : name );
00198   }
00199 
00200   // Make the mailto link
00201   if ( !email.isEmpty() ) {
00202     KURL mailto;
00203     mailto.setProtocol( "mailto" );
00204     mailto.setPath( email );
00205     const QString iconPath =
00206       KGlobal::iconLoader()->iconPath( "mail_new", KIcon::Small );
00207     tmpString += htmlAddLink( mailto.url(),
00208                               "<img valign=\"top\" src=\"" + iconPath + "\">" );
00209   }
00210 
00211   return tmpString;
00212 }
00213 
00214 static QString displayViewFormatAttendees( Incidence *incidence )
00215 {
00216   QString tmpStr;
00217   Attendee::List attendees = incidence->attendees();
00218 
00219   // Add organizer link
00220   tmpStr += "<tr>";
00221   tmpStr += "<td><b>" + i18n( "Organizer:" ) + "</b></td>";
00222   tmpStr += "<td>" +
00223             displayViewLinkPerson( incidence->organizer().email(),
00224                                    incidence->organizer().name(),
00225                                    QString::null ) +
00226             "</td>";
00227   tmpStr += "</tr>";
00228 
00229   // Add attendees links
00230   tmpStr += "<tr>";
00231   tmpStr += "<td><b>" + i18n( "Attendees:" ) + "</b></td>";
00232   tmpStr += "<td>";
00233   Attendee::List::ConstIterator it;
00234   for( it = attendees.begin(); it != attendees.end(); ++it ) {
00235     Attendee *a = *it;
00236     if ( a->email() == incidence->organizer().email() ) {
00237       // skip attendee that is also the organizer
00238       continue;
00239     }
00240     tmpStr += displayViewLinkPerson( a->email(), a->name(), a->uid() );
00241     if ( !a->delegator().isEmpty() ) {
00242       tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
00243     }
00244     if ( !a->delegate().isEmpty() ) {
00245       tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
00246     }
00247     tmpStr += "<br>";
00248   }
00249   if ( tmpStr.endsWith( "<br>" ) ) {
00250     tmpStr.truncate( tmpStr.length() - 4 );
00251   }
00252 
00253   tmpStr += "</td>";
00254   tmpStr += "</tr>";
00255   return tmpStr;
00256 }
00257 
00258 static QString displayViewFormatAttachments( Incidence *incidence )
00259 {
00260   QString tmpStr;
00261   Attachment::List as = incidence->attachments();
00262   Attachment::List::ConstIterator it;
00263   uint count = 0;
00264   for( it = as.begin(); it != as.end(); ++it ) {
00265     count++;
00266     if ( (*it)->isUri() ) {
00267       QString name;
00268       if ( (*it)->uri().startsWith( "kmail:" ) ) {
00269         name = i18n( "Show mail" );
00270       } else {
00271         name = (*it)->label();
00272       }
00273       tmpStr += htmlAddLink( (*it)->uri(), name );
00274     } else {
00275       tmpStr += (*it)->label();
00276     }
00277     if ( count < as.count() ) {
00278       tmpStr += "<br>";
00279     }
00280   }
00281   return tmpStr;
00282 }
00283 
00284 static QString displayViewFormatCategories( Incidence *incidence )
00285 {
00286   return incidence->categoriesStr();
00287 }
00288 
00289 static QString displayViewFormatCreationDate( Incidence *incidence )
00290 {
00291   return i18n( "Creation date: %1" ).
00292     arg( IncidenceFormatter::dateTimeToString( incidence->created(), false, true ) );
00293 }
00294 
00295 static QString displayViewFormatBirthday( Event *event )
00296 {
00297   if ( !event ) {
00298     return  QString::null;
00299   }
00300   if ( event->customProperty("KABC","BIRTHDAY") != "YES" ) {
00301     return QString::null;
00302   }
00303 
00304   QString uid = event->customProperty("KABC","UID-1");
00305   QString name = event->customProperty("KABC","NAME-1");
00306   QString email= event->customProperty("KABC","EMAIL-1");
00307 
00308   QString tmpStr = displayViewLinkPerson( email, name, uid );
00309 
00310   if ( event->customProperty( "KABC", "ANNIVERSARY") == "YES" ) {
00311     uid = event->customProperty("KABC","UID-2");
00312     name = event->customProperty("KABC","NAME-2");
00313     email= event->customProperty("KABC","EMAIL-2");
00314     tmpStr += "<br>";
00315     tmpStr += displayViewLinkPerson( email, name, uid );
00316   }
00317 
00318   return tmpStr;
00319 }
00320 
00321 static QString displayViewFormatHeader( Incidence *incidence )
00322 {
00323   QString tmpStr = "<table><tr>";
00324 
00325   // show icons
00326   {
00327     tmpStr += "<td>";
00328 
00329     if ( incidence->type() == "Event" ) {
00330       QString iconPath;
00331       if ( incidence->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) {
00332         if ( incidence->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00333           iconPath =
00334             KGlobal::iconLoader()->iconPath( "calendaranniversary", KIcon::Small );
00335         } else {
00336           iconPath = KGlobal::iconLoader()->iconPath( "calendarbirthday", KIcon::Small );
00337         }
00338       } else {
00339         iconPath = KGlobal::iconLoader()->iconPath( "appointment", KIcon::Small );
00340       }
00341       tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
00342     }
00343     if ( incidence->type() == "Todo" ) {
00344       tmpStr += "<img valign=\"top\" src=\"" +
00345                 KGlobal::iconLoader()->iconPath( "todo", KIcon::Small ) +
00346                 "\">";
00347     }
00348     if ( incidence->type() == "Journal" ) {
00349       tmpStr += "<img valign=\"top\" src=\"" +
00350                 KGlobal::iconLoader()->iconPath( "journal", KIcon::Small ) +
00351                 "\">";
00352     }
00353     if ( incidence->isAlarmEnabled() ) {
00354       tmpStr += "<img valign=\"top\" src=\"" +
00355                 KGlobal::iconLoader()->iconPath( "bell", KIcon::Small ) +
00356                 "\">";
00357     }
00358     if ( incidence->doesRecur() ) {
00359       tmpStr += "<img valign=\"top\" src=\"" +
00360                 KGlobal::iconLoader()->iconPath( "recur", KIcon::Small ) +
00361                 "\">";
00362     }
00363     if ( incidence->isReadOnly() ) {
00364       tmpStr += "<img valign=\"top\" src=\"" +
00365                 KGlobal::iconLoader()->iconPath( "readonlyevent", KIcon::Small ) +
00366                 "\">";
00367     }
00368 
00369     tmpStr += "</td>";
00370   }
00371 
00372   tmpStr += "<td>";
00373   tmpStr += "<b><u>" + incidence->summary() + "</u></b>";
00374   tmpStr += "</td>";
00375 
00376   tmpStr += "</tr></table>";
00377 
00378   return tmpStr;
00379 }
00380 
00381 static QString displayViewFormatEvent( Calendar *calendar, Event *event,
00382                                        const QDate &date )
00383 {
00384   if ( !event ) {
00385     return QString::null;
00386   }
00387 
00388   QString tmpStr = displayViewFormatHeader( event );
00389 
00390   tmpStr += "<table>";
00391   tmpStr += "<col width=\"25%\"/>";
00392   tmpStr += "<col width=\"75%\"/>";
00393 
00394   if ( calendar ) {
00395     QString calStr = IncidenceFormatter::resourceString( calendar, event );
00396     if ( !calStr.isEmpty() ) {
00397       tmpStr += "<tr>";
00398       tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00399       tmpStr += "<td>" + calStr + "</td>";
00400       tmpStr += "</tr>";
00401     }
00402   }
00403 
00404   if ( !event->location().isEmpty() ) {
00405     tmpStr += "<tr>";
00406     tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
00407     tmpStr += "<td>" + event->location() + "</td>";
00408     tmpStr += "</tr>";
00409   }
00410 
00411   tmpStr += "<tr>";
00412   QDateTime startDt = event->dtStart();
00413   QDateTime endDt = event->dtEnd();
00414   if ( event->doesRecur() ) {
00415     if ( date.isValid() ) {
00416       QDateTime dt( date, QTime( 0, 0, 0 ) );
00417       int diffDays = startDt.daysTo( dt );
00418       dt = dt.addSecs( -1 );
00419       startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() );
00420       if ( event->hasEndDate() ) {
00421         endDt = endDt.addDays( diffDays );
00422         if ( startDt > endDt ) {
00423           startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() );
00424           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
00425         }
00426       }
00427     }
00428   }
00429   if ( event->doesFloat() ) {
00430     if ( event->isMultiDay() ) {
00431       tmpStr += "<td><b>" + i18n( "Time:" ) + "</b></td>";
00432       tmpStr += "<td>" +
00433                 i18n("<beginTime> - <endTime>","%1 - %2").
00434                 arg( IncidenceFormatter::dateToString( startDt, false ) ).
00435                 arg( IncidenceFormatter::dateToString( endDt, false ) ) +
00436                 "</td>";
00437     } else {
00438       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00439       tmpStr += "<td>" +
00440                 i18n("date as string","%1").
00441                 arg( IncidenceFormatter::dateToString( startDt, false ) ) +
00442                 "</td>";
00443     }
00444   } else {
00445     if ( event->isMultiDay() ) {
00446       tmpStr += "<td><b>" + i18n( "Time:" ) + "</b></td>";
00447       tmpStr += "<td>" +
00448                 i18n("<beginTime> - <endTime>","%1 - %2").
00449                 arg( IncidenceFormatter::dateToString( startDt, false ) ).
00450                 arg( IncidenceFormatter::dateToString( endDt, false ) ) +
00451                 "</td>";
00452     } else {
00453       tmpStr += "<td><b>" + i18n( "Time:" ) + "</b></td>";
00454       if ( event->hasEndDate() && startDt != endDt ) {
00455         tmpStr += "<td>" +
00456                   i18n("<beginTime> - <endTime>","%1 - %2").
00457                   arg( IncidenceFormatter::timeToString( startDt, true ) ).
00458                   arg( IncidenceFormatter::timeToString( endDt, true ) ) +
00459                   "</td>";
00460       } else {
00461         tmpStr += "<td>" +
00462                   IncidenceFormatter::timeToString( startDt, true ) +
00463                   "</td>";
00464       }
00465       tmpStr += "</tr><tr>";
00466       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00467       tmpStr += "<td>" +
00468                 i18n("date as string","%1").
00469                 arg( IncidenceFormatter::dateToString( startDt, false ) ) +
00470                 "</td>";
00471     }
00472   }
00473   tmpStr += "</tr>";
00474 
00475   if ( event->customProperty("KABC","BIRTHDAY")== "YES" ) {
00476     tmpStr += "<tr>";
00477     if ( event->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00478       tmpStr += "<td><b>" + i18n( "Anniversary:" ) + "</b></td>";
00479     } else {
00480       tmpStr += "<td><b>" + i18n( "Birthday:" ) + "</b></td>";
00481     }
00482     tmpStr += "<td>" + displayViewFormatBirthday( event ) + "</td>";
00483     tmpStr += "</tr>";
00484     tmpStr += "</table>";
00485     return tmpStr;
00486   }
00487 
00488   if ( !event->description().isEmpty() ) {
00489     tmpStr += "<tr>";
00490     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00491     tmpStr += "<td>" + event->description() + "</td>";
00492     tmpStr += "</tr>";
00493   }
00494 
00495   int categoryCount = event->categories().count();
00496   if ( categoryCount > 0 ) {
00497     tmpStr += "<tr>";
00498     tmpStr += "<td><b>" +
00499               i18n( "Category:", "Categories:", categoryCount ) +
00500               "</b></td>";
00501     tmpStr += "<td>" + displayViewFormatCategories( event ) + "</td>";
00502     tmpStr += "</tr>";
00503   }
00504 
00505   if ( event->doesRecur() ) {
00506     QDateTime dt =
00507       event->recurrence()->getNextDateTime( QDateTime::currentDateTime() );
00508     tmpStr += "<tr>";
00509     tmpStr += "<td><b>" + i18n( "next recurrence", "Next:" ) + "</b></td>";
00510     tmpStr += "<td>" +
00511               ( dt.isValid() ?
00512                 IncidenceFormatter::dateTimeToString( dt, event->doesFloat(), false ) :
00513                 i18n( "no date", "none" ) ) +
00514               "</td>";
00515     tmpStr += "</tr>";
00516   }
00517 
00518   if ( event->attendees().count() > 1 ) {
00519     tmpStr += displayViewFormatAttendees( event );
00520   }
00521 
00522   int attachmentCount = event->attachments().count();
00523   if ( attachmentCount > 0 ) {
00524     tmpStr += "<tr>";
00525     tmpStr += "<td><b>" +
00526               i18n( "Attachment:", "Attachments:", attachmentCount ) +
00527               "</b></td>";
00528     tmpStr += "<td>" + displayViewFormatAttachments( event ) + "</td>";
00529     tmpStr += "</tr>";
00530   }
00531   tmpStr += "</table>";
00532 
00533   tmpStr += "<em>" + displayViewFormatCreationDate( event ) + "</em>";
00534 
00535   return tmpStr;
00536 }
00537 
00538 static QString displayViewFormatTodo( Calendar *calendar, Todo *todo,
00539                                       const QDate &date )
00540 {
00541   if ( !todo ) {
00542     return QString::null;
00543   }
00544 
00545   QString tmpStr = displayViewFormatHeader( todo );
00546 
00547   tmpStr += "<table>";
00548   tmpStr += "<col width=\"25%\"/>";
00549   tmpStr += "<col width=\"75%\"/>";
00550 
00551   if ( calendar ) {
00552     QString calStr = IncidenceFormatter::resourceString( calendar, todo );
00553     if ( !calStr.isEmpty() ) {
00554       tmpStr += "<tr>";
00555       tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00556       tmpStr += "<td>" + calStr + "</td>";
00557       tmpStr += "</tr>";
00558     }
00559   }
00560 
00561   if ( !todo->location().isEmpty() ) {
00562     tmpStr += "<tr>";
00563     tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
00564     tmpStr += "<td>" + todo->location() + "</td>";
00565     tmpStr += "</tr>";
00566   }
00567 
00568   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
00569     QDateTime startDt = todo->dtStart();
00570     if ( todo->doesRecur() ) {
00571       if ( date.isValid() ) {
00572         startDt.setDate( date );
00573       }
00574     }
00575     tmpStr += "<tr>";
00576     tmpStr += "<td><b>" + i18n( "Start:" ) + "</b></td>";
00577     tmpStr += "<td>" +
00578               IncidenceFormatter::dateTimeToString( startDt,
00579                                                     todo->doesFloat(), false ) +
00580               "</td>";
00581     tmpStr += "</tr>";
00582   }
00583 
00584   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
00585     QDateTime dueDt = todo->dtDue();
00586     if ( todo->doesRecur() ) {
00587       if ( date.isValid() ) {
00588         dueDt.addDays( todo->dtDue().date().daysTo( date ) );
00589       }
00590     }
00591     tmpStr += "<tr>";
00592     tmpStr += "<td><b>" + i18n( "Due:" ) + "</b></td>";
00593     tmpStr += "<td>" +
00594               IncidenceFormatter::dateTimeToString( dueDt,
00595                                                     todo->doesFloat(), false ) +
00596               "</td>";
00597     tmpStr += "</tr>";
00598   }
00599 
00600   if ( !todo->description().isEmpty() ) {
00601     tmpStr += "<tr>";
00602     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00603     tmpStr += "<td>" + todo->description() + "</td>";
00604     tmpStr += "</tr>";
00605   }
00606 
00607   int categoryCount = todo->categories().count();
00608   if ( categoryCount > 0 ) {
00609     tmpStr += "<tr>";
00610     tmpStr += "<td><b>" +
00611               i18n( "Category:", "Categories:", categoryCount ) +
00612               "</b></td>";
00613     tmpStr += "<td>" + displayViewFormatCategories( todo ) + "</td>";
00614     tmpStr += "</tr>";
00615   }
00616 
00617   tmpStr += "<tr>";
00618   tmpStr += "<td><b>" + i18n( "Priority:" ) + "</b></td>";
00619   tmpStr += "<td>";
00620   if ( todo->priority() > 0 ) {
00621     tmpStr += QString::number( todo->priority() );
00622   } else {
00623     tmpStr += i18n( "Unspecified" );
00624   }
00625   tmpStr += "</td>";
00626   tmpStr += "</tr>";
00627 
00628   tmpStr += "<tr>";
00629   tmpStr += "<td><b>" + i18n( "Completed:" ) + "</b></td>";
00630   tmpStr += "<td>" + i18n( "%1%" ).arg( todo->percentComplete() ) + "</td>";
00631   tmpStr += "</tr>";
00632 
00633   if ( todo->doesRecur() ) {
00634     QDateTime dt =
00635       todo->recurrence()->getNextDateTime( QDateTime::currentDateTime() );
00636     tmpStr += "<tr>";
00637     tmpStr += "<td><b>" +
00638               i18n( "next recurrence", "Next:" ) +
00639               "</b></td>";
00640     tmpStr += ( dt.isValid() ?
00641                 IncidenceFormatter::dateTimeToString( dt, todo->doesFloat(), false ) :
00642                 i18n( "no date", "none" ) ) +
00643               "</td>";
00644     tmpStr += "</tr>";
00645   }
00646 
00647   if ( todo->attendees().count() > 1 ) {
00648     tmpStr += displayViewFormatAttendees( todo );
00649   }
00650 
00651   int attachmentCount = todo->attachments().count();
00652   if ( attachmentCount > 0 ) {
00653     tmpStr += "<tr>";
00654     tmpStr += "<td><b>" +
00655               i18n( "Attachment:", "Attachments:", attachmentCount ) +
00656               "</b></td>";
00657     tmpStr += "<td>" + displayViewFormatAttachments( todo ) + "</td>";
00658     tmpStr += "</tr>";
00659   }
00660 
00661   tmpStr += "</table>";
00662 
00663   tmpStr += "<em>" + displayViewFormatCreationDate( todo ) + "</em>";
00664 
00665   return tmpStr;
00666 }
00667 
00668 static QString displayViewFormatJournal( Calendar *calendar, Journal *journal )
00669 {
00670   if ( !journal ) {
00671     return QString::null;
00672   }
00673 
00674   QString tmpStr = displayViewFormatHeader( journal );
00675 
00676   tmpStr += "<table>";
00677   tmpStr += "<col width=\"25%\"/>";
00678   tmpStr += "<col width=\"75%\"/>";
00679 
00680   if ( calendar ) {
00681     QString calStr = IncidenceFormatter::resourceString( calendar, journal );
00682     if ( !calStr.isEmpty() ) {
00683       tmpStr += "<tr>";
00684       tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00685       tmpStr += "<td>" + calStr + "</td>";
00686       tmpStr += "</tr>";
00687     }
00688   }
00689 
00690   tmpStr += "<tr>";
00691   tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00692   tmpStr += "<td>" +
00693             IncidenceFormatter::dateToString( journal->dtStart(), false ) +
00694             "</td>";
00695   tmpStr += "</tr>";
00696 
00697   if ( !journal->description().isEmpty() ) {
00698     tmpStr += "<tr>";
00699     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00700     tmpStr += "<td>" + journal->description() + "</td>";
00701     tmpStr += "</tr>";
00702   }
00703 
00704   int categoryCount = journal->categories().count();
00705   if ( categoryCount > 0 ) {
00706     tmpStr += "<tr>";
00707     tmpStr += "<td><b>" +
00708               i18n( "Category:", "Categories:", categoryCount ) +
00709               "</b></td>";
00710     tmpStr += "<td>" + displayViewFormatCategories( journal ) + "</td>";
00711     tmpStr += "</tr>";
00712   }
00713   tmpStr += "</table>";
00714 
00715   tmpStr += "<em>" + displayViewFormatCreationDate( journal ) + "</em>";
00716 
00717   return tmpStr;
00718 }
00719 
00720 static QString displayViewFormatFreeBusy( Calendar * /*calendar*/, FreeBusy *fb )
00721 {
00722   if ( !fb ) {
00723     return QString::null;
00724   }
00725 
00726   QString tmpStr = htmlAddTag( "h2",
00727                                htmlAddTag( "b",
00728                                            i18n("Free/Busy information for %1").
00729                                            arg( fb->organizer().fullName() ) ) );
00730 
00731   tmpStr += htmlAddTag( "h4", i18n("Busy times in date range %1 - %2:").
00732                         arg( IncidenceFormatter::dateToString( fb->dtStart(), true ) ).
00733                         arg( IncidenceFormatter::dateToString( fb->dtEnd(), true ) ) );
00734 
00735   QValueList<Period> periods = fb->busyPeriods();
00736 
00737   QString text = htmlAddTag( "em", htmlAddTag( "b", i18n("Busy:") ) );
00738   QValueList<Period>::iterator it;
00739   for ( it = periods.begin(); it != periods.end(); ++it ) {
00740     Period per = *it;
00741     if ( per.hasDuration() ) {
00742       int dur = per.duration().asSeconds();
00743       QString cont;
00744       if ( dur >= 3600 ) {
00745         cont += i18n("1 hour ", "%n hours ", dur / 3600 );
00746         dur %= 3600;
00747       }
00748       if ( dur >= 60 ) {
00749         cont += i18n("1 minute ", "%n minutes ", dur / 60);
00750         dur %= 60;
00751       }
00752       if ( dur > 0 ) {
00753         cont += i18n("1 second", "%n seconds", dur);
00754       }
00755       text += i18n("startDate for duration", "%1 for %2").
00756               arg( IncidenceFormatter::dateTimeToString( per.start(), false, true ) ).
00757               arg( cont );
00758       text += "<br>";
00759     } else {
00760       if ( per.start().date() == per.end().date() ) {
00761         text += i18n("date, fromTime - toTime ", "%1, %2 - %3").
00762                 arg( IncidenceFormatter::dateToString( per.start().date(), true ) ).
00763                 arg( IncidenceFormatter::timeToString( per.start(), true ) ).
00764                 arg( IncidenceFormatter::timeToString( per.end(), true ) );
00765       } else {
00766         text += i18n("fromDateTime - toDateTime", "%1 - %2").
00767                 arg( IncidenceFormatter::dateTimeToString( per.start(), false, true ) ).
00768                 arg( IncidenceFormatter::dateTimeToString( per.end(), false, true ) );
00769       }
00770       text += "<br>";
00771     }
00772   }
00773   tmpStr += htmlAddTag( "p", text );
00774   return tmpStr;
00775 }
00776 
00777 class IncidenceFormatter::EventViewerVisitor : public IncidenceBase::Visitor
00778 {
00779   public:
00780     EventViewerVisitor()
00781       : mCalendar( 0 ), mResult( "" ) {}
00782 
00783     bool act( Calendar *calendar, IncidenceBase *incidence, const QDate &date )
00784     {
00785       mCalendar = calendar;
00786       mDate = date;
00787       mResult = "";
00788       return incidence->accept( *this );
00789     }
00790     QString result() const { return mResult; }
00791 
00792   protected:
00793     bool visit( Event *event )
00794     {
00795       mResult = displayViewFormatEvent( mCalendar, event, mDate );
00796       return !mResult.isEmpty();
00797     }
00798     bool visit( Todo *todo )
00799     {
00800       mResult = displayViewFormatTodo( mCalendar, todo, mDate );
00801       return !mResult.isEmpty();
00802     }
00803     bool visit( Journal *journal )
00804     {
00805       mResult = displayViewFormatJournal( mCalendar, journal );
00806       return !mResult.isEmpty();
00807     }
00808     bool visit( FreeBusy *fb )
00809     {
00810       mResult = displayViewFormatFreeBusy( mCalendar, fb );
00811       return !mResult.isEmpty();
00812     }
00813 
00814   protected:
00815     Calendar *mCalendar;
00816     QDate mDate;
00817     QString mResult;
00818 };
00819 
00820 QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence )
00821 {
00822   return extensiveDisplayStr( 0, incidence, QDate() );
00823 }
00824 
00825 QString IncidenceFormatter::extensiveDisplayStr( Calendar *calendar,
00826                                                  IncidenceBase *incidence,
00827                                                  const QDate &date )
00828 {
00829   if ( !incidence ) {
00830     return QString::null;
00831   }
00832 
00833   EventViewerVisitor v;
00834   if ( v.act( calendar, incidence, date ) ) {
00835     return v.result();
00836   } else {
00837     return QString::null;
00838   }
00839 }
00840 
00841 /***********************************************************************
00842  *  Helper functions for the body part formatter of kmail (Invitations)
00843  ***********************************************************************/
00844 
00845 static QString string2HTML( const QString& str )
00846 {
00847   return QStyleSheet::convertFromPlainText(str, QStyleSheetItem::WhiteSpaceNormal);
00848 }
00849 
00850 static QString cleanHtml( const QString &html )
00851 {
00852   QRegExp rx( "<body[^>]*>(.*)</body>" );
00853   rx.setCaseSensitive( false );
00854   rx.search( html );
00855   QString body = rx.cap( 1 );
00856 
00857   return QStyleSheet::escape( body.remove( QRegExp( "<[^>]*>" ) ).stripWhiteSpace() );
00858 }
00859 
00860 static QString eventStartTimeStr( Event *event )
00861 {
00862   QString tmp;
00863   if ( !event->doesFloat() ) {
00864     tmp = i18n( "%1: Start Date, %2: Start Time", "%1 %2" ).
00865           arg( IncidenceFormatter::dateToString( event->dtStart(), true ),
00866                IncidenceFormatter::timeToString( event->dtStart(), true ) );
00867   } else {
00868     tmp = i18n( "%1: Start Date", "%1 (all day)" ).
00869           arg( IncidenceFormatter::dateToString( event->dtStart(), true ) );
00870   }
00871   return tmp;
00872 }
00873 
00874 static QString eventEndTimeStr( Event *event )
00875 {
00876   QString tmp;
00877   if ( event->hasEndDate() && event->dtEnd().isValid() ) {
00878     if ( !event->doesFloat() ) {
00879       tmp = i18n( "%1: End Date, %2: End Time", "%1 %2" ).
00880             arg( IncidenceFormatter::dateToString( event->dtEnd(), true ),
00881                  IncidenceFormatter::timeToString( event->dtEnd(), true ) );
00882     } else {
00883       tmp = i18n( "%1: End Date", "%1 (all day)" ).
00884             arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) );
00885     }
00886   }
00887   return tmp;
00888 }
00889 
00890 static QString invitationRow( const QString &cell1, const QString &cell2 )
00891 {
00892   return "<tr><td>" + cell1 + "</td><td>" + cell2 + "</td></tr>\n";
00893 }
00894 
00895 static Attendee *findDelegatedFromMyAttendee( Incidence *incidence )
00896 {
00897   // Return the first attendee that was delegated-from me
00898 
00899   Attendee *attendee = 0;
00900   if ( !incidence ) {
00901     return attendee;
00902   }
00903 
00904   KEMailSettings settings;
00905   QStringList profiles = settings.profiles();
00906   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
00907     settings.setProfile( *it );
00908 
00909     QString delegatorName, delegatorEmail;
00910     Attendee::List attendees = incidence->attendees();
00911     Attendee::List::ConstIterator it2;
00912     for ( it2 = attendees.begin(); it2 != attendees.end(); ++it2 ) {
00913       Attendee *a = *it2;
00914       KPIM::getNameAndMail( a->delegator(), delegatorName, delegatorEmail );
00915       if ( settings.getSetting( KEMailSettings::EmailAddress ) == delegatorEmail ) {
00916         attendee = a;
00917         break;
00918       }
00919     }
00920   }
00921   return attendee;
00922 }
00923 
00924 static Attendee *findMyAttendee( Incidence *incidence )
00925 {
00926   // Return the attendee for the incidence that is probably me
00927 
00928   Attendee *attendee = 0;
00929   if ( !incidence ) {
00930     return attendee;
00931   }
00932 
00933   KEMailSettings settings;
00934   QStringList profiles = settings.profiles();
00935   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
00936     settings.setProfile( *it );
00937 
00938     Attendee::List attendees = incidence->attendees();
00939     Attendee::List::ConstIterator it2;
00940     for ( it2 = attendees.begin(); it2 != attendees.end(); ++it2 ) {
00941       Attendee *a = *it2;
00942       if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) {
00943         attendee = a;
00944         break;
00945       }
00946     }
00947   }
00948   return attendee;
00949 }
00950 
00951 static Attendee *findAttendee( Incidence *incidence, const QString &email )
00952 {
00953   // Search for an attendee by email address
00954 
00955   Attendee *attendee = 0;
00956   if ( !incidence ) {
00957     return attendee;
00958   }
00959 
00960   Attendee::List attendees = incidence->attendees();
00961   Attendee::List::ConstIterator it;
00962   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00963     Attendee *a = *it;
00964     if ( email == a->email() ) {
00965       attendee = a;
00966       break;
00967     }
00968   }
00969   return attendee;
00970 }
00971 
00972 static bool rsvpRequested( Incidence *incidence )
00973 {
00974   if ( !incidence ) {
00975     return false;
00976   }
00977 
00978   //use a heuristic to determine if a response is requested.
00979 
00980   bool rsvp = true; // better send superfluously than not at all
00981   Attendee::List attendees = incidence->attendees();
00982   Attendee::List::ConstIterator it;
00983   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00984     if ( it == attendees.begin() ) {
00985       rsvp = (*it)->RSVP(); // use what the first one has
00986     } else {
00987       if ( (*it)->RSVP() != rsvp ) {
00988         rsvp = true; // they differ, default
00989         break;
00990       }
00991     }
00992   }
00993   return rsvp;
00994 }
00995 
00996 static QString rsvpRequestedStr( bool rsvpRequested, const QString &role )
00997 {
00998   if ( rsvpRequested ) {
00999     if ( role.isEmpty() ) {
01000       return i18n( "Your response is requested" );
01001     } else {
01002       return i18n( "Your response as <b>%1</b> is requested" ).arg( role );
01003     }
01004   } else {
01005     if ( role.isEmpty() ) {
01006       return i18n( "No response is necessary" );
01007     } else {
01008       return i18n( "No response as <b>%1</b> is necessary" ).arg( role );
01009     }
01010   }
01011 }
01012 
01013 static QString myStatusStr( Incidence *incidence )
01014 {
01015   QString ret;
01016   Attendee *a = findMyAttendee( incidence );
01017   if ( a &&
01018        a->status() != Attendee::NeedsAction && a->status() != Attendee::Delegated ) {
01019     ret = i18n( "(<b>Note</b>: the Organizer preset your response to <b>%1</b>)" ).
01020           arg( Attendee::statusName( a->status() ) );
01021   }
01022   return ret;
01023 }
01024 
01025 static QString invitationPerson( const QString& email, QString name, QString uid )
01026 {
01027   // Make the search, if there is an email address to search on,
01028   // and either name or uid is missing
01029   if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
01030     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
01031     KABC::Addressee::List addressList = add_book->findByEmail( email );
01032     if ( !addressList.isEmpty() ) {
01033       KABC::Addressee o = addressList.first();
01034       if ( !o.isEmpty() && addressList.size() < 2 ) {
01035         if ( name.isEmpty() ) {
01036           // No name set, so use the one from the addressbook
01037           name = o.formattedName();
01038         }
01039         uid = o.uid();
01040       } else {
01041         // Email not found in the addressbook. Don't make a link
01042         uid = QString::null;
01043       }
01044     }
01045   }
01046 
01047   // Show the attendee
01048   QString tmpString;
01049   if ( !uid.isEmpty() ) {
01050     // There is a UID, so make a link to the addressbook
01051     if ( name.isEmpty() ) {
01052       // Use the email address for text
01053       tmpString += htmlAddLink( "uid:" + uid, email );
01054     } else {
01055       tmpString += htmlAddLink( "uid:" + uid, name );
01056     }
01057   } else {
01058     // No UID, just show some text
01059     tmpString += ( name.isEmpty() ? email : name );
01060   }
01061   tmpString += '\n';
01062 
01063   // Make the mailto link
01064   if ( !email.isEmpty() ) {
01065     KCal::Person person( name, email );
01066     KURL mailto;
01067     mailto.setProtocol( "mailto" );
01068     mailto.setPath( person.fullName() );
01069     const QString iconPath =
01070       KGlobal::iconLoader()->iconPath( "mail_new", KIcon::Small );
01071     tmpString += htmlAddLink( mailto.url(), "<img src=\"" + iconPath + "\">" )
01072 ;
01073   }
01074   tmpString += "\n";
01075 
01076   return tmpString;
01077 }
01078 
01079 static QString invitationsDetailsIncidence( Incidence *incidence, bool noHtmlMode )
01080 {
01081   // if description and comment -> use both
01082   // if description, but no comment -> use the desc as the comment (and no desc)
01083   // if comment, but no description -> use the comment and no description
01084 
01085   QString html;
01086   QString descr;
01087   QStringList comments;
01088 
01089   if ( incidence->comments().isEmpty() ) {
01090     if ( !incidence->description().isEmpty() ) {
01091       // use description as comments
01092       if ( !QStyleSheet::mightBeRichText( incidence->description() ) ) {
01093         comments << string2HTML( incidence->description() );
01094       } else {
01095         comments << incidence->description();
01096         if ( noHtmlMode ) {
01097           comments[0] = cleanHtml( comments[0] );
01098         }
01099         comments[0] = htmlAddTag( "p", comments[0] );
01100       }
01101     }
01102     //else desc and comments are empty
01103   } else {
01104     // non-empty comments
01105     QStringList cl = incidence->comments();
01106     uint i = 0;
01107     for( QStringList::Iterator it=cl.begin(); it!=cl.end(); ++it ) {
01108       if ( !QStyleSheet::mightBeRichText( *it ) ) {
01109         comments.append( string2HTML( *it ) );
01110       } else {
01111         if ( noHtmlMode ) {
01112           comments.append( cleanHtml( "<body>" + (*it) + "</body>" ) );
01113         } else {
01114           comments.append( *it );
01115         }
01116       }
01117       i++;
01118     }
01119     if ( !incidence->description().isEmpty() ) {
01120       // use description too
01121       if ( !QStyleSheet::mightBeRichText( incidence->description() ) ) {
01122         descr = string2HTML( incidence->description() );
01123       } else {
01124         descr = incidence->description();
01125         if ( noHtmlMode ) {
01126           descr = cleanHtml( descr );
01127         }
01128         descr = htmlAddTag( "p", descr );
01129       }
01130     }
01131   }
01132 
01133   if( !descr.isEmpty() ) {
01134     html += "<p>";
01135     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01136     html += "<tr><td><center>" +
01137             htmlAddTag( "u", i18n( "Description:" ) ) +
01138             "</center></td></tr>";
01139     html += "<tr><td>" + descr + "</td></tr>";
01140     html += "</table>";
01141   }
01142 
01143   if ( !comments.isEmpty() ) {
01144     html += "<p>";
01145     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01146     html += "<tr><td><center>" +
01147             htmlAddTag( "u", i18n( "Comments:" ) ) +
01148             "</center></td></tr>";
01149     html += "<tr><td>";
01150     if ( comments.count() > 1 ) {
01151       html += "<ul>";
01152       for ( uint i=0; i < comments.count(); ++i ) {
01153         html += "<li>" + comments[i] + "</li>";
01154       }
01155       html += "</ul>";
01156     } else {
01157       html += comments[0];
01158     }
01159     html += "</td></tr>";
01160     html += "</table>";
01161   }
01162   return html;
01163 }
01164 
01165 static QString invitationDetailsEvent( Event* event, bool noHtmlMode )
01166 {
01167   // Invitation details are formatted into an HTML table
01168   if ( !event ) {
01169     return QString::null;
01170   }
01171 
01172   QString sSummary = i18n( "Summary unspecified" );
01173   if ( !event->summary().isEmpty() ) {
01174     if ( !QStyleSheet::mightBeRichText( event->summary() ) ) {
01175       sSummary = QStyleSheet::escape( event->summary() );
01176     } else {
01177       sSummary = event->summary();
01178       if ( noHtmlMode ) {
01179         sSummary = cleanHtml( sSummary );
01180       }
01181     }
01182   }
01183 
01184   QString sLocation = i18n( "Location unspecified" );
01185   if ( !event->location().isEmpty() ) {
01186     if ( !QStyleSheet::mightBeRichText( event->location() ) ) {
01187       sLocation = QStyleSheet::escape( event->location() );
01188     } else {
01189       sLocation = event->location();
01190       if ( noHtmlMode ) {
01191         sLocation = cleanHtml( sLocation );
01192       }
01193     }
01194   }
01195 
01196   QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
01197   QString html = QString("<div dir=\"%1\">\n").arg(dir);
01198   html += "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n";
01199 
01200   // Invitation summary & location rows
01201   html += invitationRow( i18n( "What:" ), sSummary );
01202   html += invitationRow( i18n( "Where:" ), sLocation );
01203 
01204   // If a 1 day event
01205   if ( event->dtStart().date() == event->dtEnd().date() ) {
01206     html += invitationRow( i18n( "Date:" ),
01207                            IncidenceFormatter::dateToString( event->dtStart(), false ) );
01208     if ( !event->doesFloat() ) {
01209       html += invitationRow( i18n( "Time:" ),
01210                              IncidenceFormatter::timeToString( event->dtStart(), true ) +
01211                              " - " +
01212                              IncidenceFormatter::timeToString( event->dtEnd(), true ) );
01213     }
01214   } else {
01215     html += invitationRow( i18n( "Starting date of an event", "From:" ),
01216                            IncidenceFormatter::dateToString( event->dtStart(), false ) );
01217     if ( !event->doesFloat() ) {
01218       html += invitationRow( i18n( "Starting time of an event", "At:" ),
01219                              IncidenceFormatter::timeToString( event->dtStart(), true ) );
01220     }
01221     if ( event->hasEndDate() ) {
01222       html += invitationRow( i18n( "Ending date of an event", "To:" ),
01223                              IncidenceFormatter::dateToString( event->dtEnd(), false ) );
01224       if ( !event->doesFloat() ) {
01225         html += invitationRow( i18n( "Starting time of an event", "At:" ),
01226                                IncidenceFormatter::timeToString( event->dtEnd(), true ) );
01227       }
01228     } else {
01229       html += invitationRow( i18n( "Ending date of an event", "To:" ),
01230                              i18n( "no end date specified" ) );
01231     }
01232   }
01233 
01234   // Invitation Duration Row
01235   if ( !event->doesFloat() && event->hasEndDate() ) {
01236     QString tmp;
01237     int secs = event->dtStart().secsTo( event->dtEnd() );
01238     int days = secs / 86400;
01239     if ( days > 0 ) {
01240       tmp += i18n( "1 day", "%n days", days );
01241       tmp += ' ';
01242       secs -= ( days * 86400 );
01243     }
01244     int hours = secs / 3600;
01245     if ( hours > 0 ) {
01246       tmp += i18n( "1 hour", "%n hours", hours );
01247       tmp += ' ';
01248       secs -= ( hours * 3600 );
01249     }
01250     int mins = secs / 60;
01251     if ( mins > 0 ) {
01252       tmp += i18n( "1 minute", "%n minutes",  mins );
01253       tmp += ' ';
01254     }
01255     html += invitationRow( i18n( "Duration:" ), tmp );
01256   }
01257 
01258   if ( event->doesRecur() )
01259     html += invitationRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( event ) );
01260 
01261   html += "</table>\n";
01262   html += invitationsDetailsIncidence( event, noHtmlMode );
01263   html += "</div>\n";
01264 
01265   return html;
01266 }
01267 
01268 static QString invitationDetailsTodo( Todo *todo, bool noHtmlMode )
01269 {
01270   // Task details are formatted into an HTML table
01271   if ( !todo ) {
01272     return QString::null;
01273   }
01274 
01275   QString sSummary = i18n( "Summary unspecified" );
01276   if ( !todo->summary().isEmpty() ) {
01277     if ( !QStyleSheet::mightBeRichText( todo->summary() ) ) {
01278       sSummary = QStyleSheet::escape( todo->summary() );
01279     } else {
01280       sSummary = todo->summary();
01281       if ( noHtmlMode ) {
01282         sSummary = cleanHtml( sSummary );
01283       }
01284     }
01285   }
01286 
01287   QString sLocation = i18n( "Location unspecified" );
01288   if ( !todo->location().isEmpty() ) {
01289     if ( !QStyleSheet::mightBeRichText( todo->location() ) ) {
01290       sLocation = QStyleSheet::escape( todo->location() );
01291     } else {
01292       sLocation = todo->location();
01293       if ( noHtmlMode ) {
01294         sLocation = cleanHtml( sLocation );
01295       }
01296     }
01297   }
01298 
01299   QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
01300   QString html = QString("<div dir=\"%1\">\n").arg(dir);
01301   html += "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n";
01302 
01303   // Invitation summary & location rows
01304   html += invitationRow( i18n( "What:" ), sSummary );
01305   html += invitationRow( i18n( "Where:" ), sLocation );
01306 
01307   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01308     html += invitationRow( i18n( "Start Date:" ),
01309                            IncidenceFormatter::dateToString( todo->dtStart(), false ) );
01310   }
01311   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01312     html += invitationRow( i18n( "Due Date:" ),
01313                            IncidenceFormatter::dateToString( todo->dtDue(), false ) );
01314     if ( !todo->doesFloat() ) {
01315       html += invitationRow( i18n( "Due Time:" ),
01316                              KGlobal::locale()->formatTime( todo->dtDue().time() ) );
01317     }
01318 
01319   } else {
01320     html += invitationRow( i18n( "Due Date:" ),
01321                            i18n( "Due Date: None", "None" ) );
01322   }
01323 
01324   html += "</table></div>\n";
01325   html += invitationsDetailsIncidence( todo, noHtmlMode );
01326 
01327   return html;
01328 }
01329 
01330 static QString invitationDetailsJournal( Journal *journal, bool noHtmlMode )
01331 {
01332   if ( !journal ) {
01333     return QString::null;
01334   }
01335 
01336   QString sSummary = i18n( "Summary unspecified" );
01337   QString sDescr = i18n( "Description unspecified" );
01338   if ( ! journal->summary().isEmpty() ) {
01339     sSummary = journal->summary();
01340     if ( noHtmlMode ) {
01341       sSummary = cleanHtml( sSummary );
01342     }
01343   }
01344   if ( ! journal->description().isEmpty() ) {
01345     sDescr = journal->description();
01346     if ( noHtmlMode ) {
01347       sDescr = cleanHtml( sDescr );
01348     }
01349   }
01350   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
01351   html += invitationRow( i18n( "Summary:" ), sSummary );
01352   html += invitationRow( i18n( "Date:" ),
01353                          IncidenceFormatter::dateToString( journal->dtStart(), false ) );
01354   html += invitationRow( i18n( "Description:" ), sDescr );
01355   html += "</table>\n";
01356   html += invitationsDetailsIncidence( journal, noHtmlMode );
01357 
01358   return html;
01359 }
01360 
01361 static QString invitationDetailsFreeBusy( FreeBusy *fb, bool /*noHtmlMode*/ )
01362 {
01363   if ( !fb )
01364     return QString::null;
01365   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
01366 
01367   html += invitationRow( i18n("Person:"), fb->organizer().fullName() );
01368   html += invitationRow( i18n("Start date:"),
01369                          IncidenceFormatter::dateToString( fb->dtStart(), true ) );
01370   html += invitationRow( i18n("End date:"),
01371                          KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) );
01372   html += "<tr><td colspan=2><hr></td></tr>\n";
01373   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
01374 
01375   QValueList<Period> periods = fb->busyPeriods();
01376 
01377   QValueList<Period>::iterator it;
01378   for ( it = periods.begin(); it != periods.end(); ++it ) {
01379     Period per = *it;
01380     if ( per.hasDuration() ) {
01381       int dur = per.duration().asSeconds();
01382       QString cont;
01383       if ( dur >= 3600 ) {
01384         cont += i18n("1 hour ", "%n hours ", dur / 3600);
01385         dur %= 3600;
01386       }
01387       if ( dur >= 60 ) {
01388         cont += i18n("1 minute", "%n minutes ", dur / 60);
01389         dur %= 60;
01390       }
01391       if ( dur > 0 ) {
01392         cont += i18n("1 second", "%n seconds", dur);
01393       }
01394       html += invitationRow( QString::null, i18n("startDate for duration", "%1 for %2")
01395           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
01396           .arg(cont) );
01397     } else {
01398       QString cont;
01399       if ( per.start().date() == per.end().date() ) {
01400         cont = i18n("date, fromTime - toTime ", "%1, %2 - %3")
01401             .arg( KGlobal::locale()->formatDate( per.start().date() ) )
01402             .arg( KGlobal::locale()->formatTime( per.start().time() ) )
01403             .arg( KGlobal::locale()->formatTime( per.end().time() ) );
01404       } else {
01405         cont = i18n("fromDateTime - toDateTime", "%1 - %2")
01406           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
01407           .arg( KGlobal::locale()->formatDateTime( per.end(), false ) );
01408       }
01409 
01410       html += invitationRow( QString::null, cont );
01411     }
01412   }
01413 
01414   html += "</table>\n";
01415   return html;
01416 }
01417 
01418 static bool replyMeansCounter( Incidence */*incidence*/ )
01419 {
01420   return false;
01435 }
01436 
01437 static QString invitationHeaderEvent( Event *event, ScheduleMessage *msg )
01438 {
01439   if ( !msg || !event )
01440     return QString::null;
01441 
01442   switch ( msg->method() ) {
01443   case Scheduler::Publish:
01444     return i18n( "This invitation has been published" );
01445   case Scheduler::Request:
01446     if ( event->revision() > 0 ) {
01447       return i18n( "This invitation has been updated" );
01448     }
01449     if ( iamOrganizer( event ) ) {
01450       return i18n( "I sent this invitation" );
01451     } else {
01452       if ( !event->organizer().fullName().isEmpty() ) {
01453         return i18n( "You received an invitation from %1" ).
01454           arg( event->organizer().fullName() );
01455       } else {
01456         return i18n( "You received an invitation" );
01457       }
01458     }
01459   case Scheduler::Refresh:
01460     return i18n( "This invitation was refreshed" );
01461   case Scheduler::Cancel:
01462     return i18n( "This invitation has been canceled" );
01463   case Scheduler::Add:
01464     return i18n( "Addition to the invitation" );
01465   case Scheduler::Reply:
01466   {
01467     if ( replyMeansCounter( event ) ) {
01468       return i18n( "%1 makes this counter proposal" ).
01469         arg( firstAttendeeName( event, i18n( "Sender" ) ) );
01470     }
01471 
01472     Attendee::List attendees = event->attendees();
01473     if( attendees.count() == 0 ) {
01474       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01475       return QString::null;
01476     }
01477     if( attendees.count() != 1 ) {
01478       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01479                     << "but is " << attendees.count() << endl;
01480     }
01481     QString attendeeName = firstAttendeeName( event, i18n( "Sender" ) );
01482 
01483     QString delegatorName, dummy;
01484     Attendee* attendee = *attendees.begin();
01485     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
01486     if ( delegatorName.isEmpty() ) {
01487       delegatorName = attendee->delegator();
01488     }
01489 
01490     switch( attendee->status() ) {
01491     case Attendee::NeedsAction:
01492       return i18n( "%1 indicates this invitation still needs some action" ).arg( attendeeName );
01493     case Attendee::Accepted:
01494       if ( delegatorName.isEmpty() ) {
01495         return i18n( "%1 accepts this invitation" ).arg( attendeeName );
01496       } else {
01497         return i18n( "%1 accepts this invitation on behalf of %2" ).
01498           arg( attendeeName ).arg( delegatorName );
01499       }
01500     case Attendee::Tentative:
01501       if ( delegatorName.isEmpty() ) {
01502         return i18n( "%1 tentatively accepts this invitation" ).
01503           arg( attendeeName );
01504       } else {
01505         return i18n( "%1 tentatively accepts this invitation on behalf of %2" ).
01506           arg( attendeeName ).arg( delegatorName );
01507       }
01508     case Attendee::Declined:
01509       if ( delegatorName.isEmpty() ) {
01510         return i18n( "%1 declines this invitation" ).arg( attendeeName );
01511       } else {
01512         return i18n( "%1 declines this invitation on behalf of %2" ).
01513           arg( attendeeName ).arg( delegatorName );
01514       }
01515     case Attendee::Delegated: {
01516       QString delegate, dummy;
01517       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
01518       if ( delegate.isEmpty() ) {
01519         delegate = attendee->delegate();
01520       }
01521       if ( !delegate.isEmpty() ) {
01522         return i18n( "%1 has delegated this invitation to %2" ).
01523           arg( attendeeName ) .arg( delegate );
01524       } else {
01525         return i18n( "%1 has delegated this invitation" ).arg( attendeeName );
01526       }
01527     }
01528     case Attendee::Completed:
01529       return i18n( "This invitation is now completed" );
01530     case Attendee::InProcess:
01531       return i18n( "%1 is still processing the invitation" ).
01532         arg( attendeeName );
01533     default:
01534       return i18n( "Unknown response to this invitation" );
01535     }
01536     break;
01537   }
01538 
01539   case Scheduler::Counter:
01540     return i18n( "%1 makes this counter proposal" ).
01541       arg( firstAttendeeName( event, i18n( "Sender" ) ) );
01542 
01543   case Scheduler::Declinecounter:
01544     return i18n( "%1 declines the counter proposal" ).
01545       arg( firstAttendeeName( event, i18n( "Sender" ) ) );
01546 
01547   case Scheduler::NoMethod:
01548     return i18n("Error: iMIP message with unknown method: '%1'").
01549       arg( msg->method() );
01550   }
01551   return QString::null;
01552 }
01553 
01554 static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg )
01555 {
01556   if ( !msg || !todo ) {
01557     return QString::null;
01558   }
01559 
01560   switch ( msg->method() ) {
01561   case Scheduler::Publish:
01562     return i18n("This task has been published");
01563   case Scheduler::Request:
01564     if ( todo->revision() > 0 ) {
01565       return i18n( "This task has been updated" );
01566     } else {
01567       return i18n( "You have been assigned this task" );
01568     }
01569   case Scheduler::Refresh:
01570     return i18n( "This task was refreshed" );
01571   case Scheduler::Cancel:
01572     return i18n( "This task was canceled" );
01573   case Scheduler::Add:
01574     return i18n( "Addition to the task" );
01575   case Scheduler::Reply:
01576   {
01577     if ( replyMeansCounter( todo ) ) {
01578       return i18n( "%1 makes this counter proposal" ).
01579         arg( firstAttendeeName( todo, i18n( "Sender" ) ) );
01580     }
01581 
01582     Attendee::List attendees = todo->attendees();
01583     if( attendees.count() == 0 ) {
01584       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01585       return QString::null;
01586     }
01587     if( attendees.count() != 1 ) {
01588       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01589                     << "but is " << attendees.count() << endl;
01590     }
01591     QString attendeeName = firstAttendeeName( todo, i18n( "Sender" ) );
01592 
01593     QString delegatorName, dummy;
01594     Attendee* attendee = *attendees.begin();
01595     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
01596     if ( delegatorName.isEmpty() ) {
01597       delegatorName = attendee->delegator();
01598     }
01599 
01600     switch( attendee->status() ) {
01601     case Attendee::NeedsAction:
01602       return i18n( "%1 indicates this task assignment still needs some action" ).arg( attendeeName );
01603     case Attendee::Accepted:
01604       if ( delegatorName.isEmpty() ) {
01605         return i18n( "%1 accepts this task" ).arg( attendeeName );
01606       } else {
01607         return i18n( "%1 accepts this task on behalf of %2" ).
01608           arg( attendeeName ).arg( delegatorName );
01609       }
01610     case Attendee::Tentative:
01611       if ( delegatorName.isEmpty() ) {
01612         return i18n( "%1 tentatively accepts this task" ).
01613           arg( attendeeName );
01614       } else {
01615         return i18n( "%1 tentatively accepts this task on behalf of %2" ).
01616           arg( attendeeName ).arg( delegatorName );
01617       }
01618     case Attendee::Declined:
01619       if ( delegatorName.isEmpty() ) {
01620         return i18n( "%1 declines this task" ).arg( attendeeName );
01621       } else {
01622         return i18n( "%1 declines this task on behalf of %2" ).
01623           arg( attendeeName ).arg( delegatorName );
01624       }
01625     case Attendee::Delegated: {
01626       QString delegate, dummy;
01627       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
01628       if ( delegate.isEmpty() ) {
01629         delegate = attendee->delegate();
01630       }
01631       if ( !delegate.isEmpty() ) {
01632         return i18n( "%1 has delegated this request for the task to %2" ).
01633           arg( attendeeName ).arg( delegate );
01634       } else {
01635         return i18n( "%1 has delegated this request for the task" ).
01636           arg( attendeeName );
01637       }
01638     }
01639     case Attendee::Completed:
01640       return i18n( "The request for this task is now completed" );
01641     case Attendee::InProcess:
01642       return i18n( "%1 is still processing the task" ).
01643         arg( attendeeName );
01644     default:
01645       return i18n( "Unknown response to this task" );
01646     }
01647     break;
01648   }
01649 
01650   case Scheduler::Counter:
01651     return i18n( "%1 makes this counter proposal" ).
01652       arg( firstAttendeeName( todo, i18n( "Sender" ) ) );
01653 
01654   case Scheduler::Declinecounter:
01655     return i18n( "%1 declines the counter proposal" ).
01656       arg( firstAttendeeName( todo, i18n( "Sender" ) ) );
01657 
01658   case Scheduler::NoMethod:
01659     return i18n( "Error: iMIP message with unknown method: '%1'" ).
01660       arg( msg->method() );
01661   }
01662   return QString::null;
01663 }
01664 
01665 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
01666 {
01667   if ( !msg || !journal ) {
01668     return QString::null;
01669   }
01670 
01671   switch ( msg->method() ) {
01672   case Scheduler::Publish:
01673     return i18n("This journal has been published");
01674   case Scheduler::Request:
01675     return i18n( "You have been assigned this journal" );
01676   case Scheduler::Refresh:
01677     return i18n( "This journal was refreshed" );
01678   case Scheduler::Cancel:
01679     return i18n( "This journal was canceled" );
01680   case Scheduler::Add:
01681     return i18n( "Addition to the journal" );
01682   case Scheduler::Reply:
01683   {
01684     if ( replyMeansCounter( journal ) ) {
01685       return i18n( "Sender makes this counter proposal" );
01686     }
01687 
01688     Attendee::List attendees = journal->attendees();
01689     if( attendees.count() == 0 ) {
01690       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01691       return QString::null;
01692     }
01693     if( attendees.count() != 1 ) {
01694       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01695                     << "but is " << attendees.count() << endl;
01696     }
01697     Attendee* attendee = *attendees.begin();
01698 
01699     switch( attendee->status() ) {
01700     case Attendee::NeedsAction:
01701       return i18n( "Sender indicates this journal assignment still needs some action" );
01702     case Attendee::Accepted:
01703       return i18n( "Sender accepts this journal" );
01704     case Attendee::Tentative:
01705       return i18n( "Sender tentatively accepts this journal" );
01706     case Attendee::Declined:
01707       return i18n( "Sender declines this journal" );
01708     case Attendee::Delegated:
01709       return i18n( "Sender has delegated this request for the journal" );
01710     case Attendee::Completed:
01711       return i18n( "The request for this journal is now completed" );
01712     case Attendee::InProcess:
01713       return i18n( "Sender is still processing the invitation" );
01714     default:
01715       return i18n( "Unknown response to this journal" );
01716     }
01717     break;
01718   }
01719   case Scheduler::Counter:
01720     return i18n( "Sender makes this counter proposal" );
01721   case Scheduler::Declinecounter:
01722     return i18n( "Sender declines the counter proposal" );
01723   case Scheduler::NoMethod:
01724     return i18n("Error: iMIP message with unknown method: '%1'").
01725       arg( msg->method() );
01726   }
01727   return QString::null;
01728 }
01729 
01730 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
01731 {
01732   if ( !msg || !fb ) {
01733     return QString::null;
01734   }
01735 
01736   switch ( msg->method() ) {
01737   case Scheduler::Publish:
01738     return i18n("This free/busy list has been published");
01739   case Scheduler::Request:
01740     return i18n( "The free/busy list has been requested" );
01741   case Scheduler::Refresh:
01742     return i18n( "This free/busy list was refreshed" );
01743   case Scheduler::Cancel:
01744     return i18n( "This free/busy list was canceled" );
01745   case Scheduler::Add:
01746     return i18n( "Addition to the free/busy list" );
01747   case Scheduler::NoMethod:
01748   default:
01749     return i18n("Error: Free/Busy iMIP message with unknown method: '%1'").
01750       arg( msg->method() );
01751   }
01752 }
01753 
01754 static QString invitationAttendees( Incidence *incidence )
01755 {
01756   QString tmpStr;
01757   if ( !incidence ) {
01758     return tmpStr;
01759   }
01760 
01761   tmpStr += htmlAddTag( "u", i18n( "Attendee List" ) );
01762   tmpStr += "<br/>";
01763 
01764   int count=0;
01765   Attendee::List attendees = incidence->attendees();
01766   if ( !attendees.isEmpty() ) {
01767 
01768     Attendee::List::ConstIterator it;
01769     for( it = attendees.begin(); it != attendees.end(); ++it ) {
01770       Attendee *a = *it;
01771       if ( !iamAttendee( a ) ) {
01772         count++;
01773         if ( count == 1 ) {
01774           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\" columns=\"2\">";
01775         }
01776         tmpStr += "<tr>";
01777         tmpStr += "<td>";
01778         tmpStr += invitationPerson( a->email(), a->name(), QString::null );
01779         if ( !a->delegator().isEmpty() ) {
01780           tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
01781         }
01782         if ( !a->delegate().isEmpty() ) {
01783           tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
01784         }
01785         tmpStr += "</td>";
01786         tmpStr += "<td>" + a->statusStr() + "</td>";
01787         tmpStr += "</tr>";
01788       }
01789     }
01790   }
01791   if ( count ) {
01792     tmpStr += "</table>";
01793   } else {
01794     tmpStr += "<i>" + i18n( "No attendee", "None" ) + "</i>";
01795   }
01796 
01797   return tmpStr;
01798 }
01799 
01800 static QString invitationAttachments( InvitationFormatterHelper *helper, Incidence *incidence )
01801 {
01802   QString tmpStr;
01803   if ( !incidence ) {
01804     return tmpStr;
01805   }
01806 
01807   Attachment::List attachments = incidence->attachments();
01808   if ( !attachments.isEmpty() ) {
01809     tmpStr += i18n( "Attached Documents:" ) + "<ol>";
01810 
01811     Attachment::List::ConstIterator it;
01812     for( it = attachments.begin(); it != attachments.end(); ++it ) {
01813       Attachment *a = *it;
01814       tmpStr += "<li>";
01815       // Attachment icon
01816       KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
01817       const QString iconStr = mimeType ? mimeType->icon( a->uri(), false ) : QString( "application-octet-stream" );
01818       const QString iconPath = KGlobal::iconLoader()->iconPath( iconStr, KIcon::Small );
01819       if ( !iconPath.isEmpty() ) {
01820         tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
01821       }
01822       tmpStr += helper->makeLink( "ATTACH:" + a->label(), a->label() );
01823       tmpStr += "</li>";
01824     }
01825     tmpStr += "</ol>";
01826   }
01827 
01828   return tmpStr;
01829 }
01830 
01831 class IncidenceFormatter::ScheduleMessageVisitor
01832   : public IncidenceBase::Visitor
01833 {
01834   public:
01835     ScheduleMessageVisitor() : mMessage(0) { mResult = ""; }
01836     bool act( IncidenceBase *incidence, ScheduleMessage *msg )
01837     {
01838       mMessage = msg;
01839       return incidence->accept( *this );
01840     }
01841     QString result() const { return mResult; }
01842 
01843   protected:
01844     QString mResult;
01845     ScheduleMessage *mMessage;
01846 };
01847 
01848 class IncidenceFormatter::InvitationHeaderVisitor
01849   : public IncidenceFormatter::ScheduleMessageVisitor
01850 {
01851   protected:
01852     bool visit( Event *event )
01853     {
01854       mResult = invitationHeaderEvent( event, mMessage );
01855       return !mResult.isEmpty();
01856     }
01857     bool visit( Todo *todo )
01858     {
01859       mResult = invitationHeaderTodo( todo, mMessage );
01860       return !mResult.isEmpty();
01861     }
01862     bool visit( Journal *journal )
01863     {
01864       mResult = invitationHeaderJournal( journal, mMessage );
01865       return !mResult.isEmpty();
01866     }
01867     bool visit( FreeBusy *fb )
01868     {
01869       mResult = invitationHeaderFreeBusy( fb, mMessage );
01870       return !mResult.isEmpty();
01871     }
01872 };
01873 
01874 class IncidenceFormatter::InvitationBodyVisitor
01875   : public IncidenceFormatter::ScheduleMessageVisitor
01876 {
01877   public:
01878     InvitationBodyVisitor( bool noHtmlMode )
01879       : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ) {}
01880 
01881   protected:
01882     bool visit( Event *event )
01883     {
01884       mResult = invitationDetailsEvent( event, mNoHtmlMode );
01885       return !mResult.isEmpty();
01886     }
01887     bool visit( Todo *todo )
01888     {
01889       mResult = invitationDetailsTodo( todo, mNoHtmlMode );
01890       return !mResult.isEmpty();
01891     }
01892     bool visit( Journal *journal )
01893     {
01894       mResult = invitationDetailsJournal( journal, mNoHtmlMode );
01895       return !mResult.isEmpty();
01896     }
01897     bool visit( FreeBusy *fb )
01898     {
01899       mResult = invitationDetailsFreeBusy( fb, mNoHtmlMode );
01900       return !mResult.isEmpty();
01901     }
01902 
01903   private:
01904     bool mNoHtmlMode;
01905 };
01906 
01907 class IncidenceFormatter::IncidenceCompareVisitor
01908   : public IncidenceBase::Visitor
01909 {
01910   public:
01911     IncidenceCompareVisitor() : mExistingIncidence(0) {}
01912     bool act( IncidenceBase *incidence, Incidence* existingIncidence )
01913     {
01914       Incidence *inc = dynamic_cast<Incidence*>( incidence );
01915       if ( !inc || !existingIncidence || inc->revision() <= existingIncidence->revision() )
01916         return false;
01917       mExistingIncidence = existingIncidence;
01918       return incidence->accept( *this );
01919     }
01920 
01921     QString result() const
01922     {
01923       if ( mChanges.isEmpty() ) {
01924         return QString::null;
01925       }
01926       QString html = "<div align=\"left\"><ul><li>";
01927       html += mChanges.join( "</li><li>" );
01928       html += "</li><ul></div>";
01929       return html;
01930     }
01931 
01932   protected:
01933     bool visit( Event *event )
01934     {
01935       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
01936       compareIncidences( event, mExistingIncidence );
01937       return !mChanges.isEmpty();
01938     }
01939     bool visit( Todo *todo )
01940     {
01941       compareIncidences( todo, mExistingIncidence );
01942       return !mChanges.isEmpty();
01943     }
01944     bool visit( Journal *journal )
01945     {
01946       compareIncidences( journal, mExistingIncidence );
01947       return !mChanges.isEmpty();
01948     }
01949     bool visit( FreeBusy *fb )
01950     {
01951       Q_UNUSED( fb );
01952       return !mChanges.isEmpty();
01953     }
01954 
01955   private:
01956     void compareEvents( Event *newEvent, Event *oldEvent )
01957     {
01958       if ( !oldEvent || !newEvent )
01959         return;
01960       if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->doesFloat() != newEvent->doesFloat() )
01961         mChanges += i18n( "The invitation starting time has been changed from %1 to %2" )
01962                     .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) );
01963       if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->doesFloat() != newEvent->doesFloat() )
01964         mChanges += i18n( "The invitation ending time has been changed from %1 to %2" )
01965                     .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) );
01966     }
01967 
01968     void compareIncidences( Incidence *newInc, Incidence *oldInc )
01969     {
01970       if ( !oldInc || !newInc )
01971         return;
01972       if ( oldInc->summary() != newInc->summary() )
01973         mChanges += i18n( "The summary has been changed to: \"%1\"" ).arg( newInc->summary() );
01974       if ( oldInc->location() != newInc->location() )
01975         mChanges += i18n( "The location has been changed to: \"%1\"" ).arg( newInc->location() );
01976       if ( oldInc->description() != newInc->description() )
01977         mChanges += i18n( "The description has been changed to: \"%1\"" ).arg( newInc->description() );
01978       Attendee::List oldAttendees = oldInc->attendees();
01979       Attendee::List newAttendees = newInc->attendees();
01980       for ( Attendee::List::ConstIterator it = newAttendees.constBegin(); it != newAttendees.constEnd(); ++it ) {
01981         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
01982         if ( !oldAtt ) {
01983           mChanges += i18n( "Attendee %1 has been added" ).arg( (*it)->fullName() );
01984         } else {
01985           if ( oldAtt->status() != (*it)->status() )
01986             mChanges += i18n( "The status of attendee %1 has been changed to: %2" ).arg( (*it)->fullName() )
01987                         .arg( (*it)->statusStr() );
01988         }
01989       }
01990       for ( Attendee::List::ConstIterator it = oldAttendees.constBegin(); it != oldAttendees.constEnd(); ++it ) {
01991         Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
01992         if ( !newAtt )
01993           mChanges += i18n( "Attendee %1 has been removed" ).arg( (*it)->fullName() );
01994       }
01995     }
01996 
01997   private:
01998     Incidence* mExistingIncidence;
01999     QStringList mChanges;
02000 };
02001 
02002 
02003 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
02004 {
02005   if ( !id.startsWith( "ATTACH:" ) ) {
02006     QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
02007                   arg( generateLinkURL( id ), text );
02008     return res;
02009   } else {
02010     // draw the attachment links in non-bold face
02011     QString res = QString( "<a href=\"%1\">%2</a>" ).
02012                   arg( generateLinkURL( id ), text );
02013     return res;
02014   }
02015 }
02016 
02017 // Check if the given incidence is likely one that we own instead one from
02018 // a shared calendar (Kolab-specific)
02019 static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence )
02020 {
02021   CalendarResources *cal = dynamic_cast<CalendarResources*>( calendar );
02022   if ( !cal || !incidence ) {
02023     return true;
02024   }
02025   ResourceCalendar *res = cal->resource( incidence );
02026   if ( !res ) {
02027     return true;
02028   }
02029   const QString subRes = res->subresourceIdentifier( incidence );
02030   if ( !subRes.contains( "/.INBOX.directory/" ) ) {
02031     return false;
02032   }
02033   return true;
02034 }
02035 
02036 // The spacer for the invitation buttons
02037 static QString spacer = "<td> &nbsp; </td>";
02038 // The open & close table cell tags for the invitation buttons
02039 static QString tdOpen = "<td>";
02040 static QString tdClose = "</td>" + spacer;
02041 
02042 static QString responseButtons( Incidence *inc, bool rsvpReq, bool rsvpRec,
02043                                 InvitationFormatterHelper *helper )
02044 {
02045   QString html;
02046   if ( !helper ) {
02047     return html;
02048   }
02049 
02050   if ( !rsvpReq ) {
02051     // Record only
02052     html += tdOpen;
02053     html += helper->makeLink( "record", i18n( "[Record]" ) );
02054     html += tdClose;
02055 
02056     // Move to trash
02057     html += tdOpen;
02058     html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) );
02059     html += tdClose;
02060 
02061   } else {
02062 
02063     // Accept
02064     html += tdOpen;
02065     html += helper->makeLink( "accept", i18n( "[Accept]" ) );
02066     html += tdClose;
02067 
02068     // Tentative
02069     html += tdOpen;
02070     html += helper->makeLink( "accept_conditionally",
02071                               i18n( "Accept conditionally", "[Accept cond.]" ) );
02072     html += tdClose;
02073 
02074     // Counter proposal
02075     html += tdOpen;
02076     html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
02077     html += tdClose;
02078 
02079     // Decline
02080     html += tdOpen;
02081     html += helper->makeLink( "decline", i18n( "[Decline]" ) );
02082     html += tdClose;
02083   }
02084 
02085   if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
02086     // Delegate
02087     html += tdOpen;
02088     html += helper->makeLink( "delegate", i18n( "[Delegate]" ) );
02089     html += tdClose;
02090 
02091     // Forward
02092     html += tdOpen;
02093     html += helper->makeLink( "forward", i18n( "[Forward]" ) );
02094     html += tdClose;
02095 
02096     // Check calendar
02097     if ( inc && inc->type() == "Event" ) {
02098       html += tdOpen;
02099       html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
02100       html += tdClose;
02101     }
02102   }
02103   return html;
02104 }
02105 
02106 static QString counterButtons( Incidence *incidence,
02107                                InvitationFormatterHelper *helper )
02108 {
02109   QString html;
02110   if ( !helper ) {
02111     return html;
02112   }
02113 
02114   // Accept proposal
02115   html += tdOpen;
02116   html += helper->makeLink( "accept_counter", i18n("[Accept]") );
02117   html += tdClose;
02118 
02119   // Decline proposal
02120   html += tdOpen;
02121   html += helper->makeLink( "decline_counter", i18n("[Decline]") );
02122   html += tdClose;
02123 
02124   // Check calendar
02125   if ( incidence && incidence->type() == "Event" ) {
02126     html += tdOpen;
02127     html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
02128     html += tdClose;
02129   }
02130   return html;
02131 }
02132 
02133 QString IncidenceFormatter::formatICalInvitationHelper( QString invitation,
02134                                                         Calendar *mCalendar,
02135                                                         InvitationFormatterHelper *helper,
02136                                                         bool noHtmlMode )
02137 {
02138   if ( invitation.isEmpty() ) {
02139     return QString::null;
02140   }
02141 
02142   ICalFormat format;
02143   // parseScheduleMessage takes the tz from the calendar, no need to set it manually here for the format!
02144   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
02145 
02146   if( !msg ) {
02147     kdDebug( 5850 ) << "Failed to parse the scheduling message" << endl;
02148     Q_ASSERT( format.exception() );
02149     kdDebug( 5850 ) << format.exception()->message() << endl;
02150     return QString::null;
02151   }
02152 
02153   IncidenceBase *incBase = msg->event();
02154 
02155   // Determine if this incidence is in my calendar (and owned by me)
02156   Incidence *existingIncidence = 0;
02157   if ( incBase && helper->calendar() ) {
02158     existingIncidence = helper->calendar()->incidence( incBase->uid() );
02159     if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
02160       existingIncidence = 0;
02161     }
02162     if ( !existingIncidence ) {
02163       const Incidence::List list = helper->calendar()->incidences();
02164       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
02165         if ( (*it)->schedulingID() == incBase->uid() &&
02166              incidenceOwnedByMe( helper->calendar(), *it ) ) {
02167           existingIncidence = *it;
02168           break;
02169         }
02170       }
02171     }
02172   }
02173 
02174   // First make the text of the message
02175   QString html;
02176 
02177   QString tableStyle = QString::fromLatin1(
02178     "style=\"border: solid 1px; margin: 0em;\"" );
02179   QString tableHead = QString::fromLatin1(
02180     "<div align=\"center\">"
02181     "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
02182     "<tr><td>").arg(tableStyle);
02183 
02184   html += tableHead;
02185   InvitationHeaderVisitor headerVisitor;
02186   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
02187   if ( !headerVisitor.act( incBase, msg ) )
02188     return QString::null;
02189   html += "<b>" + headerVisitor.result() + "</b>";
02190 
02191   InvitationBodyVisitor bodyVisitor( noHtmlMode );
02192   if ( !bodyVisitor.act( incBase, msg ) )
02193     return QString::null;
02194   html += bodyVisitor.result();
02195 
02196   if ( msg->method() == Scheduler::Request ) { // ### Scheduler::Publish/Refresh/Add as well?
02197     IncidenceCompareVisitor compareVisitor;
02198     if ( compareVisitor.act( incBase, existingIncidence ) ) {
02199       html += i18n("<p align=\"left\">The following changes have been made by the organizer:</p>");
02200       html += compareVisitor.result();
02201     }
02202   }
02203 
02204   Incidence *inc = dynamic_cast<Incidence*>( incBase );
02205 
02206   // determine if I am the organizer for this invitation
02207   bool myInc = iamOrganizer( inc );
02208 
02209   // determine if the invitation response has already been recorded
02210   bool rsvpRec = false;
02211   Attendee *ea = 0;
02212   if ( !myInc ) {
02213     Incidence *rsvpIncidence = existingIncidence;
02214     if ( !rsvpIncidence && inc && inc->revision() > 0 ) {
02215       rsvpIncidence = inc;
02216     }
02217     if ( rsvpIncidence ) {
02218       ea = findMyAttendee( rsvpIncidence );
02219     }
02220     if ( ea &&
02221          ( ea->status() == Attendee::Accepted ||
02222            ea->status() == Attendee::Declined ||
02223            ea->status() == Attendee::Tentative ) ) {
02224       rsvpRec = true;
02225     }
02226   }
02227 
02228   // determine invitation role
02229   QString role;
02230   bool isDelegated = false;
02231   Attendee *a = findMyAttendee( inc );
02232   if ( !a && inc ) {
02233     if ( !inc->attendees().isEmpty() ) {
02234       a = inc->attendees().first();
02235     }
02236   }
02237   if ( a ) {
02238     isDelegated = ( a->status() == Attendee::Delegated );
02239     role = Attendee::roleName( a->role() );
02240   }
02241 
02242   // Print if RSVP needed, not-needed, or response already recorded
02243   bool rsvpReq = rsvpRequested( inc );
02244   if ( !myInc && a ) {
02245     html += "<br/>";
02246     html += "<i><u>";
02247     if ( rsvpRec && inc ) {
02248       if ( inc->revision() == 0 ) {
02249         html += i18n( "Your <b>%1</b> response has already been recorded" ).
02250                 arg( ea->statusStr() );
02251       } else {
02252         html += i18n( "Your status for this invitation is <b>%1</b>" ).
02253                 arg( ea->statusStr() );
02254       }
02255       rsvpReq = false;
02256     } else if ( msg->method() == Scheduler::Cancel ) {
02257       html += i18n( "This invitation was declined" );
02258     } else if ( msg->method() == Scheduler::Add ) {
02259       html += i18n( "This invitation was accepted" );
02260     } else {
02261       if ( !isDelegated ) {
02262         html += rsvpRequestedStr( rsvpReq, role );
02263       } else {
02264         html += i18n( "Awaiting delegation response" );
02265       }
02266     }
02267     html += "</u></i>";
02268   }
02269 
02270   // Print if the organizer gave you a preset status
02271   if ( !myInc ) {
02272     if ( inc && inc->revision() == 0 ) {
02273       QString statStr = myStatusStr( inc );
02274       if ( !statStr.isEmpty() ) {
02275         html += "<br/>";
02276         html += "<i>";
02277         html += statStr;
02278         html += "</i>";
02279       }
02280     }
02281   }
02282 
02283   // Add groupware links
02284 
02285   html += "<br><table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td></tr>";
02286 
02287   switch ( msg->method() ) {
02288     case Scheduler::Publish:
02289     case Scheduler::Request:
02290     case Scheduler::Refresh:
02291     case Scheduler::Add:
02292     {
02293       if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
02294         html += "<tr>";
02295         if ( inc->type() == "Todo" ) {
02296           html += "<td colspan=\"9\">";
02297           html += helper->makeLink( "reply", i18n( "[Record invitation in my task list]" ) );
02298         } else {
02299           html += "<td colspan=\"13\">";
02300           html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) );
02301         }
02302         html += "</td></tr>";
02303       }
02304 
02305       if ( !myInc && a ) {
02306         html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
02307       }
02308       break;
02309     }
02310 
02311     case Scheduler::Cancel:
02312       // Remove invitation
02313       html += "<tr>";
02314       if ( inc->type() == "Todo" ) {
02315         html += "<td colspan=\"9\">";
02316         html += helper->makeLink( "cancel", i18n( "[Remove invitation from my task list]" ) );
02317       } else {
02318         html += "<td colspan=\"13\">";
02319         html += helper->makeLink( "cancel", i18n( "[Remove invitation from my calendar]" ) );
02320       }
02321       html += "</td></tr>";
02322       break;
02323 
02324     case Scheduler::Reply:
02325     {
02326       // Record invitation response
02327       Attendee *a = 0;
02328       Attendee *ea = 0;
02329       if ( inc ) {
02330         // First, determine if this reply is really a counter in disguise.
02331         if ( replyMeansCounter( inc ) ) {
02332           html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
02333           break;
02334         }
02335 
02336         // Next, maybe this is a declined reply that was delegated from me?
02337         // find first attendee who is delegated-from me
02338         // look a their PARTSTAT response, if the response is declined,
02339         // then we need to start over which means putting all the action
02340         // buttons and NOT putting on the [Record response..] button
02341         a = findDelegatedFromMyAttendee( inc );
02342         if ( a ) {
02343           if ( a->status() != Attendee::Accepted ||
02344                a->status() != Attendee::Tentative ) {
02345             html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
02346             break;
02347           }
02348         }
02349 
02350         // Finally, simply allow a Record of the reply
02351         if ( !inc->attendees().isEmpty() ) {
02352           a = inc->attendees().first();
02353         }
02354         if ( a ) {
02355           ea = findAttendee( existingIncidence, a->email() );
02356         }
02357       }
02358       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
02359         if ( inc && inc->revision() > 0 ) {
02360           html += "<br><u><i>";
02361           html += i18n( "The response has been recorded [%1]" ).arg( ea->statusStr() );
02362           html += "</i></u>";
02363         }
02364       } else {
02365         if ( inc ) {
02366           html += "<tr><td>";
02367           if ( inc->type() == "Todo" ) {
02368             html += helper->makeLink( "reply", i18n( "[Record response in my task list]" ) );
02369           } else {
02370             html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
02371           }
02372           html += "</td></tr>";
02373         }
02374       }
02375       break;
02376     }
02377 
02378     case Scheduler::Counter:
02379       // Counter proposal
02380       html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
02381       break;
02382 
02383     case Scheduler::Declinecounter:
02384     case Scheduler::NoMethod:
02385       break;
02386   }
02387 
02388   // close the groupware table
02389   html += "</td></tr></table>";
02390 
02391   // Add the attendee list if I am the organizer
02392   if ( myInc && helper->calendar() ) {
02393     html += invitationAttendees( helper->calendar()->incidence( inc->uid() ) );
02394   }
02395 
02396   // close the top-level table
02397   html += "</td></tr></table><br></div>";
02398 
02399   // Add the attachment list
02400   html += invitationAttachments( helper, inc );
02401 
02402   return html;
02403 }
02404 
02405 QString IncidenceFormatter::formatICalInvitation( QString invitation,
02406                                                   Calendar *mCalendar,
02407                                                   InvitationFormatterHelper *helper )
02408 {
02409   return formatICalInvitationHelper( invitation, mCalendar, helper, false );
02410 }
02411 
02412 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
02413                                                         Calendar *mCalendar,
02414                                                         InvitationFormatterHelper *helper )
02415 {
02416   return formatICalInvitationHelper( invitation, mCalendar, helper, true );
02417 }
02418 
02419 /*******************************************************************
02420  *  Helper functions for the msTNEF -> VPart converter
02421  *******************************************************************/
02422 
02423 
02424 //-----------------------------------------------------------------------------
02425 
02426 static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
02427                            const QString& fallback = QString::null)
02428 {
02429   return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
02430                             fallback );
02431 }
02432 
02433 static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
02434                            const QString& fallback = QString::null )
02435 {
02436   return tnefMsg->findNamedProp( name, fallback );
02437 }
02438 
02439 struct save_tz { char* old_tz; char* tz_env_str; };
02440 
02441 /* temporarily go to a different timezone */
02442 static struct save_tz set_tz( const char* _tc )
02443 {
02444   const char *tc = _tc?_tc:"UTC";
02445 
02446   struct save_tz rv;
02447 
02448   rv.old_tz = 0;
02449   rv.tz_env_str = 0;
02450 
02451   //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;
02452 
02453   char* tz_env = 0;
02454   if( getenv( "TZ" ) ) {
02455     tz_env = strdup( getenv( "TZ" ) );
02456     rv.old_tz = tz_env;
02457   }
02458   char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
02459   strcpy( tmp_env, "TZ=" );
02460   strcpy( tmp_env+3, tc );
02461   putenv( tmp_env );
02462 
02463   rv.tz_env_str = tmp_env;
02464 
02465   /* tmp_env is not free'ed -- it is part of the environment */
02466 
02467   tzset();
02468   //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;
02469 
02470   return rv;
02471 }
02472 
02473 /* restore previous timezone */
02474 static void unset_tz( struct save_tz old_tz )
02475 {
02476   if( old_tz.old_tz ) {
02477     char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
02478     strcpy( tmp_env, "TZ=" );
02479     strcpy( tmp_env+3, old_tz.old_tz );
02480     putenv( tmp_env );
02481     /* tmp_env is not free'ed -- it is part of the environment */
02482     free( old_tz.old_tz );
02483   } else {
02484     /* clear TZ from env */
02485     putenv( strdup("TZ") );
02486   }
02487   tzset();
02488 
02489   /* is this OK? */
02490   if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
02491 }
02492 
02493 static QDateTime utc2Local( const QDateTime& utcdt )
02494 {
02495   struct tm tmL;
02496 
02497   save_tz tmp_tz = set_tz("UTC");
02498   time_t utc = utcdt.toTime_t();
02499   unset_tz( tmp_tz );
02500 
02501   localtime_r( &utc, &tmL );
02502   return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
02503                     QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
02504 }
02505 
02506 
02507 static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
02508                                           bool bDateOnly = false )
02509 {
02510   QDate tmpDate;
02511   QTime tmpTime;
02512   int year, month, day, hour, minute, second;
02513 
02514   if( bDateOnly ) {
02515     year = dtStr.left( 4 ).toInt();
02516     month = dtStr.mid( 4, 2 ).toInt();
02517     day = dtStr.mid( 6, 2 ).toInt();
02518     hour = 0;
02519     minute = 0;
02520     second = 0;
02521   } else {
02522     year = dtStr.left( 4 ).toInt();
02523     month = dtStr.mid( 4, 2 ).toInt();
02524     day = dtStr.mid( 6, 2 ).toInt();
02525     hour = dtStr.mid( 9, 2 ).toInt();
02526     minute = dtStr.mid( 11, 2 ).toInt();
02527     second = dtStr.mid( 13, 2 ).toInt();
02528   }
02529   tmpDate.setYMD( year, month, day );
02530   tmpTime.setHMS( hour, minute, second );
02531 
02532   if( tmpDate.isValid() && tmpTime.isValid() ) {
02533     QDateTime dT = QDateTime( tmpDate, tmpTime );
02534 
02535     if( !bDateOnly ) {
02536       // correct for GMT ( == Zulu time == UTC )
02537       if (dtStr.at(dtStr.length()-1) == 'Z') {
02538         //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
02539         //localUTCOffset( dT ) );
02540         dT = utc2Local( dT );
02541       }
02542     }
02543     return dT;
02544   } else
02545     return QDateTime();
02546 }
02547 
02548 
02549 
02550 QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef )
02551 {
02552   bool bOk = false;
02553 
02554   KTNEFParser parser;
02555   QBuffer buf( tnef );
02556   CalendarLocal cal ( QString::fromLatin1( "UTC" ) );
02557   KABC::Addressee addressee;
02558   KABC::VCardConverter cardConv;
02559   ICalFormat calFormat;
02560   Event* event = new Event();
02561 
02562   if( parser.openDevice( &buf ) ) {
02563     KTNEFMessage* tnefMsg = parser.message();
02564     //QMap<int,KTNEFProperty*> props = parser.message()->properties();
02565 
02566     // Everything depends from property PR_MESSAGE_CLASS
02567     // (this is added by KTNEFParser):
02568     QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
02569       .upper();
02570     if( !msgClass.isEmpty() ) {
02571       // Match the old class names that might be used by Outlook for
02572       // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
02573       bool bCompatClassAppointment = false;
02574       bool bCompatMethodRequest = false;
02575       bool bCompatMethodCancled = false;
02576       bool bCompatMethodAccepted = false;
02577       bool bCompatMethodAcceptedCond = false;
02578       bool bCompatMethodDeclined = false;
02579       if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
02580         bCompatClassAppointment = true;
02581         if( msgClass.endsWith( ".MTGREQ" ) )
02582           bCompatMethodRequest = true;
02583         if( msgClass.endsWith( ".MTGCNCL" ) )
02584           bCompatMethodCancled = true;
02585         if( msgClass.endsWith( ".MTGRESPP" ) )
02586           bCompatMethodAccepted = true;
02587         if( msgClass.endsWith( ".MTGRESPA" ) )
02588           bCompatMethodAcceptedCond = true;
02589         if( msgClass.endsWith( ".MTGRESPN" ) )
02590           bCompatMethodDeclined = true;
02591       }
02592       bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );
02593 
02594       if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
02595         // Compose a vCal
02596         bool bIsReply = false;
02597         QString prodID = "-//Microsoft Corporation//Outlook ";
02598         prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
02599         prodID += "MIMEDIR/EN\n";
02600         prodID += "VERSION:2.0\n";
02601         calFormat.setApplication( "Outlook", prodID );
02602 
02603         Scheduler::Method method;
02604         if( bCompatMethodRequest )
02605           method = Scheduler::Request;
02606         else if( bCompatMethodCancled )
02607           method = Scheduler::Cancel;
02608         else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
02609                  bCompatMethodDeclined ) {
02610           method = Scheduler::Reply;
02611           bIsReply = true;
02612         } else {
02613           // pending(khz): verify whether "0x0c17" is the right tag ???
02614           //
02615           // at the moment we think there are REQUESTS and UPDATES
02616           //
02617           // but WHAT ABOUT REPLIES ???
02618           //
02619           //
02620 
02621           if( tnefMsg->findProp(0x0c17) == "1" )
02622             bIsReply = true;
02623           method = Scheduler::Request;
02624         }
02625 
02627         ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );
02628 
02629         QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );
02630 
02631         if( !sSenderSearchKeyEmail.isEmpty() ) {
02632           int colon = sSenderSearchKeyEmail.find( ':' );
02633           // May be e.g. "SMTP:KHZ@KDE.ORG"
02634           if( sSenderSearchKeyEmail.find( ':' ) == -1 )
02635             sSenderSearchKeyEmail.remove( 0, colon+1 );
02636         }
02637 
02638         QString s( tnefMsg->findProp( 0x0e04 ) );
02639         QStringList attendees = QStringList::split( ';', s );
02640         if( attendees.count() ) {
02641           for( QStringList::Iterator it = attendees.begin();
02642                it != attendees.end(); ++it ) {
02643             // Skip all entries that have no '@' since these are
02644             // no mail addresses
02645             if( (*it).find('@') == -1 ) {
02646               s = (*it).stripWhiteSpace();
02647 
02648               Attendee *attendee = new Attendee( s, s, true );
02649               if( bIsReply ) {
02650                 if( bCompatMethodAccepted )
02651                   attendee->setStatus( Attendee::Accepted );
02652                 if( bCompatMethodDeclined )
02653                   attendee->setStatus( Attendee::Declined );
02654                 if( bCompatMethodAcceptedCond )
02655                   attendee->setStatus(Attendee::Tentative);
02656               } else {
02657                 attendee->setStatus( Attendee::NeedsAction );
02658                 attendee->setRole( Attendee::ReqParticipant );
02659               }
02660               event->addAttendee(attendee);
02661             }
02662           }
02663         } else {
02664           // Oops, no attendees?
02665           // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
02666           s = sSenderSearchKeyEmail;
02667           if( !s.isEmpty() ) {
02668             Attendee *attendee = new Attendee( QString::null, QString::null,
02669                                                true );
02670             if( bIsReply ) {
02671               if( bCompatMethodAccepted )
02672                 attendee->setStatus( Attendee::Accepted );
02673               if( bCompatMethodAcceptedCond )
02674                 attendee->setStatus( Attendee::Declined );
02675               if( bCompatMethodDeclined )
02676                 attendee->setStatus( Attendee::Tentative );
02677             } else {
02678               attendee->setStatus(Attendee::NeedsAction);
02679               attendee->setRole(Attendee::ReqParticipant);
02680             }
02681             event->addAttendee(attendee);
02682           }
02683         }
02684         s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
02685         if( s.isEmpty() && !bIsReply )
02686           s = sSenderSearchKeyEmail;
02687         // TODO: Use the common name?
02688         if( !s.isEmpty() )
02689           event->setOrganizer( s );
02690 
02691         s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
02692           .replace( QChar( ':' ), QString::null );
02693         event->setDtStart( QDateTime::fromString( s ) ); // ## Format??
02694 
02695         s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
02696           .replace( QChar( ':' ), QString::null );
02697         event->setDtEnd( QDateTime::fromString( s ) );
02698 
02699         s = tnefMsg->findProp( 0x8208 );
02700         event->setLocation( s );
02701 
02702         // is it OK to set this to OPAQUE always ??
02703         //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
02704         //vPart += "SEQUENCE:0\n";
02705 
02706         // is "0x0023" OK  -  or should we look for "0x0003" ??
02707         s = tnefMsg->findProp( 0x0023 );
02708         event->setUid( s );
02709 
02710         // PENDING(khz): is this value in local timezone? Must it be
02711         // adjusted? Most likely this is a bug in the server or in
02712         // Outlook - we ignore it for now.
02713         s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
02714           .replace( QChar( ':' ), QString::null );
02715         // ### libkcal always uses currentDateTime()
02716         // event->setDtStamp(QDateTime::fromString(s));
02717 
02718         s = tnefMsg->findNamedProp( "Keywords" );
02719         event->setCategories( s );
02720 
02721         s = tnefMsg->findProp( 0x1000 );
02722         event->setDescription( s );
02723 
02724         s = tnefMsg->findProp( 0x0070 );
02725         event->setSummary( s );
02726 
02727         s = tnefMsg->findProp( 0x0026 );
02728         event->setPriority( s.toInt() );
02729 
02730         // is reminder flag set ?
02731         if(!tnefMsg->findProp(0x8503).isEmpty()) {
02732           Alarm *alarm = new Alarm(event);
02733           QDateTime highNoonTime =
02734             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
02735                                      .replace( QChar( '-' ), "" )
02736                                      .replace( QChar( ':' ), "" ) );
02737           QDateTime wakeMeUpTime =
02738             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
02739                                      .replace( QChar( '-' ), "" )
02740                                      .replace( QChar( ':' ), "" ) );
02741           alarm->setTime(wakeMeUpTime);
02742 
02743           if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
02744             alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
02745           else
02746             // default: wake them up 15 minutes before the appointment
02747             alarm->setStartOffset( Duration( 15*60 ) );
02748           alarm->setDisplayAlarm( i18n( "Reminder" ) );
02749 
02750           // Sorry: the different action types are not known (yet)
02751           //        so we always set 'DISPLAY' (no sounds, no images...)
02752           event->addAlarm( alarm );
02753         }
02754         cal.addEvent( event );
02755         bOk = true;
02756         // we finished composing a vCal
02757       } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
02758         addressee.setUid( stringProp( tnefMsg, attMSGID ) );
02759         addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) );
02760         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
02761         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
02762         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
02763         addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) );
02764         addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) );
02765         addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
02766         addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
02767         addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) );
02768         addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) );
02769         addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) );
02770 
02771         QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
02772           .replace( QChar( '-' ), QString::null )
02773           .replace( QChar( ':' ), QString::null );
02774         if( !s.isEmpty() )
02775           addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );
02776 
02777         addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );
02778 
02779         // collect parts of Name entry
02780         addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
02781         addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
02782         addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) );
02783         addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) );
02784         addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );
02785 
02786         addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
02787         addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
02788         addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
02789         /*
02790         the MAPI property ID of this (multiline) )field is unknown:
02791         vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
02792         */
02793 
02794         KABC::Address adr;
02795         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) );
02796         adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
02797         adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
02798         adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
02799         adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) );
02800         adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
02801         adr.setType(KABC::Address::Home);
02802         addressee.insertAddress(adr);
02803 
02804         adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
02805         adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) );
02806         adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) );
02807         adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) );
02808         adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
02809         adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) );
02810         adr.setType( KABC::Address::Work );
02811         addressee.insertAddress( adr );
02812 
02813         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) );
02814         adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
02815         adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
02816         adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
02817         adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) );
02818         adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
02819         adr.setType( KABC::Address::Dom );
02820         addressee.insertAddress(adr);
02821 
02822         // problem: the 'other' address was stored by KOrganizer in
02823         //          a line looking like the following one:
02824         // 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
02825 
02826         QString nr;
02827         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
02828         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) );
02829         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
02830         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) );
02831         nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
02832         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) );
02833         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
02834         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) );
02835         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
02836         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) );
02837 
02838         s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
02839           .replace( QChar( '-' ), QString::null )
02840           .replace( QChar( ':' ), QString::null );
02841         if( !s.isEmpty() )
02842           addressee.setBirthday( QDateTime::fromString( s ) );
02843 
02844         bOk = ( !addressee.isEmpty() );
02845       } else if( "IPM.NOTE" == msgClass ) {
02846 
02847       } // else if ... and so on ...
02848     }
02849   }
02850 
02851   // Compose return string
02852   QString iCal = calFormat.toString( &cal );
02853   if( !iCal.isEmpty() )
02854     // This was an iCal
02855     return iCal;
02856 
02857   // Not an iCal - try a vCard
02858   KABC::VCardConverter converter;
02859   return converter.createVCard( addressee );
02860 }
02861 
02862 
02863 QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef,
02864         Calendar *mCalendar, InvitationFormatterHelper *helper )
02865 {
02866   QString vPart = IncidenceFormatter::msTNEFToVPart( tnef );
02867   QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper );
02868   if( !iCal.isEmpty() )
02869     return iCal;
02870   return vPart;
02871 }
02872 
02873 
02874 
02875 
02876 /*******************************************************************
02877  *  Helper functions for the Incidence tooltips
02878  *******************************************************************/
02879 
02880 class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
02881 {
02882   public:
02883     ToolTipVisitor()
02884       : mCalendar( 0 ), mRichText( true ), mResult( "" ) {}
02885 
02886     bool act( Calendar *calendar, IncidenceBase *incidence,
02887               const QDate &date=QDate(), bool richText=true )
02888     {
02889       mCalendar = calendar;
02890       mDate = date;
02891       mRichText = richText;
02892       mResult = "";
02893       return incidence ? incidence->accept( *this ) : false;
02894     }
02895     QString result() const { return mResult; }
02896 
02897   protected:
02898     bool visit( Event *event );
02899     bool visit( Todo *todo );
02900     bool visit( Journal *journal );
02901     bool visit( FreeBusy *fb );
02902 
02903     QString dateRangeText( Event *event, const QDate &date );
02904     QString dateRangeText( Todo *todo, const QDate &date );
02905     QString dateRangeText( Journal *journal );
02906     QString dateRangeText( FreeBusy *fb );
02907 
02908     QString generateToolTip( Incidence* incidence, QString dtRangeText );
02909 
02910   protected:
02911     Calendar *mCalendar;
02912     QDate mDate;
02913     bool mRichText;
02914     QString mResult;
02915 };
02916 
02917 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const QDate &date )
02918 {
02919   QString ret;
02920   QString tmp;
02921 
02922   QDateTime startDt = event->dtStart();
02923   QDateTime endDt = event->dtEnd();
02924   if ( event->doesRecur() ) {
02925     if ( date.isValid() ) {
02926       QDateTime dt( date, QTime( 0, 0, 0 ) );
02927       int diffDays = startDt.daysTo( dt );
02928       dt = dt.addSecs( -1 );
02929       startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() );
02930       if ( event->hasEndDate() ) {
02931         endDt = endDt.addDays( diffDays );
02932         if ( startDt > endDt ) {
02933           startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() );
02934           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
02935         }
02936       }
02937     }
02938   }
02939   if ( event->isMultiDay() ) {
02940 
02941     tmp = "<br>" + i18n("Event start", "<i>From:</i>&nbsp;%1");
02942     if (event->doesFloat())
02943       ret += tmp.arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
02944     else
02945       ret += tmp.arg( IncidenceFormatter::dateToString( startDt ).replace(" ", "&nbsp;") );
02946 
02947     tmp = "<br>" + i18n("Event end","<i>To:</i>&nbsp;%1");
02948     if (event->doesFloat())
02949       ret += tmp.arg( IncidenceFormatter::dateToString( endDt, false ).replace(" ", "&nbsp;") );
02950     else
02951       ret += tmp.arg( IncidenceFormatter::dateToString( endDt ).replace(" ", "&nbsp;") );
02952 
02953   } else {
02954 
02955     ret += "<br>"+i18n("<i>Date:</i>&nbsp;%1").
02956            arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
02957     if ( !event->doesFloat() ) {
02958       const QString dtStartTime =
02959         IncidenceFormatter::timeToString( startDt, true ).replace( " ", "&nbsp;" );
02960       const QString dtEndTime =
02961         IncidenceFormatter::timeToString( endDt, true ).replace( " ", "&nbsp;" );
02962       if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00'
02963         tmp = "<br>" + i18n("time for event, &nbsp; to prevent ugly line breaks",
02964         "<i>Time:</i>&nbsp;%1").
02965         arg( dtStartTime );
02966       } else {
02967         tmp = "<br>" + i18n("time range for event, &nbsp; to prevent ugly line breaks",
02968         "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2").
02969         arg( dtStartTime, dtEndTime );
02970       }
02971       ret += tmp;
02972     }
02973 
02974   }
02975   return ret;
02976 }
02977 
02978 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const QDate &date )
02979 {
02980   QString ret;
02981   bool floats( todo->doesFloat() );
02982 
02983   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
02984     QDateTime startDt = todo->dtStart();
02985     if ( todo->doesRecur() ) {
02986       if ( date.isValid() ) {
02987         startDt.setDate( date );
02988       }
02989     }
02990     ret += "<br>" +
02991            i18n("<i>Start:</i>&nbsp;%1").
02992            arg( IncidenceFormatter::dateTimeToString( startDt, floats, false ).
02993                 replace( " ", "&nbsp;" ) );
02994   }
02995 
02996   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
02997     QDateTime dueDt = todo->dtDue();
02998     if ( todo->doesRecur() ) {
02999       if ( date.isValid() ) {
03000         dueDt.addDays( todo->dtDue().date().daysTo( date ) );
03001       }
03002     }
03003     ret += "<br>" +
03004            i18n("<i>Due:</i>&nbsp;%1").
03005            arg( IncidenceFormatter::dateTimeToString( todo->dtDue(), floats, false ).
03006                 replace( " ", "&nbsp;" ) );
03007   }
03008 
03009   if ( todo->isCompleted() ) {
03010     ret += "<br>" +
03011            i18n("<i>Completed:</i>&nbsp;%1").
03012            arg( todo->completedStr().replace( " ", "&nbsp;" ) );
03013   } else {
03014     ret += "<br>" +
03015            i18n( "%1% completed" ).arg(todo->percentComplete() );
03016   }
03017 
03018   return ret;
03019 }
03020 
03021 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal )
03022 {
03023   QString ret;
03024   if (journal->dtStart().isValid() ) {
03025     ret += "<br>" +
03026            i18n("<i>Date:</i>&nbsp;%1").
03027            arg( IncidenceFormatter::dateToString( journal->dtStart(), false ) );
03028   }
03029   return ret;
03030 }
03031 
03032 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
03033 {
03034   QString tmp( "<br>" + i18n("<i>Period start:</i>&nbsp;%1") );
03035   QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) );
03036   tmp = "<br>" + i18n("<i>Period start:</i>&nbsp;%1");
03037   ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) );
03038   return ret;
03039 }
03040 
03041 
03042 
03043 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
03044 {
03045   mResult = generateToolTip( event, dateRangeText( event, mDate ) );
03046   return !mResult.isEmpty();
03047 }
03048 
03049 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
03050 {
03051   mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
03052   return !mResult.isEmpty();
03053 }
03054 
03055 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
03056 {
03057   mResult = generateToolTip( journal, dateRangeText( journal ) );
03058   return !mResult.isEmpty();
03059 }
03060 
03061 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
03062 {
03063   mResult = "<qt><b>" + i18n("Free/Busy information for %1")
03064         .arg(fb->organizer().fullName()) + "</b>";
03065   mResult += dateRangeText( fb );
03066   mResult += "</qt>";
03067   return !mResult.isEmpty();
03068 }
03069 
03070 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence* incidence, QString dtRangeText )
03071 {
03072   if ( !incidence )
03073     return QString::null;
03074 
03075   QString tmp = "<qt><b>"+ incidence->summary().replace("\n", "<br>")+"</b>";
03076 
03077   if ( mCalendar ) {
03078     QString calStr = IncidenceFormatter::resourceString( mCalendar, incidence );
03079     if ( !calStr.isEmpty() ) {
03080       tmp += "<br>" + i18n( "<i>Calendar:</i> %1" ).arg( calStr );
03081     }
03082   }
03083 
03084   tmp += dtRangeText;
03085 
03086   if (!incidence->location().isEmpty()) {
03087     tmp += "<br>"+i18n("<i>Location:</i>&nbsp;%1").
03088       arg( incidence->location().replace("\n", "<br>") );
03089   }
03090   if (!incidence->description().isEmpty()) {
03091     QString desc(incidence->description());
03092     if (desc.length()>120) {
03093       desc = desc.left(120) + "...";
03094     }
03095     tmp += "<br>----------<br>" + i18n("<i>Description:</i><br>") + desc.replace("\n", "<br>");
03096   }
03097   tmp += "</qt>";
03098   return tmp;
03099 }
03100 
03101 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
03102 {
03103   return toolTipStr( 0, incidence, QDate(), richText );
03104 }
03105 
03106 QString IncidenceFormatter::toolTipStr( Calendar *calendar,
03107                                         IncidenceBase *incidence,
03108                                         const QDate &date,
03109                                         bool richText )
03110 {
03111   ToolTipVisitor v;
03112   if ( v.act( calendar, incidence, date, richText ) ) {
03113     return v.result();
03114   } else {
03115     return QString::null;
03116   }
03117 }
03118 
03119 /*******************************************************************
03120  *  Helper functions for the Incidence tooltips
03121  *******************************************************************/
03122 
03123 class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
03124 {
03125   public:
03126     MailBodyVisitor() : mResult( "" ) {}
03127 
03128     bool act( IncidenceBase *incidence )
03129     {
03130       mResult = "";
03131       return incidence ? incidence->accept( *this ) : false;
03132     }
03133     QString result() const { return mResult; }
03134 
03135   protected:
03136     bool visit( Event *event );
03137     bool visit( Todo *todo );
03138     bool visit( Journal *journal );
03139     bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); }
03140   protected:
03141     QString mResult;
03142 };
03143 
03144 
03145 static QString mailBodyIncidence( Incidence *incidence )
03146 {
03147   QString body;
03148   if ( !incidence->summary().isEmpty() ) {
03149     body += i18n("Summary: %1\n").arg( incidence->summary() );
03150   }
03151   if ( !incidence->organizer().isEmpty() ) {
03152     body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() );
03153   }
03154   if ( !incidence->location().isEmpty() ) {
03155     body += i18n("Location: %1\n").arg( incidence->location() );
03156   }
03157   return body;
03158 }
03159 
03160 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
03161 {
03162   QString recurrence[]= {i18n("no recurrence", "None"),
03163     i18n("Minutely"), i18n("Hourly"), i18n("Daily"),
03164     i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"),
03165     i18n("Yearly"), i18n("Yearly"), i18n("Yearly")};
03166 
03167   mResult = mailBodyIncidence( event );
03168   mResult += i18n("Start Date: %1\n").
03169              arg( IncidenceFormatter::dateToString( event->dtStart(), true ) );
03170   if ( !event->doesFloat() ) {
03171     mResult += i18n("Start Time: %1\n").
03172                arg( IncidenceFormatter::timeToString( event->dtStart(), true ) );
03173   }
03174   if ( event->dtStart() != event->dtEnd() ) {
03175     mResult += i18n("End Date: %1\n").
03176                arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) );
03177   }
03178   if ( !event->doesFloat() ) {
03179     mResult += i18n("End Time: %1\n").
03180                arg( IncidenceFormatter::timeToString( event->dtEnd(), true ) );
03181   }
03182   if ( event->doesRecur() ) {
03183     Recurrence *recur = event->recurrence();
03184     // TODO: Merge these two to one of the form "Recurs every 3 days"
03185     mResult += i18n("Recurs: %1\n")
03186              .arg( recurrence[ recur->recurrenceType() ] );
03187     mResult += i18n("Frequency: %1\n")
03188              .arg( event->recurrence()->frequency() );
03189 
03190     if ( recur->duration() > 0 ) {
03191       mResult += i18n ("Repeats once", "Repeats %n times", recur->duration());
03192       mResult += '\n';
03193     } else {
03194       if ( recur->duration() != -1 ) {
03195 // TODO_Recurrence: What to do with floating
03196         QString endstr;
03197         if ( event->doesFloat() ) {
03198           endstr = KGlobal::locale()->formatDate( recur->endDate() );
03199         } else {
03200           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() );
03201         }
03202         mResult += i18n("Repeat until: %1\n").arg( endstr );
03203       } else {
03204         mResult += i18n("Repeats forever\n");
03205       }
03206     }
03207   }
03208   QString details = event->description();
03209   if ( !details.isEmpty() ) {
03210     mResult += i18n("Details:\n%1\n").arg( details );
03211   }
03212   return !mResult.isEmpty();
03213 }
03214 
03215 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
03216 {
03217   mResult = mailBodyIncidence( todo );
03218 
03219   if ( todo->hasStartDate() ) {
03220     mResult += i18n("Start Date: %1\n").
03221                arg( IncidenceFormatter::dateToString( todo->dtStart( false ), true ) );
03222     if ( !todo->doesFloat() ) {
03223       mResult += i18n("Start Time: %1\n").
03224                  arg( IncidenceFormatter::timeToString( todo->dtStart( false ),true ) );
03225     }
03226   }
03227   if ( todo->hasDueDate() ) {
03228     mResult += i18n("Due Date: %1\n").
03229                arg( IncidenceFormatter::dateToString( todo->dtDue(), true ) );
03230     if ( !todo->doesFloat() ) {
03231       mResult += i18n("Due Time: %1\n").
03232                  arg( IncidenceFormatter::timeToString( todo->dtDue(), true ) );
03233     }
03234   }
03235   QString details = todo->description();
03236   if ( !details.isEmpty() ) {
03237     mResult += i18n("Details:\n%1\n").arg( details );
03238   }
03239   return !mResult.isEmpty();
03240 }
03241 
03242 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
03243 {
03244   mResult = mailBodyIncidence( journal );
03245   mResult += i18n("Date: %1\n").
03246              arg( IncidenceFormatter::dateToString( journal->dtStart(), true ) );
03247   if ( !journal->doesFloat() ) {
03248     mResult += i18n("Time: %1\n").
03249                arg( IncidenceFormatter::timeToString( journal->dtStart(), true ) );
03250   }
03251   if ( !journal->description().isEmpty() )
03252     mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() );
03253   return !mResult.isEmpty();
03254 }
03255 
03256 
03257 
03258 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
03259 {
03260   if ( !incidence )
03261     return QString::null;
03262 
03263   MailBodyVisitor v;
03264   if ( v.act( incidence ) ) {
03265     return v.result();
03266   }
03267   return QString::null;
03268 }
03269 
03270 /************************************
03271  *  More static formatting functions
03272  ************************************/
03273 
03274 QString IncidenceFormatter::recurrenceString(Incidence * incidence)
03275 {
03276   if ( !incidence->doesRecur() )
03277     return i18n( "No recurrence" );
03278 
03279      // recurrence
03280   QStringList dayList;
03281   dayList.append( i18n( "31st Last" ) );
03282   dayList.append( i18n( "30th Last" ) );
03283   dayList.append( i18n( "29th Last" ) );
03284   dayList.append( i18n( "28th Last" ) );
03285   dayList.append( i18n( "27th Last" ) );
03286   dayList.append( i18n( "26th Last" ) );
03287   dayList.append( i18n( "25th Last" ) );
03288   dayList.append( i18n( "24th Last" ) );
03289   dayList.append( i18n( "23rd Last" ) );
03290   dayList.append( i18n( "22nd Last" ) );
03291   dayList.append( i18n( "21st Last" ) );
03292   dayList.append( i18n( "20th Last" ) );
03293   dayList.append( i18n( "19th Last" ) );
03294   dayList.append( i18n( "18th Last" ) );
03295   dayList.append( i18n( "17th Last" ) );
03296   dayList.append( i18n( "16th Last" ) );
03297   dayList.append( i18n( "15th Last" ) );
03298   dayList.append( i18n( "14th Last" ) );
03299   dayList.append( i18n( "13th Last" ) );
03300   dayList.append( i18n( "12th Last" ) );
03301   dayList.append( i18n( "11th Last" ) );
03302   dayList.append( i18n( "10th Last" ) );
03303   dayList.append( i18n( "9th Last" ) );
03304   dayList.append( i18n( "8th Last" ) );
03305   dayList.append( i18n( "7th Last" ) );
03306   dayList.append( i18n( "6th Last" ) );
03307   dayList.append( i18n( "5th Last" ) );
03308   dayList.append( i18n( "4th Last" ) );
03309   dayList.append( i18n( "3rd Last" ) );
03310   dayList.append( i18n( "2nd Last" ) );
03311   dayList.append( i18n( "last day of the month", "Last" ) );
03312   dayList.append( i18n( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
03313   dayList.append( i18n( "1st" ) );
03314   dayList.append( i18n( "2nd" ) );
03315   dayList.append( i18n( "3rd" ) );
03316   dayList.append( i18n( "4th" ) );
03317   dayList.append( i18n( "5th" ) );
03318 
03319   QString recurString;
03320   const KCalendarSystem *calSys = KGlobal::locale()->calendar();;
03321 
03322   Recurrence *recurs = incidence->recurrence();
03323   switch ( recurs->recurrenceType() ) {
03324 
03325       case Recurrence::rNone:
03326           recurString = i18n( "no recurrence", "None" );
03327           break;
03328       case Recurrence::rDaily:
03329           recurString = i18n( "Every day", "Every %1 days", recurs->frequency() );
03330           break;
03331       case Recurrence::rWeekly:
03332       {
03333           QString dayNames;
03334           // Respect start of week setting
03335           int weekStart = KGlobal::locale()->weekStartDay();
03336           bool addSpace = false;
03337           for ( int i = 0; i < 7; ++i ) {
03338               if ( recurs->days().testBit( (i+weekStart+6)%7 )) {
03339                   if (addSpace) dayNames.append(" ");
03340                   dayNames.append( calSys->weekDayName( ((i+weekStart+6)%7)+1, true ) );
03341                   addSpace=true;
03342               }
03343           }
03344           recurString = i18n( "Every week on %1",
03345                               "Every %n weeks on %1",
03346                               recurs->frequency()).arg( dayNames );
03347           break;
03348       }
03349       case Recurrence::rMonthlyPos:
03350       {
03351           KCal::RecurrenceRule::WDayPos rule = recurs->monthPositions()[0];
03352           recurString = i18n( "Every month on the %1 %2",
03353                               "Every %n months on the %1 %2",
03354                               recurs->frequency() ).arg(dayList[rule.pos() + 31]).arg(
03355                                       calSys->weekDayName( rule.day(),false ) );
03356           break;
03357       }
03358       case Recurrence::rMonthlyDay:
03359       {
03360           int days = recurs->monthDays()[0];
03361           if (days < 0) {
03362               recurString = i18n( "Every month on the %1 day",
03363                                   "Every %n months on the %1 day",
03364                                   recurs->frequency() ).arg( dayList[days + 31] );
03365           } else {
03366               recurString = i18n( "Every month on day %1",
03367                                   "Every %n months on day %1",
03368                                   recurs->frequency() ).arg( recurs->monthDays()[0] );
03369           }
03370           break;
03371       }
03372 
03373       case Recurrence::rYearlyMonth:
03374       {
03375           recurString = i18n( "Every year on day %1 of %2",
03376                               "Every %n years on day %1 of %2",
03377                               recurs->frequency() )
03378                   .arg(recurs->yearDates()[0])
03379                   .arg(calSys->monthName( recurs->yearMonths()[0], recurs->startDate().year() ) );
03380           break;
03381       }
03382       case Recurrence::rYearlyPos:
03383       {
03384           KCal::RecurrenceRule::WDayPos rule = recurs->yearPositions()[0];
03385           recurString = i18n( "Every year on the %1 %2 of %3",
03386                               "Every %n years on the %1 %2of %3",
03387                               recurs->frequency()).arg( dayList[rule.pos() + 31] )
03388                   .arg( calSys->weekDayName( rule.day(), false ))
03389                   .arg( calSys->monthName( recurs->yearMonths()[0], recurs->startDate().year() ) );
03390           break;
03391       }
03392       case Recurrence::rYearlyDay:
03393       {
03394           recurString = i18n( "Every year on day %1",
03395                               "Every %n years on day %1",
03396                               recurs->frequency()).arg( recurs->yearDays()[0] );
03397           break;
03398       }
03399 
03400       default:
03401           return i18n( "Incidence recurs" );
03402   }
03403 
03404   return recurString;
03405 }
03406 
03407 QString IncidenceFormatter::timeToString( const QDateTime &date, bool shortfmt )
03408 {
03409   return KGlobal::locale()->formatTime( date.time(), !shortfmt );
03410 }
03411 
03412 QString IncidenceFormatter::dateToString( const QDateTime &date, bool shortfmt )
03413 {
03414   return
03415     KGlobal::locale()->formatDate( date.date(), shortfmt );
03416 }
03417 
03418 QString IncidenceFormatter::dateTimeToString( const QDateTime &date,
03419                                               bool allDay, bool shortfmt )
03420 {
03421   if ( allDay ) {
03422     return dateToString( date, shortfmt );
03423   }
03424 
03425   return  KGlobal::locale()->formatDateTime( date, shortfmt );
03426 }
03427 
03428 QString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence )
03429 {
03430   if ( !calendar || !incidence ) {
03431     return QString::null;
03432   }
03433 
03434   CalendarResources *calendarResource = dynamic_cast<CalendarResources*>( calendar );
03435   if ( !calendarResource ) {
03436     return QString::null;
03437   }
03438 
03439   ResourceCalendar *resourceCalendar = calendarResource->resource( incidence );
03440   if ( resourceCalendar ) {
03441     if ( !resourceCalendar->subresources().isEmpty() ) {
03442       QString subRes = resourceCalendar->subresourceIdentifier( incidence );
03443       if ( subRes.isEmpty() ) {
03444         return resourceCalendar->resourceName();
03445       } else {
03446         return resourceCalendar->labelForSubresource( subRes );
03447       }
03448     }
03449     return resourceCalendar->resourceName();
03450   }
03451 
03452   return QString::null;
03453 }
KDE Home | KDE Accessibility Home | Description of Access Keys