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