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