libkcal

incidenceformatter.cpp

Go to the documentation of this file.
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 <kdebug.h>
00046 
00047 #include <klocale.h>
00048 #include <kglobal.h>
00049 #include <kiconloader.h>
00050 
00051 #include <qbuffer.h>
00052 #include <qstylesheet.h>
00053 #include <qdatetime.h>
00054 
00055 #include <time.h>
00056 
00057 
00058 using namespace KCal;
00059 
00060 
00061 /*******************************************************************
00062  *  Helper functions for the extensive display (event viewer)
00063  *******************************************************************/
00064 
00065 static QString eventViewerAddLink( const QString &ref, const QString &text,
00066                              bool newline = true )
00067 {
00068   QString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" );
00069   if ( newline ) tmpStr += "\n";
00070   return tmpStr;
00071 }
00072 
00073 static QString eventViewerAddTag( const QString & tag, const QString & text )
00074 {
00075   int numLineBreaks = text.contains( "\n" );
00076   QString str = "<" + tag + ">";
00077   QString tmpText = text;
00078   QString tmpStr = str;
00079   if( numLineBreaks >= 0 ) {
00080     if ( numLineBreaks > 0) {
00081       int pos = 0;
00082       QString tmp;
00083       for( int i = 0; i <= numLineBreaks; i++ ) {
00084         pos = tmpText.find( "\n" );
00085         tmp = tmpText.left( pos );
00086         tmpText = tmpText.right( tmpText.length() - pos - 1 );
00087         tmpStr += tmp + "<br>";
00088       }
00089     } else {
00090       tmpStr += tmpText;
00091     }
00092   }
00093   tmpStr += "</" + tag + ">";
00094   return tmpStr;
00095 }
00096 
00097 static QString linkPerson( const QString& email, QString name, QString uid )
00098 {
00099   // Make the search, if there is an email address to search on,
00100   // and either name or uid is missing
00101   if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
00102     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
00103     KABC::Addressee::List addressList = add_book->findByEmail( email );
00104     KABC::Addressee o = addressList.first();
00105     if ( !o.isEmpty() && addressList.size() < 2 ) {
00106       if ( name.isEmpty() )
00107         // No name set, so use the one from the addressbook
00108         name = o.formattedName();
00109       uid = o.uid();
00110     } else
00111       // Email not found in the addressbook. Don't make a link
00112       uid = QString::null;
00113   }
00114   kdDebug(5850) << "formatAttendees: uid = " << uid << endl;
00115 
00116   // Show the attendee
00117   QString tmpString = "<li>";
00118   if ( !uid.isEmpty() ) {
00119     // There is a UID, so make a link to the addressbook
00120     if ( name.isEmpty() )
00121       // Use the email address for text
00122       tmpString += eventViewerAddLink( "uid:" + uid, email );
00123     else
00124       tmpString += eventViewerAddLink( "uid:" + uid, name );
00125   } else {
00126     // No UID, just show some text
00127     tmpString += ( name.isEmpty() ? email : name );
00128   }
00129   tmpString += '\n';
00130 
00131   // Make the mailto link
00132   if ( !email.isEmpty() ) {
00133     KCal::Person person( name, email );
00134     KURL mailto;
00135     mailto.setProtocol( "mailto" );
00136     mailto.setPath( person.fullName() );
00137     tmpString += eventViewerAddLink( mailto.url(), QString::null );
00138   }
00139   tmpString += "</li>\n";
00140 
00141   return tmpString;
00142 }
00143 
00144 static QString eventViewerFormatAttendees( Incidence *event )
00145 {
00146   QString tmpStr;
00147   Attendee::List attendees = event->attendees();
00148   if ( attendees.count() ) {
00149 
00150     // Add organizer link
00151     tmpStr += eventViewerAddTag( "i", i18n("Organizer") );
00152     tmpStr += "<ul>";
00153     tmpStr += linkPerson( event->organizer().email(),
00154                           event->organizer().name(), QString::null );
00155     tmpStr += "</ul>";
00156 
00157     // Add attendees links
00158     tmpStr += eventViewerAddTag( "i", i18n("Attendees") );
00159     tmpStr += "<ul>";
00160     Attendee::List::ConstIterator it;
00161     for( it = attendees.begin(); it != attendees.end(); ++it ) {
00162       Attendee *a = *it;
00163       tmpStr += linkPerson( a->email(), a->name(), a->uid() );
00164       if ( !a->delegator().isEmpty() ) {
00165           tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
00166       }
00167       if ( !a->delegate().isEmpty() ) {
00168           tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
00169       }
00170     }
00171     tmpStr += "</ul>";
00172   }
00173   return tmpStr;
00174 }
00175 
00176 static QString eventViewerFormatAttachments( Incidence *i )
00177 {
00178   QString tmpStr;
00179   Attachment::List as = i->attachments();
00180   if ( as.count() > 0 ) {
00181     Attachment::List::ConstIterator it;
00182     for( it = as.begin(); it != as.end(); ++it ) {
00183       if ( (*it)->isUri() ) {
00184         QString name;
00185         if ( (*it)->uri().startsWith( "kmail:" ) )
00186           name = i18n( "Show mail" );
00187         else
00188           name = (*it)->uri();
00189         tmpStr += eventViewerAddLink( (*it)->uri(), name );
00190         tmpStr += "<br>";
00191       }
00192     }
00193   }
00194   return tmpStr;
00195 }
00196 
00197 /*
00198   FIXME:This function depends of kaddressbook. Is necessary a new
00199   type of event?
00200 */
00201 static QString eventViewerFormatBirthday( Event *event )
00202 {
00203   if ( !event) return  QString::null;
00204   if ( event->customProperty("KABC","BIRTHDAY") != "YES" ) return QString::null;
00205 
00206   QString uid = event->customProperty("KABC","UID-1");
00207   QString name = event->customProperty("KABC","NAME-1");
00208   QString email= event->customProperty("KABC","EMAIL-1");
00209 
00210   QString tmpString = "<ul>";
00211   tmpString += linkPerson( email, name, uid );
00212 
00213   if ( event->customProperty( "KABC", "ANNIVERSARY") == "YES" ) {
00214     uid = event->customProperty("KABC","UID-2");
00215     name = event->customProperty("KABC","NAME-2");
00216     email= event->customProperty("KABC","EMAIL-2");
00217     tmpString += linkPerson( email, name, uid );
00218   }
00219 
00220   tmpString += "</ul>";
00221   return tmpString;
00222 }
00223 
00224 static QString eventViewerFormatHeader( Incidence *incidence )
00225 {
00226   QString tmpStr = "<table><tr>";
00227 
00228   // show icons
00229   {
00230     tmpStr += "<td>";
00231 
00232     if ( incidence->type() == "Event" ) {
00233       tmpStr += "<img src=\"" +
00234                 KGlobal::iconLoader()->iconPath( "appointment", KIcon::Small ) +
00235                 "\">";
00236     }
00237     if ( incidence->type() == "Todo" ) {
00238       tmpStr += "<img src=\"" +
00239                 KGlobal::iconLoader()->iconPath( "todo", KIcon::Small ) +
00240                 "\">";
00241     }
00242     if ( incidence->type() == "Journal" ) {
00243       tmpStr += "<img src=\"" +
00244                 KGlobal::iconLoader()->iconPath( "journal", KIcon::Small ) +
00245                 "\">";
00246     }
00247     if ( incidence->isAlarmEnabled() ) {
00248       tmpStr += "<img src=\"" +
00249                 KGlobal::iconLoader()->iconPath( "bell", KIcon::Small ) +
00250                 "\">";
00251     }
00252     if ( incidence->doesRecur() ) {
00253       tmpStr += "<img src=\"" +
00254                 KGlobal::iconLoader()->iconPath( "recur", KIcon::Small ) +
00255                 "\">";
00256     }
00257     if ( incidence->isReadOnly() ) {
00258       tmpStr += "<img src=\"" +
00259                 KGlobal::iconLoader()->iconPath( "readonlyevent", KIcon::Small ) +
00260                 "\">";
00261     }
00262 
00263     tmpStr += "</td>";
00264   }
00265 
00266   tmpStr += "<td>"
00267             + eventViewerAddTag( "u",
00268                                  eventViewerAddTag( "b", incidence->summary() ) )
00269             + "</td>";
00270   tmpStr += "</tr></table><br>";
00271 
00272   return tmpStr;
00273 }
00274 
00275 static QString eventViewerFormatEvent( Event *event )
00276 {
00277   if ( !event ) return QString::null;
00278   QString tmpStr = eventViewerFormatHeader( event );
00279 
00280   tmpStr += "<table>";
00281 
00282   tmpStr += "<tr>";
00283   if ( event->doesFloat() ) {
00284     if ( event->isMultiDay() ) {
00285       tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>";
00286       tmpStr += "<td>" + i18n("<beginTime> - <endTime>","%1 - %2")
00287                     .arg( event->dtStartDateStr() )
00288                     .arg( event->dtEndDateStr() ) + "</td>";
00289     } else {
00290       tmpStr += "<td align=\"right\"><b>" + i18n( "Date" ) + "</b></td>";
00291       tmpStr += "<td>" + i18n("date as string","%1").arg( event->dtStartDateStr() ) + "</td>";
00292     }
00293   } else {
00294     if ( event->isMultiDay() ) {
00295       tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>";
00296       tmpStr += "<td>" + i18n("<beginTime> - <endTime>","%1 - %2")
00297                     .arg( event->dtStartStr() )
00298                     .arg( event->dtEndStr() ) + "</td>";
00299     } else {
00300       tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>";
00301       if ( event->hasEndDate() && event->dtStart() != event->dtEnd()) {
00302         tmpStr += "<td>" + i18n("<beginTime> - <endTime>","%1 - %2")
00303                       .arg( event->dtStartTimeStr() )
00304                       .arg( event->dtEndTimeStr() ) + "</td>";
00305       } else {
00306         tmpStr += "<td>" + event->dtStartTimeStr() + "</td>";
00307       }
00308       tmpStr += "</tr><tr>";
00309       tmpStr += "<td align=\"right\"><b>" + i18n( "Date" ) + "</b></td>";
00310       tmpStr += "<td>" + i18n("date as string","%1")
00311                     .arg( event->dtStartDateStr() ) + "</td>";
00312     }
00313   }
00314   tmpStr += "</tr>";
00315 
00316   if ( event->customProperty("KABC","BIRTHDAY")== "YES" ) {
00317     tmpStr += "<tr>";
00318     tmpStr += "<td align=\"right\"><b>" + i18n( "Birthday" ) + "</b></td>";
00319     tmpStr += "<td>" + eventViewerFormatBirthday( event ) + "</td>";
00320     tmpStr += "</tr>";
00321     tmpStr += "</table>";
00322     return tmpStr;
00323   }
00324 
00325   if ( !event->description().isEmpty() ) {
00326     tmpStr += "<tr>";
00327     tmpStr += "<td align=\"right\"><b>" + i18n( "Description" ) + "</b></td>";
00328     tmpStr += "<td>" + eventViewerAddTag( "p", event->description() ) + "</td>";
00329     tmpStr += "</tr>";
00330   }
00331 
00332   if ( !event->location().isEmpty() ) {
00333     tmpStr += "<tr>";
00334     tmpStr += "<td align=\"right\"><b>" + i18n( "Location" ) + "</b></td>";
00335     tmpStr += "<td>" + event->location() + "</td>";
00336     tmpStr += "</tr>";
00337   }
00338 
00339   if ( event->categories().count() > 0 ) {
00340     tmpStr += "<tr>";
00341     tmpStr += "<td align=\"right\"><b>" + i18n( "1 Category", "%n Categories", event->categories().count() )+ "</b></td>";
00342     tmpStr += "<td>" + event->categoriesStr() + "</td>";
00343     tmpStr += "</tr>";
00344   }
00345 
00346   if ( event->doesRecur() ) {
00347     QDateTime dt =
00348       event->recurrence()->getNextDateTime( QDateTime::currentDateTime() );
00349     tmpStr += "<tr>";
00350     tmpStr += "<td align=\"right\"><b>" + i18n( "Next on" ) + "</b></td>";
00351     if ( !event->doesFloat() ) {
00352       tmpStr += "<td>" +
00353                 KGlobal::locale()->formatDateTime( dt, true ) + "</td>";
00354     } else {
00355       tmpStr += "<td>" +
00356                 KGlobal::locale()->formatDate( dt.date(), true ) + "</td>";
00357     }
00358     tmpStr += "</tr>";
00359   }
00360 
00361   int attendeeCount = event->attendees().count();
00362   if ( attendeeCount > 0 ) {
00363     tmpStr += "<tr><td colspan=\"2\">";
00364     tmpStr += eventViewerFormatAttendees( event );
00365     tmpStr += "</td></tr>";
00366   }
00367 
00368   int attachmentCount = event->attachments().count();
00369   if ( attachmentCount > 0 ) {
00370     tmpStr += "<tr>";
00371     tmpStr += "<td align=\"right\"><b>" + i18n( "1 attachment", "%n attachments", attachmentCount )+ "</b></td>";
00372     tmpStr += "<td>" + eventViewerFormatAttachments( event ) + "</td>";
00373     tmpStr += "</tr>";
00374   }
00375 
00376   tmpStr += "</table>";
00377   tmpStr += "<em>" + i18n( "Creation date: %1.").arg(
00378     KGlobal::locale()->formatDateTime( event->created() , true ) ) + "</em>";
00379   return tmpStr;
00380 }
00381 
00382 static QString eventViewerFormatTodo( Todo *todo )
00383 {
00384   if ( !todo ) return QString::null;
00385   QString tmpStr = eventViewerFormatHeader( todo );
00386 
00387   tmpStr += "<table>";
00388 
00389   if ( todo->hasDueDate() ) {
00390     tmpStr += "<tr>";
00391     tmpStr += "<td align=\"right\"><b>" + i18n( "Due on" ) + "</b></td>";
00392     if ( !todo->doesFloat() ) {
00393       tmpStr += "<td>" +
00394                 KGlobal::locale()->formatDateTime( todo->dtDue(), true ) +
00395                 "</td>";
00396     } else {
00397       tmpStr += "<td>" +
00398                 KGlobal::locale()->formatDate( todo->dtDue().date(), true ) +
00399                 "</td>";
00400     }
00401     tmpStr += "</tr>";
00402   }
00403 
00404   if ( !todo->description().isEmpty() ) {
00405     tmpStr += "<tr>";
00406     tmpStr += "<td align=\"right\"><b>" + i18n( "Description" ) + "</b></td>";
00407     tmpStr += "<td>" + todo->description() + "</td>";
00408     tmpStr += "</tr>";
00409   }
00410 
00411   if ( !todo->location().isEmpty() ) {
00412     tmpStr += "<tr>";
00413     tmpStr += "<td align=\"right\"><b>" + i18n( "Location" ) + "</b></td>";
00414     tmpStr += "<td>" + todo->location() + "</td>";
00415     tmpStr += "</tr>";
00416   }
00417 
00418   if ( todo->categories().count() > 0 ) {
00419     tmpStr += "<tr>";
00420     tmpStr += "<td align=\"right\"><b>" + i18n( "1 Category", "%n Categories", todo->categories().count() )+ "</b></td>";
00421     tmpStr += "<td>" + todo->categoriesStr() + "</td>";
00422     tmpStr += "</tr>";
00423   }
00424 
00425   tmpStr += "<tr>";
00426   tmpStr += "<td align=\"right\"><b>" + i18n( "Priority" ) + "</b></td>";
00427   if ( todo->priority() > 0 ) {
00428     tmpStr += "<td>" + QString::number( todo->priority() ) + "</td>";
00429   } else {
00430     tmpStr += "<td>" + i18n( "Unspecified" ) + "</td>";
00431   }
00432   tmpStr += "</tr>";
00433 
00434   tmpStr += "<tr>";
00435   tmpStr += "<td align=\"right\"><b>" + i18n( "Completed" ) + "</b></td>";
00436   tmpStr += "<td>" + i18n( "%1 %" ).arg( todo->percentComplete() ) + "</td>";
00437   tmpStr += "</tr>";
00438 
00439   if ( todo->doesRecur() ) {
00440     QDateTime dt =
00441       todo->recurrence()->getNextDateTime( QDateTime::currentDateTime() );
00442     tmpStr += "<tr>";
00443     tmpStr += "<td align=\"right\"><b>" + i18n( "Next on" ) + "</b></td>";
00444     if ( !todo->doesFloat() ) {
00445       tmpStr += "<td>" +
00446                 KGlobal::locale()->formatDateTime( dt, true ) + "</td>";
00447     } else {
00448       tmpStr += "<td>" +
00449                 KGlobal::locale()->formatDate( dt.date(), true ) + "</td>";
00450     }
00451     tmpStr += "</tr>";
00452   }
00453 
00454   int attendeeCount = todo->attendees().count();
00455   if ( attendeeCount > 0 ) {
00456     tmpStr += "<tr><td colspan=\"2\">";
00457     tmpStr += eventViewerFormatAttendees( todo );
00458     tmpStr += "</td></tr>";
00459   }
00460 
00461   int attachmentCount = todo->attachments().count();
00462   if ( attachmentCount > 0 ) {
00463     tmpStr += "<tr>";
00464     tmpStr += "<td align=\"right\"><b>" + i18n( "1 attachment", "%n attachments", attachmentCount )+ "</b></td>";
00465     tmpStr += "<td>" + eventViewerFormatAttachments( todo ) + "</td>";
00466     tmpStr += "</tr>";
00467   }
00468 
00469   tmpStr += "</table>";
00470   tmpStr += "<em>" + i18n( "Creation date: %1.").arg(
00471     KGlobal::locale()->formatDateTime( todo->created(), true ) ) + "</em>";
00472   return tmpStr;
00473 }
00474 
00475 static QString eventViewerFormatJournal( Journal *journal )
00476 {
00477   if ( !journal ) return QString::null;
00478 
00479   QString tmpStr;
00480   if ( !journal->summary().isEmpty() ) {
00481     tmpStr += eventViewerAddTag( "u",
00482                                  eventViewerAddTag( "b", journal->summary() ) );
00483   }
00484   tmpStr += eventViewerAddTag( "b", i18n("Journal for %1").arg( journal->dtStartDateStr( false ) ) );
00485   if ( !journal->description().isEmpty() )
00486     tmpStr += eventViewerAddTag( "p", journal->description() );
00487   return tmpStr;
00488 }
00489 
00490 static QString eventViewerFormatFreeBusy( FreeBusy *fb )
00491 {
00492   if ( !fb ) return QString::null;
00493 
00494   QString tmpStr =
00495     eventViewerAddTag( "u",
00496                        eventViewerAddTag( "b", i18n("Free/Busy information for %1")
00497                                           .arg( fb->organizer().fullName() ) ) );
00498   tmpStr += eventViewerAddTag( "i", i18n("Busy times in date range %1 - %2:")
00499       .arg( KGlobal::locale()->formatDate( fb->dtStart().date(), true ) )
00500       .arg( KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) ) );
00501 
00502   QValueList<Period> periods = fb->busyPeriods();
00503 
00504   QString text = eventViewerAddTag( "em", eventViewerAddTag( "b", i18n("Busy:") ) );
00505   QValueList<Period>::iterator it;
00506   for ( it = periods.begin(); it != periods.end(); ++it ) {
00507     Period per = *it;
00508     if ( per.hasDuration() ) {
00509       int dur = per.duration().asSeconds();
00510       QString cont;
00511       if ( dur >= 3600 ) {
00512         cont += i18n("1 hour ", "%n hours ", dur / 3600 );
00513         dur %= 3600;
00514       }
00515       if ( dur >= 60 ) {
00516         cont += i18n("1 minute ", "%n minutes ", dur / 60);
00517         dur %= 60;
00518       }
00519       if ( dur > 0 ) {
00520         cont += i18n("1 second", "%n seconds", dur);
00521       }
00522       text += i18n("startDate for duration", "%1 for %2")
00523           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
00524           .arg( cont );
00525       text += "<br>";
00526     } else {
00527       if ( per.start().date() == per.end().date() ) {
00528         text += i18n("date, fromTime - toTime ", "%1, %2 - %3")
00529             .arg( KGlobal::locale()->formatDate( per.start().date() ) )
00530             .arg( KGlobal::locale()->formatTime( per.start().time() ) )
00531             .arg( KGlobal::locale()->formatTime( per.end().time() ) );
00532       } else {
00533         text += i18n("fromDateTime - toDateTime", "%1 - %2")
00534           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
00535           .arg( KGlobal::locale()->formatDateTime( per.end(), false ) );
00536       }
00537       text += "<br>";
00538     }
00539   }
00540   tmpStr += eventViewerAddTag( "p", text );
00541   return tmpStr;
00542 }
00543 
00544 class IncidenceFormatter::EventViewerVisitor : public IncidenceBase::Visitor
00545 {
00546   public:
00547     EventViewerVisitor() { mResult = ""; }
00548     bool act( IncidenceBase *incidence ) { return incidence->accept( *this ); }
00549     QString result() const { return mResult; }
00550   protected:
00551     bool visit( Event *event )
00552     {
00553       mResult = eventViewerFormatEvent( event );
00554       return !mResult.isEmpty();
00555     }
00556     bool visit( Todo *todo )
00557     {
00558       mResult = eventViewerFormatTodo( todo );
00559       return !mResult.isEmpty();
00560     }
00561     bool visit( Journal *journal )
00562     {
00563       mResult = eventViewerFormatJournal( journal );
00564       return !mResult.isEmpty();
00565     }
00566     bool visit( FreeBusy *fb )
00567     {
00568       mResult = eventViewerFormatFreeBusy( fb );
00569       return !mResult.isEmpty();
00570     }
00571 
00572   protected:
00573     QString mResult;
00574 };
00575 
00576 QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence )
00577 {
00578   if ( !incidence ) return QString::null;
00579   EventViewerVisitor v;
00580   if ( v.act( incidence ) ) {
00581     return v.result();
00582   } else
00583     return QString::null;
00584 }
00585 
00586 
00587 
00588 
00589 /*******************************************************************
00590  *  Helper functions for the body part formatter of kmail
00591  *******************************************************************/
00592 
00593 static QString string2HTML( const QString& str )
00594 {
00595   return QStyleSheet::convertFromPlainText(str, QStyleSheetItem::WhiteSpaceNormal);
00596 }
00597 
00598 static QString eventStartTimeStr( Event *event )
00599 {
00600   QString tmp;
00601   if ( ! event->doesFloat() ) {
00602     tmp =  i18n("%1: Start Date, %2: Start Time", "%1 %2")
00603              .arg( event->dtStartDateStr(), event->dtStartTimeStr() );
00604   } else {
00605     tmp = i18n("%1: Start Date", "%1 (time unspecified)")
00606             .arg( event->dtStartDateStr() );
00607   }
00608   return tmp;
00609 }
00610 
00611 static QString eventEndTimeStr( Event *event )
00612 {
00613   QString tmp;
00614   if ( event->hasEndDate() ) {
00615     if ( ! event->doesFloat() ) {
00616       tmp =  i18n("%1: End Date, %2: End Time", "%1 %2")
00617                .arg( event->dtEndDateStr(), event->dtEndTimeStr() );
00618     } else {
00619       tmp = i18n("%1: End Date", "%1 (time unspecified)")
00620               .arg( event->dtEndDateStr() );
00621     }
00622   } else {
00623     tmp = i18n( "Unspecified" );
00624   }
00625   return tmp;
00626 }
00627 
00628 static QString invitationRow( const QString &cell1, const QString &cell2 )
00629 {
00630   return "<tr><td>" + cell1 + "</td><td>" + cell2 + "</td></tr>\n";
00631 }
00632 
00633 static QString invitationsDetailsIncidence( Incidence *incidence )
00634 {
00635   QString html;
00636   QString descr = incidence->description();
00637   if( !descr.isEmpty() ) {
00638     html += "<br/><u>" + i18n("Description:")
00639       + "</u><table border=\"0\"><tr><td>&nbsp;</td><td>";
00640     html += string2HTML(descr) + "</td></tr></table>";
00641   }
00642   QStringList comments = incidence->comments();
00643   if ( !comments.isEmpty() ) {
00644     html += "<br><u>" + i18n("Comments:")
00645           + "</u><table border=\"0\"><tr><td>&nbsp;</td><td><ul>";
00646     for ( uint i = 0; i < comments.count(); ++i )
00647       html += "<li>" + string2HTML( comments[i] ) + "</li>";
00648     html += "</ul></td></tr></table>";
00649   }
00650   return html;
00651 }
00652 
00653 static QString invitationDetailsEvent( Event* event )
00654 {
00655   // Meeting details are formatted into an HTML table
00656   if ( !event )
00657     return QString::null;
00658 
00659   QString html;
00660   QString tmp;
00661 
00662   QString sSummary = i18n( "Summary unspecified" );
00663   if ( ! event->summary().isEmpty() ) {
00664     sSummary = string2HTML( event->summary() );
00665   }
00666 
00667   QString sLocation = i18n( "Location unspecified" );
00668   if ( ! event->location().isEmpty() ) {
00669     sLocation = string2HTML( event->location() );
00670   }
00671 
00672   QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
00673   html = QString("<div dir=\"%1\">\n").arg(dir);
00674 
00675   html += "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n";
00676 
00677   // Meeting summary & location rows
00678   html += invitationRow( i18n( "What:" ), sSummary );
00679   html += invitationRow( i18n( "Where:" ), sLocation );
00680 
00681   // Meeting Start Time Row
00682   html += invitationRow( i18n( "Start Time:" ), eventStartTimeStr( event ) );
00683 
00684   // Meeting End Time Row
00685   html += invitationRow( i18n( "End Time:" ), eventEndTimeStr( event ) );
00686 
00687   // Meeting Duration Row
00688   if ( !event->doesFloat() && event->hasEndDate() ) {
00689     tmp = QString::null;
00690     QTime sDuration(0,0,0), t;
00691     int secs = event->dtStart().secsTo( event->dtEnd() );
00692     t = sDuration.addSecs( secs );
00693     if ( t.hour() > 0 ) {
00694       tmp += i18n( "1 hour ", "%n hours ", t.hour() );
00695     }
00696     if ( t.minute() > 0 ) {
00697       tmp += i18n( "1 minute ", "%n minutes ",  t.minute() );
00698     }
00699 
00700     html += invitationRow( i18n( "Duration:" ), tmp );
00701   }
00702 
00703   html += "</table>\n";
00704   html += invitationsDetailsIncidence( event );
00705   html += "</div>\n";
00706 
00707   return html;
00708 }
00709 
00710 static QString invitationDetailsTodo( Todo *todo )
00711 {
00712   // Task details are formatted into an HTML table
00713   if ( !todo )
00714     return QString::null;
00715 
00716   QString sSummary = i18n( "Summary unspecified" );
00717   QString sDescr = i18n( "Description unspecified" );
00718   if ( ! todo->summary().isEmpty() ) {
00719     sSummary = todo->summary();
00720   }
00721   if ( ! todo->description().isEmpty() ) {
00722     sDescr = todo->description();
00723   }
00724   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
00725   html += invitationRow( i18n( "Summary:" ), sSummary );
00726   html += invitationRow( i18n( "Description:" ), sDescr );
00727   html += "</table>\n";
00728   html += invitationsDetailsIncidence( todo );
00729 
00730   return html;
00731 }
00732 
00733 static QString invitationDetailsJournal( Journal *journal )
00734 {
00735   if ( !journal )
00736     return QString::null;
00737 
00738   QString sSummary = i18n( "Summary unspecified" );
00739   QString sDescr = i18n( "Description unspecified" );
00740   if ( ! journal->summary().isEmpty() ) {
00741     sSummary = journal->summary();
00742   }
00743   if ( ! journal->description().isEmpty() ) {
00744     sDescr = journal->description();
00745   }
00746   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
00747   html += invitationRow( i18n( "Summary:" ), sSummary );
00748   html += invitationRow( i18n( "Date:" ), journal->dtStartDateStr( false ) );
00749   html += invitationRow( i18n( "Description:" ), sDescr );
00750   html += "</table>\n";
00751   html += invitationsDetailsIncidence( journal );
00752 
00753   return html;
00754 }
00755 
00756 static QString invitationDetailsFreeBusy( FreeBusy *fb )
00757 {
00758   if ( !fb )
00759     return QString::null;
00760   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
00761 
00762   html += invitationRow( i18n("Person:"), fb->organizer().fullName() );
00763   html += invitationRow( i18n("Start date:"), fb->dtStartDateStr() );
00764   html += invitationRow( i18n("End date:"),
00765       KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) );
00766   html += "<tr><td colspan=2><hr></td></tr>\n";
00767   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
00768 
00769   QValueList<Period> periods = fb->busyPeriods();
00770 
00771   QValueList<Period>::iterator it;
00772   for ( it = periods.begin(); it != periods.end(); ++it ) {
00773     Period per = *it;
00774     if ( per.hasDuration() ) {
00775       int dur = per.duration().asSeconds();
00776       QString cont;
00777       if ( dur >= 3600 ) {
00778         cont += i18n("1 hour ", "%n hours ", dur / 3600);
00779         dur %= 3600;
00780       }
00781       if ( dur >= 60 ) {
00782         cont += i18n("1 minute", "%n minutes ", dur / 60);
00783         dur %= 60;
00784       }
00785       if ( dur > 0 ) {
00786         cont += i18n("1 second", "%n seconds", dur);
00787       }
00788       html += invitationRow( QString::null, i18n("startDate for duration", "%1 for %2")
00789           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
00790           .arg(cont) );
00791     } else {
00792       QString cont;
00793       if ( per.start().date() == per.end().date() ) {
00794         cont = i18n("date, fromTime - toTime ", "%1, %2 - %3")
00795             .arg( KGlobal::locale()->formatDate( per.start().date() ) )
00796             .arg( KGlobal::locale()->formatTime( per.start().time() ) )
00797             .arg( KGlobal::locale()->formatTime( per.end().time() ) );
00798       } else {
00799         cont = i18n("fromDateTime - toDateTime", "%1 - %2")
00800           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
00801           .arg( KGlobal::locale()->formatDateTime( per.end(), false ) );
00802       }
00803 
00804       html += invitationRow( QString::null, cont );
00805     }
00806   }
00807 
00808   html += "</table>\n";
00809   return html;
00810 }
00811 
00812 static QString invitationHeaderEvent( Event *event, ScheduleMessage *msg )
00813 {
00814   if ( !msg || !event )
00815     return QString::null;
00816   switch ( msg->method() ) {
00817     case Scheduler::Publish:
00818         return i18n("This event has been published");
00819     case Scheduler::Request:
00820         if ( event->revision() > 0 )
00821             return i18n( "This meeting has been updated" );
00822         return i18n( "You have been invited to this meeting" );
00823     case Scheduler::Refresh:
00824         return i18n( "This invitation was refreshed" );
00825     case Scheduler::Cancel:
00826         return i18n( "This meeting has been canceled" );
00827     case Scheduler::Add:
00828         return i18n( "Addition to the meeting invitation" );
00829     case Scheduler::Reply: {
00830         Attendee::List attendees = event->attendees();
00831         if( attendees.count() == 0 ) {
00832           kdDebug(5850) << "No attendees in the iCal reply!\n";
00833           return QString::null;
00834         }
00835         if( attendees.count() != 1 )
00836           kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
00837                         << "but is " << attendees.count() << endl;
00838         Attendee* attendee = *attendees.begin();
00839         QString attendeeName = attendee->name();
00840         if ( attendeeName.isEmpty() )
00841           attendeeName = attendee->email();
00842         if ( attendeeName.isEmpty() )
00843           attendeeName = i18n( "Sender" );
00844 
00845         QString delegatorName, dummy;
00846         KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
00847         if ( delegatorName.isEmpty() )
00848           delegatorName = attendee->delegator();
00849 
00850         switch( attendee->status() ) {
00851           case Attendee::NeedsAction:
00852               return i18n( "%1 indicates this invitation still needs some action" ).arg( attendeeName );
00853           case Attendee::Accepted:
00854               if ( delegatorName.isEmpty() )
00855                   return i18n( "%1 accepts this meeting invitation" ).arg( attendeeName );
00856               return i18n( "%1 accepts this meeting invitation on behalf of %2" )
00857                   .arg( attendeeName ).arg( delegatorName );
00858           case Attendee::Tentative:
00859               if ( delegatorName.isEmpty() )
00860                   return i18n( "%1 tentatively accepts this meeting invitation" ).arg( attendeeName );
00861               return i18n( "%1 tentatively accepts this meeting invitation on behalf of %2" )
00862                   .arg( attendeeName ).arg( delegatorName );
00863           case Attendee::Declined:
00864               if ( delegatorName.isEmpty() )
00865                   return i18n( "%1 declines this meeting invitation" ).arg( attendeeName );
00866               return i18n( "%1 declines this meeting invitation on behalf of %2" )
00867                   .arg( attendeeName ).arg( delegatorName );
00868           case Attendee::Delegated: {
00869               QString delegate, dummy;
00870               KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
00871               if ( delegate.isEmpty() )
00872                   delegate = attendee->delegate();
00873               if ( !delegate.isEmpty() )
00874                 return i18n( "%1 has delegated this meeting invitation to %2" )
00875                     .arg( attendeeName ) .arg( delegate );
00876               return i18n( "%1 has delegated this meeting invitation" ).arg( attendeeName );
00877           }
00878           case Attendee::Completed:
00879               return i18n( "This meeting invitation is now completed" );
00880           case Attendee::InProcess:
00881               return i18n( "%1 is still processing the invitation" ).arg( attendeeName );
00882           default:
00883               return i18n( "Unknown response to this meeting invitation" );
00884         }
00885         break; }
00886     case Scheduler::Counter:
00887         return i18n( "Sender makes this counter proposal" );
00888     case Scheduler::Declinecounter:
00889         return i18n( "Sender declines the counter proposal" );
00890     case Scheduler::NoMethod:
00891         return i18n("Error: iMIP message with unknown method: '%1'")
00892             .arg( msg->method() );
00893   }
00894   return QString::null;
00895 }
00896 
00897 static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg )
00898 {
00899   if ( !msg || !todo )
00900     return QString::null;
00901   switch ( msg->method() ) {
00902     case Scheduler::Publish:
00903         return i18n("This task has been published");
00904     case Scheduler::Request:
00905         if ( todo->revision() > 0 )
00906             return i18n( "This task has been updated" );
00907         return i18n( "You have been assigned this task" );
00908     case Scheduler::Refresh:
00909         return i18n( "This task was refreshed" );
00910     case Scheduler::Cancel:
00911         return i18n( "This task was canceled" );
00912     case Scheduler::Add:
00913         return i18n( "Addition to the task" );
00914     case Scheduler::Reply: {
00915         Attendee::List attendees = todo->attendees();
00916         if( attendees.count() == 0 ) {
00917           kdDebug(5850) << "No attendees in the iCal reply!\n";
00918           return QString::null;
00919         }
00920         if( attendees.count() != 1 )
00921           kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
00922                         << "but is " << attendees.count() << endl;
00923         Attendee* attendee = *attendees.begin();
00924 
00925         switch( attendee->status() ) {
00926           case Attendee::NeedsAction:
00927               return i18n( "Sender indicates this task assignment still needs some action" );
00928           case Attendee::Accepted:
00929               return i18n( "Sender accepts this task" );
00930           case Attendee::Tentative:
00931               return i18n( "Sender tentatively accepts this task" );
00932           case Attendee::Declined:
00933               return i18n( "Sender declines this task" );
00934           case Attendee::Delegated: {
00935               QString delegate, dummy;
00936               KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
00937               if ( delegate.isEmpty() )
00938                 delegate = attendee->delegate();
00939               if ( !delegate.isEmpty() )
00940                 return i18n( "Sender has delegated this request for the task to %1" ).arg( delegate );
00941               return i18n( "Sender has delegated this request for the task " );
00942           }
00943           case Attendee::Completed:
00944               return i18n( "The request for this task is now completed" );
00945           case Attendee::InProcess:
00946               return i18n( "Sender is still processing the invitation" );
00947           default:
00948               return i18n( "Unknown response to this task" );
00949           }
00950         break; }
00951     case Scheduler::Counter:
00952         return i18n( "Sender makes this counter proposal" );
00953     case Scheduler::Declinecounter:
00954         return i18n( "Sender declines the counter proposal" );
00955     case Scheduler::NoMethod:
00956         return i18n("Error: iMIP message with unknown method: '%1'")
00957             .arg( msg->method() );
00958   }
00959   return QString::null;
00960 }
00961 
00962 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
00963 {
00964   // TODO: Several of the methods are not allowed for journals, so remove them.
00965   if ( !msg || !journal )
00966     return QString::null;
00967   switch ( msg->method() ) {
00968     case Scheduler::Publish:
00969         return i18n("This journal has been published");
00970     case Scheduler::Request:
00971         return i18n( "You have been assigned this journal" );
00972     case Scheduler::Refresh:
00973         return i18n( "This journal was refreshed" );
00974     case Scheduler::Cancel:
00975         return i18n( "This journal was canceled" );
00976     case Scheduler::Add:
00977         return i18n( "Addition to the journal" );
00978     case Scheduler::Reply: {
00979         Attendee::List attendees = journal->attendees();
00980         if( attendees.count() == 0 ) {
00981           kdDebug(5850) << "No attendees in the iCal reply!\n";
00982           return QString::null;
00983         }
00984         if( attendees.count() != 1 )
00985           kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
00986                         << "but is " << attendees.count() << endl;
00987         Attendee* attendee = *attendees.begin();
00988 
00989         switch( attendee->status() ) {
00990           case Attendee::NeedsAction:
00991               return i18n( "Sender indicates this journal assignment still needs some action" );
00992           case Attendee::Accepted:
00993               return i18n( "Sender accepts this journal" );
00994           case Attendee::Tentative:
00995               return i18n( "Sender tentatively accepts this journal" );
00996           case Attendee::Declined:
00997               return i18n( "Sender declines this journal" );
00998           case Attendee::Delegated:
00999               return i18n( "Sender has delegated this request for the journal" );
01000           case Attendee::Completed:
01001               return i18n( "The request for this journal is now completed" );
01002           case Attendee::InProcess:
01003               return i18n( "Sender is still processing the invitation" );
01004           default:
01005               return i18n( "Unknown response to this journal" );
01006           }
01007         break; }
01008     case Scheduler::Counter:
01009         return i18n( "Sender makes this counter proposal" );
01010     case Scheduler::Declinecounter:
01011         return i18n( "Sender declines the counter proposal" );
01012     case Scheduler::NoMethod:
01013         return i18n("Error: iMIP message with unknown method: '%1'")
01014             .arg( msg->method() );
01015   }
01016   return QString::null;
01017 }
01018 
01019 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
01020 {
01021   if ( !msg || !fb )
01022     return QString::null;
01023   switch ( msg->method() ) {
01024     case Scheduler::Publish:
01025         return i18n("This free/busy list has been published");
01026     case Scheduler::Request:
01027         return i18n( "The free/busy list has been requested" );
01028     case Scheduler::Refresh:
01029         return i18n( "This free/busy list was refreshed" );
01030     case Scheduler::Cancel:
01031         return i18n( "This free/busy list was canceled" );
01032     case Scheduler::Add:
01033         return i18n( "Addition to the free/busy list" );
01034     case Scheduler::NoMethod:
01035     default:
01036         return i18n("Error: Free/Busy iMIP message with unknown method: '%1'")
01037             .arg( msg->method() );
01038   }
01039 }
01040 
01041 class IncidenceFormatter::ScheduleMessageVisitor : public IncidenceBase::Visitor
01042 {
01043   public:
01044     ScheduleMessageVisitor() : mMessage(0) { mResult = ""; }
01045     bool act( IncidenceBase *incidence, ScheduleMessage *msg ) { mMessage = msg; return incidence->accept( *this ); }
01046     QString result() const { return mResult; }
01047 
01048   protected:
01049     QString mResult;
01050     ScheduleMessage *mMessage;
01051 };
01052 
01053 class IncidenceFormatter::InvitationHeaderVisitor :
01054       public IncidenceFormatter::ScheduleMessageVisitor
01055 {
01056   protected:
01057     bool visit( Event *event )
01058     {
01059       mResult = invitationHeaderEvent( event, mMessage );
01060       return !mResult.isEmpty();
01061     }
01062     bool visit( Todo *todo )
01063     {
01064       mResult = invitationHeaderTodo( todo, mMessage );
01065       return !mResult.isEmpty();
01066     }
01067     bool visit( Journal *journal )
01068     {
01069       mResult = invitationHeaderJournal( journal, mMessage );
01070       return !mResult.isEmpty();
01071     }
01072     bool visit( FreeBusy *fb )
01073     {
01074       mResult = invitationHeaderFreeBusy( fb, mMessage );
01075       return !mResult.isEmpty();
01076     }
01077 };
01078 
01079 class IncidenceFormatter::InvitationBodyVisitor :
01080       public IncidenceFormatter::ScheduleMessageVisitor
01081 {
01082   protected:
01083     bool visit( Event *event )
01084     {
01085       mResult = invitationDetailsEvent( event );
01086       return !mResult.isEmpty();
01087     }
01088     bool visit( Todo *todo )
01089     {
01090       mResult = invitationDetailsTodo( todo );
01091       return !mResult.isEmpty();
01092     }
01093     bool visit( Journal *journal )
01094     {
01095       mResult = invitationDetailsJournal( journal );
01096       return !mResult.isEmpty();
01097     }
01098     bool visit( FreeBusy *fb )
01099     {
01100       mResult = invitationDetailsFreeBusy( fb );
01101       return !mResult.isEmpty();
01102     }
01103 };
01104 
01105 class IncidenceFormatter::IncidenceCompareVisitor :
01106   public IncidenceBase::Visitor
01107 {
01108   public:
01109     IncidenceCompareVisitor() : mExistingIncidence(0) {}
01110     bool act( IncidenceBase *incidence, Incidence* existingIncidence )
01111     {
01112       mExistingIncidence = existingIncidence;
01113       return incidence->accept( *this );
01114     }
01115 
01116     QString result() const
01117     {
01118       if ( mChanges.isEmpty() )
01119         return QString();
01120       QString html = "<div align=\"left\"><ul><li>";
01121       html += mChanges.join( "</li><li>" );
01122       html += "</li><ul></div>";
01123       return html;
01124     }
01125 
01126   protected:
01127     bool visit( Event *event )
01128     {
01129       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
01130       compareIncidences( event, mExistingIncidence );
01131       return !mChanges.isEmpty();
01132     }
01133     bool visit( Todo *todo )
01134     {
01135       compareIncidences( todo, mExistingIncidence );
01136       return !mChanges.isEmpty();
01137     }
01138     bool visit( Journal *journal )
01139     {
01140       compareIncidences( journal, mExistingIncidence );
01141       return !mChanges.isEmpty();
01142     }
01143     bool visit( FreeBusy *fb )
01144     {
01145       Q_UNUSED( fb );
01146       return !mChanges.isEmpty();
01147     }
01148 
01149   private:
01150     void compareEvents( Event *newEvent, Event *oldEvent )
01151     {
01152       if ( !oldEvent || !newEvent )
01153         return;
01154       if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->doesFloat() != newEvent->doesFloat() )
01155         mChanges += i18n( "The begin of the meeting has been changed from %1 to %2" )
01156             .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) );
01157       if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->doesFloat() != newEvent->doesFloat() )
01158         mChanges += i18n( "The end of the meeting has been changed from %1 to %2" )
01159             .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) );
01160     }
01161 
01162     void compareIncidences( Incidence *newInc, Incidence *oldInc )
01163     {
01164       if ( !oldInc || !newInc )
01165         return;
01166       if ( oldInc->summary() != newInc->summary() )
01167         mChanges += i18n( "The summary has been changed to: \"%1\"" ).arg( newInc->summary() );
01168       if ( oldInc->location() != newInc->location() )
01169         mChanges += i18n( "The location has been changed to: \"%1\"" ).arg( newInc->location() );
01170       if ( oldInc->description() != newInc->description() )
01171         mChanges += i18n( "The description has been changed to: \"%1\"" ).arg( newInc->description() );
01172       Attendee::List oldAttendees = oldInc->attendees();
01173       Attendee::List newAttendees = newInc->attendees();
01174       for ( Attendee::List::ConstIterator it = newAttendees.constBegin(); it != newAttendees.constEnd(); ++it ) {
01175         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
01176         if ( !oldAtt ) {
01177           mChanges += i18n( "Attendee %1 has been added" ).arg( (*it)->fullName() );
01178         } else {
01179           if ( oldAtt->status() != (*it)->status() )
01180             mChanges += i18n( "The status of attendee %1 has been changed to: %2" ).arg( (*it)->fullName() )
01181                 .arg( (*it)->statusStr() );
01182         }
01183       }
01184       for ( Attendee::List::ConstIterator it = oldAttendees.constBegin(); it != oldAttendees.constEnd(); ++it ) {
01185         Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
01186         if ( !newAtt )
01187           mChanges += i18n( "Attendee %1 has been removed" ).arg( (*it)->fullName() );
01188       }
01189     }
01190 
01191   private:
01192     Incidence* mExistingIncidence;
01193     QStringList mChanges;
01194 };
01195 
01196 
01197 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
01198 {
01199   QString res( "<a href=\"%1\"><b>%2</b></a>" );
01200   return res.arg( generateLinkURL( id ) ).arg( text );
01201   return res;
01202 }
01203 
01204 // Check if the given incidence is likely one that we own instead one from
01205 // a shared calendar (Kolab-specific)
01206 static bool incidenceOwnedByMe( Calendar* calendar, Incidence *incidence )
01207 {
01208   CalendarResources* cal = dynamic_cast<CalendarResources*>( calendar );
01209   if ( !cal || !incidence )
01210     return true;
01211   ResourceCalendar* res = cal->resource( incidence );
01212   if ( !res )
01213     return true;
01214   const QString subRes = res->subresourceIdentifier( incidence );
01215   if ( !subRes.contains( "/.INBOX.directory/" ) )
01216     return false;
01217   return true;
01218 }
01219 
01220 QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar,
01221     InvitationFormatterHelper *helper )
01222 {
01223   if ( invitation.isEmpty() ) return QString::null;
01224 
01225   ICalFormat format;
01226   // parseScheduleMessage takes the tz from the calendar, no need to set it manually here for the format!
01227   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
01228 
01229   if( !msg ) {
01230     kdDebug( 5850 ) << "Failed to parse the scheduling message" << endl;
01231     Q_ASSERT( format.exception() );
01232     kdDebug( 5850 ) << format.exception()->message() << endl;
01233     return QString::null;
01234   }
01235 
01236   IncidenceBase *incBase = msg->event();
01237 
01238   Incidence* existingIncidence = 0;
01239   if ( helper->calendar() ) {
01240     existingIncidence = helper->calendar()->incidence( incBase->uid() );
01241     if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) )
01242       existingIncidence = 0;
01243     if ( !existingIncidence ) {
01244       const Incidence::List list = helper->calendar()->incidences();
01245       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
01246         if ( (*it)->schedulingID() == incBase->uid() && incidenceOwnedByMe( helper->calendar(), *it ) ) {
01247           existingIncidence = *it;
01248           break;
01249         }
01250       }
01251     }
01252   }
01253 
01254   // First make the text of the message
01255   QString html;
01256 
01257   QString tableStyle = QString::fromLatin1(
01258     "style=\"border: solid 1px; margin: 0em;\"" );
01259   QString tableHead = QString::fromLatin1(
01260     "<div align=\"center\">"
01261     "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
01262     "<tr><td>").arg(tableStyle);
01263 
01264   html += tableHead;
01265   InvitationHeaderVisitor headerVisitor;
01266   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
01267   if ( !headerVisitor.act( incBase, msg ) )
01268     return QString::null;
01269   html += "<b>" + headerVisitor.result() + "</b>";
01270 
01271   InvitationBodyVisitor bodyVisitor;
01272   if ( !bodyVisitor.act( incBase, msg ) )
01273     return QString::null;
01274   html += bodyVisitor.result();
01275 
01276   if ( msg->method() == Scheduler::Request ) { // ### Scheduler::Publish/Refresh/Add as well?
01277     IncidenceCompareVisitor compareVisitor;
01278     if ( compareVisitor.act( incBase, existingIncidence ) ) {
01279       html += i18n("<p align=\"left\">The following changes have been made by the organizer:</p>");
01280       html += compareVisitor.result();
01281     }
01282   }
01283 
01284   html += "<br/>";
01285   html += "<table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td></tr><tr>";
01286 
01287 #if 0
01288   html += helper->makeLinkURL( "accept", i18n("[Enter this into my calendar]") );
01289   html += "</td><td> &nbsp; </td><td>";
01290 #endif
01291 
01292   // Add groupware links
01293 
01294   switch ( msg->method() ) {
01295     case Scheduler::Publish:
01296     case Scheduler::Request:
01297     case Scheduler::Refresh:
01298     case Scheduler::Add:
01299     {
01300         Incidence *inc = dynamic_cast<Incidence*>( incBase );
01301         if ( inc && inc->revision() > 0 && (existingIncidence || !helper->calendar()) ) {
01302             if ( incBase->type() == "Todo" ) {
01303                 html += "<td colspan=\"9\">";
01304                 html += helper->makeLink( "reply", i18n( "[Enter this into my task list]" ) );
01305             } else {
01306                 html += "<td colspan=\"13\">";
01307                 html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01308             }
01309             html += "</td></tr><tr>";
01310         }
01311         html += "<td>";
01312 
01313         if ( !existingIncidence ) {
01314           // Accept
01315           html += helper->makeLink( "accept", i18n( "[Accept]" ) );
01316           html += "</td><td> &nbsp; </td><td>";
01317           html += helper->makeLink( "accept_conditionally",
01318                             i18n( "Accept conditionally", "[Accept cond.]" ) );
01319           html += "</td><td> &nbsp; </td><td>";
01320           // counter proposal
01321           html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
01322           html += "</td><td> &nbsp; </td><td>";
01323           // Decline
01324           html += helper->makeLink( "decline", i18n( "[Decline]" ) );
01325           html += "</td><td> &nbsp; </td><td>";
01326 
01327           // Delegate
01328           html += helper->makeLink( "delegate", i18n( "[Delegate]" ) );
01329           html += "</td><td> &nbsp; </td><td>";
01330 
01331           // Forward
01332           html += helper->makeLink( "forward", i18n( "[Forward]" ) );
01333 
01334           if ( incBase->type() == "Event" ) {
01335               html += "</b></a></td><td> &nbsp; </td><td>";
01336               html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
01337           }
01338         }
01339         break;
01340     }
01341 
01342     case Scheduler::Cancel:
01343         // Cancel event from my calendar
01344         html += helper->makeLink( "cancel", i18n( "[Remove this from my calendar]" ) );
01345         break;
01346 
01347     case Scheduler::Reply:
01348         // Enter this into my calendar
01349         if ( incBase->type() == "Todo" ) {
01350           html += helper->makeLink( "reply", i18n( "[Enter this into my task list]" ) );
01351         } else {
01352           html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01353         }
01354         break;
01355 
01356     case Scheduler::Counter:
01357         html += helper->makeLink( "accept_counter", i18n("[Accept]") );
01358         html += "&nbsp;";
01359         html += helper->makeLink( "decline_counter", i18n("[Decline]") );
01360         html += "&nbsp;";
01361         html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
01362         break;
01363     case Scheduler::Declinecounter:
01364     case Scheduler::NoMethod:
01365         break;
01366   }
01367 
01368   html += "</td></tr></table>";
01369 
01370   html += "</td></tr></table><br></div>";
01371 
01372   return html;
01373 }
01374 
01375 
01376 
01377 
01378 /*******************************************************************
01379  *  Helper functions for the msTNEF -> VPart converter
01380  *******************************************************************/
01381 
01382 
01383 //-----------------------------------------------------------------------------
01384 
01385 static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
01386                            const QString& fallback = QString::null)
01387 {
01388   return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
01389                             fallback );
01390 }
01391 
01392 static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
01393                            const QString& fallback = QString::null )
01394 {
01395   return tnefMsg->findNamedProp( name, fallback );
01396 }
01397 
01398 struct save_tz { char* old_tz; char* tz_env_str; };
01399 
01400 /* temporarily go to a different timezone */
01401 static struct save_tz set_tz( const char* _tc )
01402 {
01403   const char *tc = _tc?_tc:"UTC";
01404 
01405   struct save_tz rv;
01406 
01407   rv.old_tz = 0;
01408   rv.tz_env_str = 0;
01409 
01410   //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;
01411 
01412   char* tz_env = 0;
01413   if( getenv( "TZ" ) ) {
01414     tz_env = strdup( getenv( "TZ" ) );
01415     rv.old_tz = tz_env;
01416   }
01417   char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
01418   strcpy( tmp_env, "TZ=" );
01419   strcpy( tmp_env+3, tc );
01420   putenv( tmp_env );
01421 
01422   rv.tz_env_str = tmp_env;
01423 
01424   /* tmp_env is not free'ed -- it is part of the environment */
01425 
01426   tzset();
01427   //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;
01428 
01429   return rv;
01430 }
01431 
01432 /* restore previous timezone */
01433 static void unset_tz( struct save_tz old_tz )
01434 {
01435   if( old_tz.old_tz ) {
01436     char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
01437     strcpy( tmp_env, "TZ=" );
01438     strcpy( tmp_env+3, old_tz.old_tz );
01439     putenv( tmp_env );
01440     /* tmp_env is not free'ed -- it is part of the environment */
01441     free( old_tz.old_tz );
01442   } else {
01443     /* clear TZ from env */
01444     putenv( strdup("TZ") );
01445   }
01446   tzset();
01447 
01448   /* is this OK? */
01449   if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
01450 }
01451 
01452 static QDateTime utc2Local( const QDateTime& utcdt )
01453 {
01454   struct tm tmL;
01455 
01456   save_tz tmp_tz = set_tz("UTC");
01457   time_t utc = utcdt.toTime_t();
01458   unset_tz( tmp_tz );
01459 
01460   localtime_r( &utc, &tmL );
01461   return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
01462                     QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
01463 }
01464 
01465 
01466 static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
01467                                           bool bDateOnly = false )
01468 {
01469   QDate tmpDate;
01470   QTime tmpTime;
01471   int year, month, day, hour, minute, second;
01472 
01473   if( bDateOnly ) {
01474     year = dtStr.left( 4 ).toInt();
01475     month = dtStr.mid( 4, 2 ).toInt();
01476     day = dtStr.mid( 6, 2 ).toInt();
01477     hour = 0;
01478     minute = 0;
01479     second = 0;
01480   } else {
01481     year = dtStr.left( 4 ).toInt();
01482     month = dtStr.mid( 4, 2 ).toInt();
01483     day = dtStr.mid( 6, 2 ).toInt();
01484     hour = dtStr.mid( 9, 2 ).toInt();
01485     minute = dtStr.mid( 11, 2 ).toInt();
01486     second = dtStr.mid( 13, 2 ).toInt();
01487   }
01488   tmpDate.setYMD( year, month, day );
01489   tmpTime.setHMS( hour, minute, second );
01490 
01491   if( tmpDate.isValid() && tmpTime.isValid() ) {
01492     QDateTime dT = QDateTime( tmpDate, tmpTime );
01493 
01494     if( !bDateOnly ) {
01495       // correct for GMT ( == Zulu time == UTC )
01496       if (dtStr.at(dtStr.length()-1) == 'Z') {
01497         //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
01498         //localUTCOffset( dT ) );
01499         dT = utc2Local( dT );
01500       }
01501     }
01502     return dT;
01503   } else
01504     return QDateTime();
01505 }
01506 
01507 
01508 
01509 QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef )
01510 {
01511   bool bOk = false;
01512 
01513   KTNEFParser parser;
01514   QBuffer buf( tnef );
01515   CalendarLocal cal ( QString::fromLatin1( "UTC" ) );
01516   KABC::Addressee addressee;
01517   KABC::VCardConverter cardConv;
01518   ICalFormat calFormat;
01519   Event* event = new Event();
01520 
01521   if( parser.openDevice( &buf ) ) {
01522     KTNEFMessage* tnefMsg = parser.message();
01523     //QMap<int,KTNEFProperty*> props = parser.message()->properties();
01524 
01525     // Everything depends from property PR_MESSAGE_CLASS
01526     // (this is added by KTNEFParser):
01527     QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
01528       .upper();
01529     if( !msgClass.isEmpty() ) {
01530       // Match the old class names that might be used by Outlook for
01531       // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
01532       bool bCompatClassAppointment = false;
01533       bool bCompatMethodRequest = false;
01534       bool bCompatMethodCancled = false;
01535       bool bCompatMethodAccepted = false;
01536       bool bCompatMethodAcceptedCond = false;
01537       bool bCompatMethodDeclined = false;
01538       if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
01539         bCompatClassAppointment = true;
01540         if( msgClass.endsWith( ".MTGREQ" ) )
01541           bCompatMethodRequest = true;
01542         if( msgClass.endsWith( ".MTGCNCL" ) )
01543           bCompatMethodCancled = true;
01544         if( msgClass.endsWith( ".MTGRESPP" ) )
01545           bCompatMethodAccepted = true;
01546         if( msgClass.endsWith( ".MTGRESPA" ) )
01547           bCompatMethodAcceptedCond = true;
01548         if( msgClass.endsWith( ".MTGRESPN" ) )
01549           bCompatMethodDeclined = true;
01550       }
01551       bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );
01552 
01553       if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
01554         // Compose a vCal
01555         bool bIsReply = false;
01556         QString prodID = "-//Microsoft Corporation//Outlook ";
01557         prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
01558         prodID += "MIMEDIR/EN\n";
01559         prodID += "VERSION:2.0\n";
01560         calFormat.setApplication( "Outlook", prodID );
01561 
01562         Scheduler::Method method;
01563         if( bCompatMethodRequest )
01564           method = Scheduler::Request;
01565         else if( bCompatMethodCancled )
01566           method = Scheduler::Cancel;
01567         else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
01568                  bCompatMethodDeclined ) {
01569           method = Scheduler::Reply;
01570           bIsReply = true;
01571         } else {
01572           // pending(khz): verify whether "0x0c17" is the right tag ???
01573           //
01574           // at the moment we think there are REQUESTS and UPDATES
01575           //
01576           // but WHAT ABOUT REPLIES ???
01577           //
01578           //
01579 
01580           if( tnefMsg->findProp(0x0c17) == "1" )
01581             bIsReply = true;
01582           method = Scheduler::Request;
01583         }
01584 
01586         ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );
01587 
01588         QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );
01589 
01590         if( !sSenderSearchKeyEmail.isEmpty() ) {
01591           int colon = sSenderSearchKeyEmail.find( ':' );
01592           // May be e.g. "SMTP:KHZ@KDE.ORG"
01593           if( sSenderSearchKeyEmail.find( ':' ) == -1 )
01594             sSenderSearchKeyEmail.remove( 0, colon+1 );
01595         }
01596 
01597         QString s( tnefMsg->findProp( 0x0e04 ) );
01598         QStringList attendees = QStringList::split( ';', s );
01599         if( attendees.count() ) {
01600           for( QStringList::Iterator it = attendees.begin();
01601                it != attendees.end(); ++it ) {
01602             // Skip all entries that have no '@' since these are
01603             // no mail addresses
01604             if( (*it).find('@') == -1 ) {
01605               s = (*it).stripWhiteSpace();
01606 
01607               Attendee *attendee = new Attendee( s, s, true );
01608               if( bIsReply ) {
01609                 if( bCompatMethodAccepted )
01610                   attendee->setStatus( Attendee::Accepted );
01611                 if( bCompatMethodDeclined )
01612                   attendee->setStatus( Attendee::Declined );
01613                 if( bCompatMethodAcceptedCond )
01614                   attendee->setStatus(Attendee::Tentative);
01615               } else {
01616                 attendee->setStatus( Attendee::NeedsAction );
01617                 attendee->setRole( Attendee::ReqParticipant );
01618               }
01619               event->addAttendee(attendee);
01620             }
01621           }
01622         } else {
01623           // Oops, no attendees?
01624           // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
01625           s = sSenderSearchKeyEmail;
01626           if( !s.isEmpty() ) {
01627             Attendee *attendee = new Attendee( QString::null, QString::null,
01628                                                true );
01629             if( bIsReply ) {
01630               if( bCompatMethodAccepted )
01631                 attendee->setStatus( Attendee::Accepted );
01632               if( bCompatMethodAcceptedCond )
01633                 attendee->setStatus( Attendee::Declined );
01634               if( bCompatMethodDeclined )
01635                 attendee->setStatus( Attendee::Tentative );
01636             } else {
01637               attendee->setStatus(Attendee::NeedsAction);
01638               attendee->setRole(Attendee::ReqParticipant);
01639             }
01640             event->addAttendee(attendee);
01641           }
01642         }
01643         s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
01644         if( s.isEmpty() && !bIsReply )
01645           s = sSenderSearchKeyEmail;
01646         // TODO: Use the common name?
01647         if( !s.isEmpty() )
01648           event->setOrganizer( s );
01649 
01650         s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
01651           .replace( QChar( ':' ), QString::null );
01652         event->setDtStart( QDateTime::fromString( s ) ); // ## Format??
01653 
01654         s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
01655           .replace( QChar( ':' ), QString::null );
01656         event->setDtEnd( QDateTime::fromString( s ) );
01657 
01658         s = tnefMsg->findProp( 0x8208 );
01659         event->setLocation( s );
01660 
01661         // is it OK to set this to OPAQUE always ??
01662         //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
01663         //vPart += "SEQUENCE:0\n";
01664 
01665         // is "0x0023" OK  -  or should we look for "0x0003" ??
01666         s = tnefMsg->findProp( 0x0023 );
01667         event->setUid( s );
01668 
01669         // PENDING(khz): is this value in local timezone? Must it be
01670         // adjusted? Most likely this is a bug in the server or in
01671         // Outlook - we ignore it for now.
01672         s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
01673           .replace( QChar( ':' ), QString::null );
01674         // ### libkcal always uses currentDateTime()
01675         // event->setDtStamp(QDateTime::fromString(s));
01676 
01677         s = tnefMsg->findNamedProp( "Keywords" );
01678         event->setCategories( s );
01679 
01680         s = tnefMsg->findProp( 0x1000 );
01681         event->setDescription( s );
01682 
01683         s = tnefMsg->findProp( 0x0070 );
01684         event->setSummary( s );
01685 
01686         s = tnefMsg->findProp( 0x0026 );
01687         event->setPriority( s.toInt() );
01688 
01689         // is reminder flag set ?
01690         if(!tnefMsg->findProp(0x8503).isEmpty()) {
01691           Alarm *alarm = new Alarm(event);
01692           QDateTime highNoonTime =
01693             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
01694                                      .replace( QChar( '-' ), "" )
01695                                      .replace( QChar( ':' ), "" ) );
01696           QDateTime wakeMeUpTime =
01697             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
01698                                      .replace( QChar( '-' ), "" )
01699                                      .replace( QChar( ':' ), "" ) );
01700           alarm->setTime(wakeMeUpTime);
01701 
01702           if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
01703             alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
01704           else
01705             // default: wake them up 15 minutes before the appointment
01706             alarm->setStartOffset( Duration( 15*60 ) );
01707           alarm->setDisplayAlarm( i18n( "Reminder" ) );
01708 
01709           // Sorry: the different action types are not known (yet)
01710           //        so we always set 'DISPLAY' (no sounds, no images...)
01711           event->addAlarm( alarm );
01712         }
01713         cal.addEvent( event );
01714         bOk = true;
01715         // we finished composing a vCal
01716       } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
01717         addressee.setUid( stringProp( tnefMsg, attMSGID ) );
01718         addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) );
01719         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
01720         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
01721         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
01722         addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) );
01723         addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) );
01724         addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
01725         addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
01726         addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) );
01727         addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) );
01728         addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) );
01729 
01730         QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
01731           .replace( QChar( '-' ), QString::null )
01732           .replace( QChar( ':' ), QString::null );
01733         if( !s.isEmpty() )
01734           addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );
01735 
01736         addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );
01737 
01738         // collect parts of Name entry
01739         addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
01740         addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
01741         addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) );
01742         addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) );
01743         addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );
01744 
01745         addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
01746         addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
01747         addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
01748         /*
01749         the MAPI property ID of this (multiline) )field is unknown:
01750         vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
01751         */
01752 
01753         KABC::Address adr;
01754         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) );
01755         adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
01756         adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
01757         adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
01758         adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) );
01759         adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
01760         adr.setType(KABC::Address::Home);
01761         addressee.insertAddress(adr);
01762 
01763         adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
01764         adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) );
01765         adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) );
01766         adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) );
01767         adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
01768         adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) );
01769         adr.setType( KABC::Address::Work );
01770         addressee.insertAddress( adr );
01771 
01772         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) );
01773         adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
01774         adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
01775         adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
01776         adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) );
01777         adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
01778         adr.setType( KABC::Address::Dom );
01779         addressee.insertAddress(adr);
01780 
01781         // problem: the 'other' address was stored by KOrganizer in
01782         //          a line looking like the following one:
01783         // 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
01784 
01785         QString nr;
01786         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
01787         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) );
01788         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
01789         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) );
01790         nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
01791         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) );
01792         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
01793         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) );
01794         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
01795         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) );
01796 
01797         s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
01798           .replace( QChar( '-' ), QString::null )
01799           .replace( QChar( ':' ), QString::null );
01800         if( !s.isEmpty() )
01801           addressee.setBirthday( QDateTime::fromString( s ) );
01802 
01803         bOk = ( !addressee.isEmpty() );
01804       } else if( "IPM.NOTE" == msgClass ) {
01805 
01806       } // else if ... and so on ...
01807     }
01808   }
01809 
01810   // Compose return string
01811   QString iCal = calFormat.toString( &cal );
01812   if( !iCal.isEmpty() )
01813     // This was an iCal
01814     return iCal;
01815 
01816   // Not an iCal - try a vCard
01817   KABC::VCardConverter converter;
01818   return converter.createVCard( addressee );
01819 }
01820 
01821 
01822 QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef,
01823         Calendar *mCalendar, InvitationFormatterHelper *helper )
01824 {
01825   QString vPart = IncidenceFormatter::msTNEFToVPart( tnef );
01826   QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper );
01827   if( !iCal.isEmpty() )
01828     return iCal;
01829   return vPart;
01830 }
01831 
01832 
01833 
01834 
01835 /*******************************************************************
01836  *  Helper functions for the Incidence tooltips
01837  *******************************************************************/
01838 
01839 class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
01840 {
01841   public:
01842     ToolTipVisitor() : mRichText( true ), mResult( "" ) {}
01843 
01844     bool act( IncidenceBase *incidence, bool richText=true)
01845     {
01846       mRichText = richText;
01847       mResult = "";
01848       return incidence ? incidence->accept( *this ) : false;
01849     }
01850     QString result() const { return mResult; }
01851 
01852   protected:
01853     bool visit( Event *event );
01854     bool visit( Todo *todo );
01855     bool visit( Journal *journal );
01856     bool visit( FreeBusy *fb );
01857 
01858     QString dateRangeText( Event*event );
01859     QString dateRangeText( Todo *todo );
01860     QString dateRangeText( Journal *journal );
01861     QString dateRangeText( FreeBusy *fb );
01862 
01863     QString generateToolTip( Incidence* incidence, QString dtRangeText );
01864 
01865   protected:
01866     bool mRichText;
01867     QString mResult;
01868 };
01869 
01870 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event*event )
01871 {
01872   QString ret;
01873   QString tmp;
01874   if ( event->isMultiDay() ) {
01875 
01876     tmp = "<br>" + i18n("Event start", "<i>From:</i>&nbsp;%1");
01877     if (event->doesFloat())
01878       ret += tmp.arg( event->dtStartDateStr().replace(" ", "&nbsp;") );
01879     else
01880       ret += tmp.arg( event->dtStartStr().replace(" ", "&nbsp;") );
01881 
01882     tmp = "<br>" + i18n("Event end","<i>To:</i>&nbsp;%1");
01883     if (event->doesFloat())
01884       ret += tmp.arg( event->dtEndDateStr().replace(" ", "&nbsp;") );
01885     else
01886       ret += tmp.arg( event->dtEndStr().replace(" ", "&nbsp;") );
01887 
01888   } else {
01889 
01890     ret += "<br>"+i18n("<i>Date:</i>&nbsp;%1").
01891         arg( event->dtStartDateStr().replace(" ", "&nbsp;") );
01892     if ( !event->doesFloat() ) {
01893       const QString dtStartTime = event->dtStartTimeStr().replace( " ", "&nbsp;" );
01894       const QString dtEndTime = event->dtEndTimeStr().replace( " ", "&nbsp;" );
01895       if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00'
01896         tmp = "<br>" + i18n("time for event, &nbsp; to prevent ugly line breaks",
01897         "<i>Time:</i>&nbsp;%1").
01898         arg( dtStartTime );
01899       } else {
01900         tmp = "<br>" + i18n("time range for event, &nbsp; to prevent ugly line breaks",
01901         "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2").
01902         arg( dtStartTime, dtEndTime );
01903       }
01904       ret += tmp;
01905     }
01906 
01907   }
01908   return ret;
01909 }
01910 
01911 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo*todo )
01912 {
01913   QString ret;
01914   bool floats( todo->doesFloat() );
01915   if (todo->hasStartDate())
01916     // No need to add <i> here. This is separated issue and each line
01917     // is very visible on its own. On the other hand... Yes, I like it
01918     // italics here :)
01919     ret += "<br>" + i18n("<i>Start:</i>&nbsp;%1").arg(
01920       (floats)
01921         ?(todo->dtStartDateStr().replace(" ", "&nbsp;"))
01922         :(todo->dtStartStr().replace(" ", "&nbsp;")) ) ;
01923   if (todo->hasDueDate())
01924     ret += "<br>" + i18n("<i>Due:</i>&nbsp;%1").arg(
01925       (floats)
01926         ?(todo->dtDueDateStr().replace(" ", "&nbsp;"))
01927         :(todo->dtDueStr().replace(" ", "&nbsp;")) );
01928   if (todo->isCompleted())
01929     ret += "<br>" + i18n("<i>Completed:</i>&nbsp;%1").arg( todo->completedStr().replace(" ", "&nbsp;") );
01930   else
01931     ret += "<br>" + i18n("%1 % completed").arg(todo->percentComplete());
01932 
01933   return ret;
01934 }
01935 
01936 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal )
01937 {
01938   QString ret;
01939   if (journal->dtStart().isValid() ) {
01940     ret += "<br>" + i18n("<i>Date:</i>&nbsp;%1").arg( journal->dtStartDateStr( false ) );
01941   }
01942   return ret;
01943 }
01944 
01945 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
01946 {
01947   QString tmp( "<br>" + i18n("<i>Period start:</i>&nbsp;%1") );
01948   QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) );
01949   tmp = "<br>" + i18n("<i>Period start:</i>&nbsp;%1");
01950   ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) );
01951   return ret;
01952 }
01953 
01954 
01955 
01956 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
01957 {
01958   mResult = generateToolTip( event, dateRangeText( event ) );
01959   return !mResult.isEmpty();
01960 }
01961 
01962 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
01963 {
01964   mResult = generateToolTip( todo, dateRangeText( todo ) );
01965   return !mResult.isEmpty();
01966 }
01967 
01968 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
01969 {
01970   mResult = generateToolTip( journal, dateRangeText( journal ) );
01971   return !mResult.isEmpty();
01972 }
01973 
01974 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
01975 {
01976   mResult = "<qt><b>" + i18n("Free/Busy information for %1")
01977         .arg(fb->organizer().fullName()) + "</b>";
01978   mResult += dateRangeText( fb );
01979   mResult += "</qt>";
01980   return !mResult.isEmpty();
01981 }
01982 
01983 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence* incidence, QString dtRangeText )
01984 {
01985   if ( !incidence )
01986     return QString::null;
01987 
01988   QString tmp = "<qt><b>"+ incidence->summary().replace("\n", "<br>")+"</b>";
01989 
01990   tmp += dtRangeText;
01991 
01992   if (!incidence->location().isEmpty()) {
01993     // Put Location: in italics
01994     tmp += "<br>"+i18n("<i>Location:</i>&nbsp;%1").
01995       arg( incidence->location().replace("\n", "<br>") );
01996   }
01997   if (!incidence->description().isEmpty()) {
01998     QString desc(incidence->description());
01999     if (desc.length()>120) {
02000       desc = desc.left(120) + "...";
02001     }
02002     tmp += "<br>----------<br>" + i18n("<i>Description:</i><br>") + desc.replace("\n", "<br>");
02003   }
02004   tmp += "</qt>";
02005   return tmp;
02006 }
02007 
02008 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
02009 {
02010   ToolTipVisitor v;
02011   if ( v.act( incidence, richText ) ) {
02012     return v.result();
02013   } else
02014     return QString::null;
02015 }
02016 
02017 
02018 
02019 
02020 /*******************************************************************
02021  *  Helper functions for the Incidence tooltips
02022  *******************************************************************/
02023 
02024 class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
02025 {
02026   public:
02027     MailBodyVisitor() : mResult( "" ) {}
02028 
02029     bool act( IncidenceBase *incidence )
02030     {
02031       mResult = "";
02032       return incidence ? incidence->accept( *this ) : false;
02033     }
02034     QString result() const { return mResult; }
02035 
02036   protected:
02037     bool visit( Event *event );
02038     bool visit( Todo *todo );
02039     bool visit( Journal *journal );
02040     bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); }
02041   protected:
02042     QString mResult;
02043 };
02044 
02045 
02046 static QString mailBodyIncidence( Incidence *incidence )
02047 {
02048   QString body;
02049   if ( !incidence->summary().isEmpty() ) {
02050     body += i18n("Summary: %1\n").arg( incidence->summary() );
02051   }
02052   if ( !incidence->organizer().isEmpty() ) {
02053     body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() );
02054   }
02055   if ( !incidence->location().isEmpty() ) {
02056     body += i18n("Location: %1\n").arg( incidence->location() );
02057   }
02058   return body;
02059 }
02060 
02061 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
02062 {
02063   QString recurrence[]= {i18n("no recurrence", "None"),
02064     i18n("Minutely"), i18n("Hourly"), i18n("Daily"),
02065     i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"),
02066     i18n("Yearly"), i18n("Yearly"), i18n("Yearly")};
02067 
02068   mResult = mailBodyIncidence( event );
02069   mResult += i18n("Start Date: %1\n").arg( event->dtStartDateStr() );
02070   if ( !event->doesFloat() ) {
02071     mResult += i18n("Start Time: %1\n").arg( event->dtStartTimeStr() );
02072   }
02073   if ( event->dtStart() != event->dtEnd() ) {
02074     mResult += i18n("End Date: %1\n").arg( event->dtEndDateStr() );
02075   }
02076   if ( !event->doesFloat() ) {
02077     mResult += i18n("End Time: %1\n").arg( event->dtEndTimeStr() );
02078   }
02079   if ( event->doesRecur() ) {
02080     Recurrence *recur = event->recurrence();
02081     // TODO: Merge these two to one of the form "Recurs every 3 days"
02082     mResult += i18n("Recurs: %1\n")
02083              .arg( recurrence[ recur->recurrenceType() ] );
02084     mResult += i18n("Frequency: %1\n")
02085              .arg( event->recurrence()->frequency() );
02086 
02087     if ( recur->duration() > 0 ) {
02088       mResult += i18n ("Repeats once", "Repeats %n times", recur->duration());
02089       mResult += '\n';
02090     } else {
02091       if ( recur->duration() != -1 ) {
02092 // TODO_Recurrence: What to do with floating
02093         QString endstr;
02094         if ( event->doesFloat() ) {
02095           endstr = KGlobal::locale()->formatDate( recur->endDate() );
02096         } else {
02097           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() );
02098         }
02099         mResult += i18n("Repeat until: %1\n").arg( endstr );
02100       } else {
02101         mResult += i18n("Repeats forever\n");
02102       }
02103     }
02104   }
02105   QString details = event->description();
02106   if ( !details.isEmpty() ) {
02107     mResult += i18n("Details:\n%1\n").arg( details );
02108   }
02109   return !mResult.isEmpty();
02110 }
02111 
02112 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
02113 {
02114   mResult = mailBodyIncidence( todo );
02115 
02116   if ( todo->hasStartDate() ) {
02117     mResult += i18n("Start Date: %1\n").arg( todo->dtStartDateStr() );
02118     if ( !todo->doesFloat() ) {
02119       mResult += i18n("Start Time: %1\n").arg( todo->dtStartTimeStr() );
02120     }
02121   }
02122   if ( todo->hasDueDate() ) {
02123     mResult += i18n("Due Date: %1\n").arg( todo->dtDueDateStr() );
02124     if ( !todo->doesFloat() ) {
02125       mResult += i18n("Due Time: %1\n").arg( todo->dtDueTimeStr() );
02126     }
02127   }
02128   QString details = todo->description();
02129   if ( !details.isEmpty() ) {
02130     mResult += i18n("Details:\n%1\n").arg( details );
02131   }
02132   return !mResult.isEmpty();
02133 }
02134 
02135 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
02136 {
02137   mResult = mailBodyIncidence( journal );
02138   mResult += i18n("Date: %1\n").arg( journal->dtStartDateStr() );
02139   if ( !journal->doesFloat() ) {
02140     mResult += i18n("Time: %1\n").arg( journal->dtStartTimeStr() );
02141   }
02142   if ( !journal->description().isEmpty() )
02143     mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() );
02144   return !mResult.isEmpty();
02145 }
02146 
02147 
02148 
02149 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
02150 {
02151   if ( !incidence )
02152     return QString::null;
02153 
02154   MailBodyVisitor v;
02155   if ( v.act( incidence ) ) {
02156     return v.result();
02157   }
02158   return QString::null;
02159 }
02160 
02161 static QString recurEnd( Incidence *incidence )
02162 {
02163   QString endstr;
02164   if ( incidence->doesFloat() ) {
02165     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
02166   } else {
02167     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
02168   }
02169   return endstr;
02170 }
02171 
02172 QString IncidenceFormatter::recurrenceString(Incidence * incidence)
02173 {
02174   if ( !incidence->doesRecur() )
02175     return i18n( "No recurrence" );
02176 
02177   Recurrence *recur = incidence->recurrence();
02178   switch ( recur->recurrenceType() ) {
02179     case Recurrence::rNone:
02180       return i18n( "No recurrence" );
02181     case Recurrence::rMinutely:
02182       if ( recur->duration() != -1 )
02183         return i18n( "Recurs every minute until %1", "Recurs every %n minutes until %1", recur->frequency() )
02184             .arg( recurEnd( incidence ) );
02185       return i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() );
02186     case Recurrence::rHourly:
02187       if ( recur->duration() != -1 )
02188         return i18n( "Recurs hourly until %1", "Recurs every %n hours until %1", recur->frequency() )
02189             .arg( recurEnd( incidence ) );
02190       return i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() );
02191     case Recurrence::rDaily:
02192       if ( recur->duration() != -1 )
02193         return i18n( "Recurs daily until %1", "Recurs every %n days until %1", recur->frequency() )
02194             .arg( recurEnd( incidence ) );
02195       return i18n( "Recurs daily", "Recurs every %n days", recur->frequency() );
02196     case Recurrence::rWeekly:
02197       if ( recur->duration() != -1 )
02198         return i18n( "Recurs weekly until %1", "Recurs every %n weeks until %1", recur->frequency() )
02199             .arg( recurEnd( incidence ) );
02200       return i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() );
02201     case Recurrence::rMonthlyPos:
02202     case Recurrence::rMonthlyDay:
02203       if ( recur->duration() != -1 )
02204         return i18n( "Recurs monthly until %1" ).arg( recurEnd( incidence ) );
02205       return i18n( "Recurs monthly" );
02206     case Recurrence::rYearlyMonth:
02207     case Recurrence::rYearlyDay:
02208     case Recurrence::rYearlyPos:
02209       if ( recur->duration() != -1 )
02210         return i18n( "Recurs yearly until %1" ).arg( recurEnd( incidence ) );
02211       return i18n( "Recurs yearly" );
02212     default:
02213       return i18n( "Incidence recurs" );
02214   }
02215 }