libkcal

incidenceformatter.cpp

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