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