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