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