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 <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   if ( event->doesRecur() )
00704     html += invitationRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( event ) );
00705 
00706   html += "</table>\n";
00707   html += invitationsDetailsIncidence( event );
00708   html += "</div>\n";
00709 
00710   return html;
00711 }
00712 
00713 static QString invitationDetailsTodo( Todo *todo )
00714 {
00715   // Task details are formatted into an HTML table
00716   if ( !todo )
00717     return QString::null;
00718 
00719   QString sSummary = i18n( "Summary unspecified" );
00720   QString sDescr = i18n( "Description unspecified" );
00721   if ( ! todo->summary().isEmpty() ) {
00722     sSummary = todo->summary();
00723   }
00724   if ( ! todo->description().isEmpty() ) {
00725     sDescr = todo->description();
00726   }
00727   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
00728   html += invitationRow( i18n( "Summary:" ), sSummary );
00729   html += invitationRow( i18n( "Description:" ), sDescr );
00730   html += "</table>\n";
00731   html += invitationsDetailsIncidence( todo );
00732 
00733   return html;
00734 }
00735 
00736 static QString invitationDetailsJournal( Journal *journal )
00737 {
00738   if ( !journal )
00739     return QString::null;
00740 
00741   QString sSummary = i18n( "Summary unspecified" );
00742   QString sDescr = i18n( "Description unspecified" );
00743   if ( ! journal->summary().isEmpty() ) {
00744     sSummary = journal->summary();
00745   }
00746   if ( ! journal->description().isEmpty() ) {
00747     sDescr = journal->description();
00748   }
00749   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
00750   html += invitationRow( i18n( "Summary:" ), sSummary );
00751   html += invitationRow( i18n( "Date:" ), journal->dtStartDateStr( false ) );
00752   html += invitationRow( i18n( "Description:" ), sDescr );
00753   html += "</table>\n";
00754   html += invitationsDetailsIncidence( journal );
00755 
00756   return html;
00757 }
00758 
00759 static QString invitationDetailsFreeBusy( FreeBusy *fb )
00760 {
00761   if ( !fb )
00762     return QString::null;
00763   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
00764 
00765   html += invitationRow( i18n("Person:"), fb->organizer().fullName() );
00766   html += invitationRow( i18n("Start date:"), fb->dtStartDateStr() );
00767   html += invitationRow( i18n("End date:"),
00768       KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) );
00769   html += "<tr><td colspan=2><hr></td></tr>\n";
00770   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
00771 
00772   QValueList<Period> periods = fb->busyPeriods();
00773 
00774   QValueList<Period>::iterator it;
00775   for ( it = periods.begin(); it != periods.end(); ++it ) {
00776     Period per = *it;
00777     if ( per.hasDuration() ) {
00778       int dur = per.duration().asSeconds();
00779       QString cont;
00780       if ( dur >= 3600 ) {
00781         cont += i18n("1 hour ", "%n hours ", dur / 3600);
00782         dur %= 3600;
00783       }
00784       if ( dur >= 60 ) {
00785         cont += i18n("1 minute", "%n minutes ", dur / 60);
00786         dur %= 60;
00787       }
00788       if ( dur > 0 ) {
00789         cont += i18n("1 second", "%n seconds", dur);
00790       }
00791       html += invitationRow( QString::null, i18n("startDate for duration", "%1 for %2")
00792           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
00793           .arg(cont) );
00794     } else {
00795       QString cont;
00796       if ( per.start().date() == per.end().date() ) {
00797         cont = i18n("date, fromTime - toTime ", "%1, %2 - %3")
00798             .arg( KGlobal::locale()->formatDate( per.start().date() ) )
00799             .arg( KGlobal::locale()->formatTime( per.start().time() ) )
00800             .arg( KGlobal::locale()->formatTime( per.end().time() ) );
00801       } else {
00802         cont = i18n("fromDateTime - toDateTime", "%1 - %2")
00803           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
00804           .arg( KGlobal::locale()->formatDateTime( per.end(), false ) );
00805       }
00806 
00807       html += invitationRow( QString::null, cont );
00808     }
00809   }
00810 
00811   html += "</table>\n";
00812   return html;
00813 }
00814 
00815 static QString invitationHeaderEvent( Event *event, ScheduleMessage *msg )
00816 {
00817   if ( !msg || !event )
00818     return QString::null;
00819   switch ( msg->method() ) {
00820     case Scheduler::Publish:
00821         return i18n("This event has been published");
00822     case Scheduler::Request:
00823         if ( event->revision() > 0 )
00824             return i18n( "This meeting has been updated" );
00825         return i18n( "You have been invited to this meeting" );
00826     case Scheduler::Refresh:
00827         return i18n( "This invitation was refreshed" );
00828     case Scheduler::Cancel:
00829         return i18n( "This meeting has been canceled" );
00830     case Scheduler::Add:
00831         return i18n( "Addition to the meeting invitation" );
00832     case Scheduler::Reply: {
00833         Attendee::List attendees = event->attendees();
00834         if( attendees.count() == 0 ) {
00835           kdDebug(5850) << "No attendees in the iCal reply!\n";
00836           return QString::null;
00837         }
00838         if( attendees.count() != 1 )
00839           kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
00840                         << "but is " << attendees.count() << endl;
00841         Attendee* attendee = *attendees.begin();
00842         QString attendeeName = attendee->name();
00843         if ( attendeeName.isEmpty() )
00844           attendeeName = attendee->email();
00845         if ( attendeeName.isEmpty() )
00846           attendeeName = i18n( "Sender" );
00847 
00848         QString delegatorName, dummy;
00849         KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
00850         if ( delegatorName.isEmpty() )
00851           delegatorName = attendee->delegator();
00852 
00853         switch( attendee->status() ) {
00854           case Attendee::NeedsAction:
00855               return i18n( "%1 indicates this invitation still needs some action" ).arg( attendeeName );
00856           case Attendee::Accepted:
00857               if ( delegatorName.isEmpty() )
00858                   return i18n( "%1 accepts this meeting invitation" ).arg( attendeeName );
00859               return i18n( "%1 accepts this meeting invitation on behalf of %2" )
00860                   .arg( attendeeName ).arg( delegatorName );
00861           case Attendee::Tentative:
00862               if ( delegatorName.isEmpty() )
00863                   return i18n( "%1 tentatively accepts this meeting invitation" ).arg( attendeeName );
00864               return i18n( "%1 tentatively accepts this meeting invitation on behalf of %2" )
00865                   .arg( attendeeName ).arg( delegatorName );
00866           case Attendee::Declined:
00867               if ( delegatorName.isEmpty() )
00868                   return i18n( "%1 declines this meeting invitation" ).arg( attendeeName );
00869               return i18n( "%1 declines this meeting invitation on behalf of %2" )
00870                   .arg( attendeeName ).arg( delegatorName );
00871           case Attendee::Delegated: {
00872               QString delegate, dummy;
00873               KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
00874               if ( delegate.isEmpty() )
00875                   delegate = attendee->delegate();
00876               if ( !delegate.isEmpty() )
00877                 return i18n( "%1 has delegated this meeting invitation to %2" )
00878                     .arg( attendeeName ) .arg( delegate );
00879               return i18n( "%1 has delegated this meeting invitation" ).arg( attendeeName );
00880           }
00881           case Attendee::Completed:
00882               return i18n( "This meeting invitation is now completed" );
00883           case Attendee::InProcess:
00884               return i18n( "%1 is still processing the invitation" ).arg( attendeeName );
00885           default:
00886               return i18n( "Unknown response to this meeting invitation" );
00887         }
00888         break; }
00889     case Scheduler::Counter:
00890         return i18n( "Sender makes this counter proposal" );
00891     case Scheduler::Declinecounter:
00892         return i18n( "Sender declines the counter proposal" );
00893     case Scheduler::NoMethod:
00894         return i18n("Error: iMIP message with unknown method: '%1'")
00895             .arg( msg->method() );
00896   }
00897   return QString::null;
00898 }
00899 
00900 static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg )
00901 {
00902   if ( !msg || !todo )
00903     return QString::null;
00904   switch ( msg->method() ) {
00905     case Scheduler::Publish:
00906         return i18n("This task has been published");
00907     case Scheduler::Request:
00908         if ( todo->revision() > 0 )
00909             return i18n( "This task has been updated" );
00910         return i18n( "You have been assigned this task" );
00911     case Scheduler::Refresh:
00912         return i18n( "This task was refreshed" );
00913     case Scheduler::Cancel:
00914         return i18n( "This task was canceled" );
00915     case Scheduler::Add:
00916         return i18n( "Addition to the task" );
00917     case Scheduler::Reply: {
00918         Attendee::List attendees = todo->attendees();
00919         if( attendees.count() == 0 ) {
00920           kdDebug(5850) << "No attendees in the iCal reply!\n";
00921           return QString::null;
00922         }
00923         if( attendees.count() != 1 )
00924           kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
00925                         << "but is " << attendees.count() << endl;
00926         Attendee* attendee = *attendees.begin();
00927 
00928         switch( attendee->status() ) {
00929           case Attendee::NeedsAction:
00930               return i18n( "Sender indicates this task assignment still needs some action" );
00931           case Attendee::Accepted:
00932               return i18n( "Sender accepts this task" );
00933           case Attendee::Tentative:
00934               return i18n( "Sender tentatively accepts this task" );
00935           case Attendee::Declined:
00936               return i18n( "Sender declines this task" );
00937           case Attendee::Delegated: {
00938               QString delegate, dummy;
00939               KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
00940               if ( delegate.isEmpty() )
00941                 delegate = attendee->delegate();
00942               if ( !delegate.isEmpty() )
00943                 return i18n( "Sender has delegated this request for the task to %1" ).arg( delegate );
00944               return i18n( "Sender has delegated this request for the task " );
00945           }
00946           case Attendee::Completed:
00947               return i18n( "The request for this task is now completed" );
00948           case Attendee::InProcess:
00949               return i18n( "Sender is still processing the invitation" );
00950           default:
00951               return i18n( "Unknown response to this task" );
00952           }
00953         break; }
00954     case Scheduler::Counter:
00955         return i18n( "Sender makes this counter proposal" );
00956     case Scheduler::Declinecounter:
00957         return i18n( "Sender declines the counter proposal" );
00958     case Scheduler::NoMethod:
00959         return i18n("Error: iMIP message with unknown method: '%1'")
00960             .arg( msg->method() );
00961   }
00962   return QString::null;
00963 }
00964 
00965 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
00966 {
00967   // TODO: Several of the methods are not allowed for journals, so remove them.
00968   if ( !msg || !journal )
00969     return QString::null;
00970   switch ( msg->method() ) {
00971     case Scheduler::Publish:
00972         return i18n("This journal has been published");
00973     case Scheduler::Request:
00974         return i18n( "You have been assigned this journal" );
00975     case Scheduler::Refresh:
00976         return i18n( "This journal was refreshed" );
00977     case Scheduler::Cancel:
00978         return i18n( "This journal was canceled" );
00979     case Scheduler::Add:
00980         return i18n( "Addition to the journal" );
00981     case Scheduler::Reply: {
00982         Attendee::List attendees = journal->attendees();
00983         if( attendees.count() == 0 ) {
00984           kdDebug(5850) << "No attendees in the iCal reply!\n";
00985           return QString::null;
00986         }
00987         if( attendees.count() != 1 )
00988           kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
00989                         << "but is " << attendees.count() << endl;
00990         Attendee* attendee = *attendees.begin();
00991 
00992         switch( attendee->status() ) {
00993           case Attendee::NeedsAction:
00994               return i18n( "Sender indicates this journal assignment still needs some action" );
00995           case Attendee::Accepted:
00996               return i18n( "Sender accepts this journal" );
00997           case Attendee::Tentative:
00998               return i18n( "Sender tentatively accepts this journal" );
00999           case Attendee::Declined:
01000               return i18n( "Sender declines this journal" );
01001           case Attendee::Delegated:
01002               return i18n( "Sender has delegated this request for the journal" );
01003           case Attendee::Completed:
01004               return i18n( "The request for this journal is now completed" );
01005           case Attendee::InProcess:
01006               return i18n( "Sender is still processing the invitation" );
01007           default:
01008               return i18n( "Unknown response to this journal" );
01009           }
01010         break; }
01011     case Scheduler::Counter:
01012         return i18n( "Sender makes this counter proposal" );
01013     case Scheduler::Declinecounter:
01014         return i18n( "Sender declines the counter proposal" );
01015     case Scheduler::NoMethod:
01016         return i18n("Error: iMIP message with unknown method: '%1'")
01017             .arg( msg->method() );
01018   }
01019   return QString::null;
01020 }
01021 
01022 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
01023 {
01024   if ( !msg || !fb )
01025     return QString::null;
01026   switch ( msg->method() ) {
01027     case Scheduler::Publish:
01028         return i18n("This free/busy list has been published");
01029     case Scheduler::Request:
01030         return i18n( "The free/busy list has been requested" );
01031     case Scheduler::Refresh:
01032         return i18n( "This free/busy list was refreshed" );
01033     case Scheduler::Cancel:
01034         return i18n( "This free/busy list was canceled" );
01035     case Scheduler::Add:
01036         return i18n( "Addition to the free/busy list" );
01037     case Scheduler::NoMethod:
01038     default:
01039         return i18n("Error: Free/Busy iMIP message with unknown method: '%1'")
01040             .arg( msg->method() );
01041   }
01042 }
01043 
01044 class IncidenceFormatter::ScheduleMessageVisitor : public IncidenceBase::Visitor
01045 {
01046   public:
01047     ScheduleMessageVisitor() : mMessage(0) { mResult = ""; }
01048     bool act( IncidenceBase *incidence, ScheduleMessage *msg ) { mMessage = msg; return incidence->accept( *this ); }
01049     QString result() const { return mResult; }
01050 
01051   protected:
01052     QString mResult;
01053     ScheduleMessage *mMessage;
01054 };
01055 
01056 class IncidenceFormatter::InvitationHeaderVisitor :
01057       public IncidenceFormatter::ScheduleMessageVisitor
01058 {
01059   protected:
01060     bool visit( Event *event )
01061     {
01062       mResult = invitationHeaderEvent( event, mMessage );
01063       return !mResult.isEmpty();
01064     }
01065     bool visit( Todo *todo )
01066     {
01067       mResult = invitationHeaderTodo( todo, mMessage );
01068       return !mResult.isEmpty();
01069     }
01070     bool visit( Journal *journal )
01071     {
01072       mResult = invitationHeaderJournal( journal, mMessage );
01073       return !mResult.isEmpty();
01074     }
01075     bool visit( FreeBusy *fb )
01076     {
01077       mResult = invitationHeaderFreeBusy( fb, mMessage );
01078       return !mResult.isEmpty();
01079     }
01080 };
01081 
01082 class IncidenceFormatter::InvitationBodyVisitor :
01083       public IncidenceFormatter::ScheduleMessageVisitor
01084 {
01085   protected:
01086     bool visit( Event *event )
01087     {
01088       mResult = invitationDetailsEvent( event );
01089       return !mResult.isEmpty();
01090     }
01091     bool visit( Todo *todo )
01092     {
01093       mResult = invitationDetailsTodo( todo );
01094       return !mResult.isEmpty();
01095     }
01096     bool visit( Journal *journal )
01097     {
01098       mResult = invitationDetailsJournal( journal );
01099       return !mResult.isEmpty();
01100     }
01101     bool visit( FreeBusy *fb )
01102     {
01103       mResult = invitationDetailsFreeBusy( fb );
01104       return !mResult.isEmpty();
01105     }
01106 };
01107 
01108 class IncidenceFormatter::IncidenceCompareVisitor :
01109   public IncidenceBase::Visitor
01110 {
01111   public:
01112     IncidenceCompareVisitor() : mExistingIncidence(0) {}
01113     bool act( IncidenceBase *incidence, Incidence* existingIncidence )
01114     {
01115       Incidence *inc = dynamic_cast<Incidence*>( incidence );
01116       if ( !inc || !existingIncidence || inc->revision() <= existingIncidence->revision() )
01117         return false;
01118       mExistingIncidence = existingIncidence;
01119       return incidence->accept( *this );
01120     }
01121 
01122     QString result() const
01123     {
01124       if ( mChanges.isEmpty() )
01125         return QString();
01126       QString html = "<div align=\"left\"><ul><li>";
01127       html += mChanges.join( "</li><li>" );
01128       html += "</li><ul></div>";
01129       return html;
01130     }
01131 
01132   protected:
01133     bool visit( Event *event )
01134     {
01135       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
01136       compareIncidences( event, mExistingIncidence );
01137       return !mChanges.isEmpty();
01138     }
01139     bool visit( Todo *todo )
01140     {
01141       compareIncidences( todo, mExistingIncidence );
01142       return !mChanges.isEmpty();
01143     }
01144     bool visit( Journal *journal )
01145     {
01146       compareIncidences( journal, mExistingIncidence );
01147       return !mChanges.isEmpty();
01148     }
01149     bool visit( FreeBusy *fb )
01150     {
01151       Q_UNUSED( fb );
01152       return !mChanges.isEmpty();
01153     }
01154 
01155   private:
01156     void compareEvents( Event *newEvent, Event *oldEvent )
01157     {
01158       if ( !oldEvent || !newEvent )
01159         return;
01160       if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->doesFloat() != newEvent->doesFloat() )
01161         mChanges += i18n( "The begin of the meeting has been changed from %1 to %2" )
01162             .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) );
01163       if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->doesFloat() != newEvent->doesFloat() )
01164         mChanges += i18n( "The end of the meeting has been changed from %1 to %2" )
01165             .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) );
01166     }
01167 
01168     void compareIncidences( Incidence *newInc, Incidence *oldInc )
01169     {
01170       if ( !oldInc || !newInc )
01171         return;
01172       if ( oldInc->summary() != newInc->summary() )
01173         mChanges += i18n( "The summary has been changed to: \"%1\"" ).arg( newInc->summary() );
01174       if ( oldInc->location() != newInc->location() )
01175         mChanges += i18n( "The location has been changed to: \"%1\"" ).arg( newInc->location() );
01176       if ( oldInc->description() != newInc->description() )
01177         mChanges += i18n( "The description has been changed to: \"%1\"" ).arg( newInc->description() );
01178       Attendee::List oldAttendees = oldInc->attendees();
01179       Attendee::List newAttendees = newInc->attendees();
01180       for ( Attendee::List::ConstIterator it = newAttendees.constBegin(); it != newAttendees.constEnd(); ++it ) {
01181         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
01182         if ( !oldAtt ) {
01183           mChanges += i18n( "Attendee %1 has been added" ).arg( (*it)->fullName() );
01184         } else {
01185           if ( oldAtt->status() != (*it)->status() )
01186             mChanges += i18n( "The status of attendee %1 has been changed to: %2" ).arg( (*it)->fullName() )
01187                 .arg( (*it)->statusStr() );
01188         }
01189       }
01190       for ( Attendee::List::ConstIterator it = oldAttendees.constBegin(); it != oldAttendees.constEnd(); ++it ) {
01191         Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
01192         if ( !newAtt )
01193           mChanges += i18n( "Attendee %1 has been removed" ).arg( (*it)->fullName() );
01194       }
01195     }
01196 
01197   private:
01198     Incidence* mExistingIncidence;
01199     QStringList mChanges;
01200 };
01201 
01202 
01203 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
01204 {
01205   QString res( "<a href=\"%1\"><b>%2</b></a>" );
01206   return res.arg( generateLinkURL( id ) ).arg( text );
01207   return res;
01208 }
01209 
01210 // Check if the given incidence is likely one that we own instead one from
01211 // a shared calendar (Kolab-specific)
01212 static bool incidenceOwnedByMe( Calendar* calendar, Incidence *incidence )
01213 {
01214   CalendarResources* cal = dynamic_cast<CalendarResources*>( calendar );
01215   if ( !cal || !incidence )
01216     return true;
01217   ResourceCalendar* res = cal->resource( incidence );
01218   if ( !res )
01219     return true;
01220   const QString subRes = res->subresourceIdentifier( incidence );
01221   if ( !subRes.contains( "/.INBOX.directory/" ) )
01222     return false;
01223   return true;
01224 }
01225 
01226 QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar,
01227     InvitationFormatterHelper *helper )
01228 {
01229   if ( invitation.isEmpty() ) return QString::null;
01230 
01231   ICalFormat format;
01232   // parseScheduleMessage takes the tz from the calendar, no need to set it manually here for the format!
01233   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
01234 
01235   if( !msg ) {
01236     kdDebug( 5850 ) << "Failed to parse the scheduling message" << endl;
01237     Q_ASSERT( format.exception() );
01238     kdDebug( 5850 ) << format.exception()->message() << endl;
01239     return QString::null;
01240   }
01241 
01242   IncidenceBase *incBase = msg->event();
01243 
01244   Incidence* existingIncidence = 0;
01245   if ( helper->calendar() ) {
01246     existingIncidence = helper->calendar()->incidence( incBase->uid() );
01247     if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) )
01248       existingIncidence = 0;
01249     if ( !existingIncidence ) {
01250       const Incidence::List list = helper->calendar()->incidences();
01251       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
01252         if ( (*it)->schedulingID() == incBase->uid() && incidenceOwnedByMe( helper->calendar(), *it ) ) {
01253           existingIncidence = *it;
01254           break;
01255         }
01256       }
01257     }
01258   }
01259 
01260   // First make the text of the message
01261   QString html;
01262 
01263   QString tableStyle = QString::fromLatin1(
01264     "style=\"border: solid 1px; margin: 0em;\"" );
01265   QString tableHead = QString::fromLatin1(
01266     "<div align=\"center\">"
01267     "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
01268     "<tr><td>").arg(tableStyle);
01269 
01270   html += tableHead;
01271   InvitationHeaderVisitor headerVisitor;
01272   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
01273   if ( !headerVisitor.act( incBase, msg ) )
01274     return QString::null;
01275   html += "<b>" + headerVisitor.result() + "</b>";
01276 
01277   InvitationBodyVisitor bodyVisitor;
01278   if ( !bodyVisitor.act( incBase, msg ) )
01279     return QString::null;
01280   html += bodyVisitor.result();
01281 
01282   if ( msg->method() == Scheduler::Request ) { // ### Scheduler::Publish/Refresh/Add as well?
01283     IncidenceCompareVisitor compareVisitor;
01284     if ( compareVisitor.act( incBase, existingIncidence ) ) {
01285       html += i18n("<p align=\"left\">The following changes have been made by the organizer:</p>");
01286       html += compareVisitor.result();
01287     }
01288   }
01289 
01290   html += "<br/>";
01291   html += "<table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td></tr><tr>";
01292 
01293 #if 0
01294   html += helper->makeLinkURL( "accept", i18n("[Enter this into my calendar]") );
01295   html += "</td><td> &nbsp; </td><td>";
01296 #endif
01297 
01298   // Add groupware links
01299 
01300   switch ( msg->method() ) {
01301     case Scheduler::Publish:
01302     case Scheduler::Request:
01303     case Scheduler::Refresh:
01304     case Scheduler::Add:
01305     {
01306         Incidence *inc = dynamic_cast<Incidence*>( incBase );
01307         if ( inc && inc->revision() > 0 && (existingIncidence || !helper->calendar()) ) {
01308             if ( incBase->type() == "Todo" ) {
01309                 html += "<td colspan=\"9\">";
01310                 html += helper->makeLink( "reply", i18n( "[Enter this into my task list]" ) );
01311             } else {
01312                 html += "<td colspan=\"13\">";
01313                 html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01314             }
01315             html += "</td></tr><tr>";
01316         }
01317         html += "<td>";
01318 
01319         if ( !existingIncidence ) {
01320           // Accept
01321           html += helper->makeLink( "accept", i18n( "[Accept]" ) );
01322           html += "</td><td> &nbsp; </td><td>";
01323           html += helper->makeLink( "accept_conditionally",
01324                             i18n( "Accept conditionally", "[Accept cond.]" ) );
01325           html += "</td><td> &nbsp; </td><td>";
01326           // counter proposal
01327           html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
01328           html += "</td><td> &nbsp; </td><td>";
01329           // Decline
01330           html += helper->makeLink( "decline", i18n( "[Decline]" ) );
01331           html += "</td><td> &nbsp; </td><td>";
01332 
01333           // Delegate
01334           html += helper->makeLink( "delegate", i18n( "[Delegate]" ) );
01335           html += "</td><td> &nbsp; </td><td>";
01336 
01337           // Forward
01338           html += helper->makeLink( "forward", i18n( "[Forward]" ) );
01339 
01340           if ( incBase->type() == "Event" ) {
01341               html += "</b></a></td><td> &nbsp; </td><td>";
01342               html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
01343           }
01344         }
01345         break;
01346     }
01347 
01348     case Scheduler::Cancel:
01349         // Cancel event from my calendar
01350         html += helper->makeLink( "cancel", i18n( "[Remove this from my calendar]" ) );
01351         break;
01352 
01353     case Scheduler::Reply:
01354         // Enter this into my calendar
01355         if ( incBase->type() == "Todo" ) {
01356           html += helper->makeLink( "reply", i18n( "[Enter this into my task list]" ) );
01357         } else {
01358           html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01359         }
01360         break;
01361 
01362     case Scheduler::Counter:
01363         html += helper->makeLink( "accept_counter", i18n("[Accept]") );
01364         html += "&nbsp;";
01365         html += helper->makeLink( "decline_counter", i18n("[Decline]") );
01366         html += "&nbsp;";
01367         html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
01368         break;
01369     case Scheduler::Declinecounter:
01370     case Scheduler::NoMethod:
01371         break;
01372   }
01373 
01374   html += "</td></tr></table>";
01375 
01376   html += "</td></tr></table><br></div>";
01377 
01378   return html;
01379 }
01380 
01381 
01382 
01383 
01384 /*******************************************************************
01385  *  Helper functions for the msTNEF -> VPart converter
01386  *******************************************************************/
01387 
01388 
01389 //-----------------------------------------------------------------------------
01390 
01391 static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
01392                            const QString& fallback = QString::null)
01393 {
01394   return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
01395                             fallback );
01396 }
01397 
01398 static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
01399                            const QString& fallback = QString::null )
01400 {
01401   return tnefMsg->findNamedProp( name, fallback );
01402 }
01403 
01404 struct save_tz { char* old_tz; char* tz_env_str; };
01405 
01406 /* temporarily go to a different timezone */
01407 static struct save_tz set_tz( const char* _tc )
01408 {
01409   const char *tc = _tc?_tc:"UTC";
01410 
01411   struct save_tz rv;
01412 
01413   rv.old_tz = 0;
01414   rv.tz_env_str = 0;
01415 
01416   //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;
01417 
01418   char* tz_env = 0;
01419   if( getenv( "TZ" ) ) {
01420     tz_env = strdup( getenv( "TZ" ) );
01421     rv.old_tz = tz_env;
01422   }
01423   char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
01424   strcpy( tmp_env, "TZ=" );
01425   strcpy( tmp_env+3, tc );
01426   putenv( tmp_env );
01427 
01428   rv.tz_env_str = tmp_env;
01429 
01430   /* tmp_env is not free'ed -- it is part of the environment */
01431 
01432   tzset();
01433   //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;
01434 
01435   return rv;
01436 }
01437 
01438 /* restore previous timezone */
01439 static void unset_tz( struct save_tz old_tz )
01440 {
01441   if( old_tz.old_tz ) {
01442     char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
01443     strcpy( tmp_env, "TZ=" );
01444     strcpy( tmp_env+3, old_tz.old_tz );
01445     putenv( tmp_env );
01446     /* tmp_env is not free'ed -- it is part of the environment */
01447     free( old_tz.old_tz );
01448   } else {
01449     /* clear TZ from env */
01450     putenv( strdup("TZ") );
01451   }
01452   tzset();
01453 
01454   /* is this OK? */
01455   if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
01456 }
01457 
01458 static QDateTime utc2Local( const QDateTime& utcdt )
01459 {
01460   struct tm tmL;
01461 
01462   save_tz tmp_tz = set_tz("UTC");
01463   time_t utc = utcdt.toTime_t();
01464   unset_tz( tmp_tz );
01465 
01466   localtime_r( &utc, &tmL );
01467   return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
01468                     QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
01469 }
01470 
01471 
01472 static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
01473                                           bool bDateOnly = false )
01474 {
01475   QDate tmpDate;
01476   QTime tmpTime;
01477   int year, month, day, hour, minute, second;
01478 
01479   if( bDateOnly ) {
01480     year = dtStr.left( 4 ).toInt();
01481     month = dtStr.mid( 4, 2 ).toInt();
01482     day = dtStr.mid( 6, 2 ).toInt();
01483     hour = 0;
01484     minute = 0;
01485     second = 0;
01486   } else {
01487     year = dtStr.left( 4 ).toInt();
01488     month = dtStr.mid( 4, 2 ).toInt();
01489     day = dtStr.mid( 6, 2 ).toInt();
01490     hour = dtStr.mid( 9, 2 ).toInt();
01491     minute = dtStr.mid( 11, 2 ).toInt();
01492     second = dtStr.mid( 13, 2 ).toInt();
01493   }
01494   tmpDate.setYMD( year, month, day );
01495   tmpTime.setHMS( hour, minute, second );
01496 
01497   if( tmpDate.isValid() && tmpTime.isValid() ) {
01498     QDateTime dT = QDateTime( tmpDate, tmpTime );
01499 
01500     if( !bDateOnly ) {
01501       // correct for GMT ( == Zulu time == UTC )
01502       if (dtStr.at(dtStr.length()-1) == 'Z') {
01503         //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
01504         //localUTCOffset( dT ) );
01505         dT = utc2Local( dT );
01506       }
01507     }
01508     return dT;
01509   } else
01510     return QDateTime();
01511 }
01512 
01513 
01514 
01515 QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef )
01516 {
01517   bool bOk = false;
01518 
01519   KTNEFParser parser;
01520   QBuffer buf( tnef );
01521   CalendarLocal cal ( QString::fromLatin1( "UTC" ) );
01522   KABC::Addressee addressee;
01523   KABC::VCardConverter cardConv;
01524   ICalFormat calFormat;
01525   Event* event = new Event();
01526 
01527   if( parser.openDevice( &buf ) ) {
01528     KTNEFMessage* tnefMsg = parser.message();
01529     //QMap<int,KTNEFProperty*> props = parser.message()->properties();
01530 
01531     // Everything depends from property PR_MESSAGE_CLASS
01532     // (this is added by KTNEFParser):
01533     QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
01534       .upper();
01535     if( !msgClass.isEmpty() ) {
01536       // Match the old class names that might be used by Outlook for
01537       // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
01538       bool bCompatClassAppointment = false;
01539       bool bCompatMethodRequest = false;
01540       bool bCompatMethodCancled = false;
01541       bool bCompatMethodAccepted = false;
01542       bool bCompatMethodAcceptedCond = false;
01543       bool bCompatMethodDeclined = false;
01544       if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
01545         bCompatClassAppointment = true;
01546         if( msgClass.endsWith( ".MTGREQ" ) )
01547           bCompatMethodRequest = true;
01548         if( msgClass.endsWith( ".MTGCNCL" ) )
01549           bCompatMethodCancled = true;
01550         if( msgClass.endsWith( ".MTGRESPP" ) )
01551           bCompatMethodAccepted = true;
01552         if( msgClass.endsWith( ".MTGRESPA" ) )
01553           bCompatMethodAcceptedCond = true;
01554         if( msgClass.endsWith( ".MTGRESPN" ) )
01555           bCompatMethodDeclined = true;
01556       }
01557       bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );
01558 
01559       if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
01560         // Compose a vCal
01561         bool bIsReply = false;
01562         QString prodID = "-//Microsoft Corporation//Outlook ";
01563         prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
01564         prodID += "MIMEDIR/EN\n";
01565         prodID += "VERSION:2.0\n";
01566         calFormat.setApplication( "Outlook", prodID );
01567 
01568         Scheduler::Method method;
01569         if( bCompatMethodRequest )
01570           method = Scheduler::Request;
01571         else if( bCompatMethodCancled )
01572           method = Scheduler::Cancel;
01573         else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
01574                  bCompatMethodDeclined ) {
01575           method = Scheduler::Reply;
01576           bIsReply = true;
01577         } else {
01578           // pending(khz): verify whether "0x0c17" is the right tag ???
01579           //
01580           // at the moment we think there are REQUESTS and UPDATES
01581           //
01582           // but WHAT ABOUT REPLIES ???
01583           //
01584           //
01585 
01586           if( tnefMsg->findProp(0x0c17) == "1" )
01587             bIsReply = true;
01588           method = Scheduler::Request;
01589         }
01590 
01592         ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );
01593 
01594         QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );
01595 
01596         if( !sSenderSearchKeyEmail.isEmpty() ) {
01597           int colon = sSenderSearchKeyEmail.find( ':' );
01598           // May be e.g. "SMTP:KHZ@KDE.ORG"
01599           if( sSenderSearchKeyEmail.find( ':' ) == -1 )
01600             sSenderSearchKeyEmail.remove( 0, colon+1 );
01601         }
01602 
01603         QString s( tnefMsg->findProp( 0x0e04 ) );
01604         QStringList attendees = QStringList::split( ';', s );
01605         if( attendees.count() ) {
01606           for( QStringList::Iterator it = attendees.begin();
01607                it != attendees.end(); ++it ) {
01608             // Skip all entries that have no '@' since these are
01609             // no mail addresses
01610             if( (*it).find('@') == -1 ) {
01611               s = (*it).stripWhiteSpace();
01612 
01613               Attendee *attendee = new Attendee( s, s, true );
01614               if( bIsReply ) {
01615                 if( bCompatMethodAccepted )
01616                   attendee->setStatus( Attendee::Accepted );
01617                 if( bCompatMethodDeclined )
01618                   attendee->setStatus( Attendee::Declined );
01619                 if( bCompatMethodAcceptedCond )
01620                   attendee->setStatus(Attendee::Tentative);
01621               } else {
01622                 attendee->setStatus( Attendee::NeedsAction );
01623                 attendee->setRole( Attendee::ReqParticipant );
01624               }
01625               event->addAttendee(attendee);
01626             }
01627           }
01628         } else {
01629           // Oops, no attendees?
01630           // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
01631           s = sSenderSearchKeyEmail;
01632           if( !s.isEmpty() ) {
01633             Attendee *attendee = new Attendee( QString::null, QString::null,
01634                                                true );
01635             if( bIsReply ) {
01636               if( bCompatMethodAccepted )
01637                 attendee->setStatus( Attendee::Accepted );
01638               if( bCompatMethodAcceptedCond )
01639                 attendee->setStatus( Attendee::Declined );
01640               if( bCompatMethodDeclined )
01641                 attendee->setStatus( Attendee::Tentative );
01642             } else {
01643               attendee->setStatus(Attendee::NeedsAction);
01644               attendee->setRole(Attendee::ReqParticipant);
01645             }
01646             event->addAttendee(attendee);
01647           }
01648         }
01649         s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
01650         if( s.isEmpty() && !bIsReply )
01651           s = sSenderSearchKeyEmail;
01652         // TODO: Use the common name?
01653         if( !s.isEmpty() )
01654           event->setOrganizer( s );
01655 
01656         s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
01657           .replace( QChar( ':' ), QString::null );
01658         event->setDtStart( QDateTime::fromString( s ) ); // ## Format??
01659 
01660         s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
01661           .replace( QChar( ':' ), QString::null );
01662         event->setDtEnd( QDateTime::fromString( s ) );
01663 
01664         s = tnefMsg->findProp( 0x8208 );
01665         event->setLocation( s );
01666 
01667         // is it OK to set this to OPAQUE always ??
01668         //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
01669         //vPart += "SEQUENCE:0\n";
01670 
01671         // is "0x0023" OK  -  or should we look for "0x0003" ??
01672         s = tnefMsg->findProp( 0x0023 );
01673         event->setUid( s );
01674 
01675         // PENDING(khz): is this value in local timezone? Must it be
01676         // adjusted? Most likely this is a bug in the server or in
01677         // Outlook - we ignore it for now.
01678         s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
01679           .replace( QChar( ':' ), QString::null );
01680         // ### libkcal always uses currentDateTime()
01681         // event->setDtStamp(QDateTime::fromString(s));
01682 
01683         s = tnefMsg->findNamedProp( "Keywords" );
01684         event->setCategories( s );
01685 
01686         s = tnefMsg->findProp( 0x1000 );
01687         event->setDescription( s );
01688 
01689         s = tnefMsg->findProp( 0x0070 );
01690         event->setSummary( s );
01691 
01692         s = tnefMsg->findProp( 0x0026 );
01693         event->setPriority( s.toInt() );
01694 
01695         // is reminder flag set ?
01696         if(!tnefMsg->findProp(0x8503).isEmpty()) {
01697           Alarm *alarm = new Alarm(event);
01698           QDateTime highNoonTime =
01699             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
01700                                      .replace( QChar( '-' ), "" )
01701                                      .replace( QChar( ':' ), "" ) );
01702           QDateTime wakeMeUpTime =
01703             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
01704                                      .replace( QChar( '-' ), "" )
01705                                      .replace( QChar( ':' ), "" ) );
01706           alarm->setTime(wakeMeUpTime);
01707 
01708           if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
01709             alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
01710           else
01711             // default: wake them up 15 minutes before the appointment
01712             alarm->setStartOffset( Duration( 15*60 ) );
01713           alarm->setDisplayAlarm( i18n( "Reminder" ) );
01714 
01715           // Sorry: the different action types are not known (yet)
01716           //        so we always set 'DISPLAY' (no sounds, no images...)
01717           event->addAlarm( alarm );
01718         }
01719         cal.addEvent( event );
01720         bOk = true;
01721         // we finished composing a vCal
01722       } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
01723         addressee.setUid( stringProp( tnefMsg, attMSGID ) );
01724         addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) );
01725         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
01726         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
01727         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
01728         addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) );
01729         addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) );
01730         addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
01731         addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
01732         addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) );
01733         addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) );
01734         addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) );
01735 
01736         QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
01737           .replace( QChar( '-' ), QString::null )
01738           .replace( QChar( ':' ), QString::null );
01739         if( !s.isEmpty() )
01740           addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );
01741 
01742         addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );
01743 
01744         // collect parts of Name entry
01745         addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
01746         addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
01747         addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) );
01748         addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) );
01749         addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );
01750 
01751         addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
01752         addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
01753         addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
01754         /*
01755         the MAPI property ID of this (multiline) )field is unknown:
01756         vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
01757         */
01758 
01759         KABC::Address adr;
01760         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) );
01761         adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
01762         adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
01763         adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
01764         adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) );
01765         adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
01766         adr.setType(KABC::Address::Home);
01767         addressee.insertAddress(adr);
01768 
01769         adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
01770         adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) );
01771         adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) );
01772         adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) );
01773         adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
01774         adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) );
01775         adr.setType( KABC::Address::Work );
01776         addressee.insertAddress( adr );
01777 
01778         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) );
01779         adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
01780         adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
01781         adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
01782         adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) );
01783         adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
01784         adr.setType( KABC::Address::Dom );
01785         addressee.insertAddress(adr);
01786 
01787         // problem: the 'other' address was stored by KOrganizer in
01788         //          a line looking like the following one:
01789         // 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
01790 
01791         QString nr;
01792         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
01793         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) );
01794         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
01795         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) );
01796         nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
01797         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) );
01798         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
01799         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) );
01800         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
01801         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) );
01802 
01803         s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
01804           .replace( QChar( '-' ), QString::null )
01805           .replace( QChar( ':' ), QString::null );
01806         if( !s.isEmpty() )
01807           addressee.setBirthday( QDateTime::fromString( s ) );
01808 
01809         bOk = ( !addressee.isEmpty() );
01810       } else if( "IPM.NOTE" == msgClass ) {
01811 
01812       } // else if ... and so on ...
01813     }
01814   }
01815 
01816   // Compose return string
01817   QString iCal = calFormat.toString( &cal );
01818   if( !iCal.isEmpty() )
01819     // This was an iCal
01820     return iCal;
01821 
01822   // Not an iCal - try a vCard
01823   KABC::VCardConverter converter;
01824   return converter.createVCard( addressee );
01825 }
01826 
01827 
01828 QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef,
01829         Calendar *mCalendar, InvitationFormatterHelper *helper )
01830 {
01831   QString vPart = IncidenceFormatter::msTNEFToVPart( tnef );
01832   QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper );
01833   if( !iCal.isEmpty() )
01834     return iCal;
01835   return vPart;
01836 }
01837 
01838 
01839 
01840 
01841 /*******************************************************************
01842  *  Helper functions for the Incidence tooltips
01843  *******************************************************************/
01844 
01845 class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
01846 {
01847   public:
01848     ToolTipVisitor() : mRichText( true ), mResult( "" ) {}
01849 
01850     bool act( IncidenceBase *incidence, bool richText=true)
01851     {
01852       mRichText = richText;
01853       mResult = "";
01854       return incidence ? incidence->accept( *this ) : false;
01855     }
01856     QString result() const { return mResult; }
01857 
01858   protected:
01859     bool visit( Event *event );
01860     bool visit( Todo *todo );
01861     bool visit( Journal *journal );
01862     bool visit( FreeBusy *fb );
01863 
01864     QString dateRangeText( Event*event );
01865     QString dateRangeText( Todo *todo );
01866     QString dateRangeText( Journal *journal );
01867     QString dateRangeText( FreeBusy *fb );
01868 
01869     QString generateToolTip( Incidence* incidence, QString dtRangeText );
01870 
01871   protected:
01872     bool mRichText;
01873     QString mResult;
01874 };
01875 
01876 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event*event )
01877 {
01878   QString ret;
01879   QString tmp;
01880   if ( event->isMultiDay() ) {
01881 
01882     tmp = "<br>" + i18n("Event start", "<i>From:</i>&nbsp;%1");
01883     if (event->doesFloat())
01884       ret += tmp.arg( event->dtStartDateStr().replace(" ", "&nbsp;") );
01885     else
01886       ret += tmp.arg( event->dtStartStr().replace(" ", "&nbsp;") );
01887 
01888     tmp = "<br>" + i18n("Event end","<i>To:</i>&nbsp;%1");
01889     if (event->doesFloat())
01890       ret += tmp.arg( event->dtEndDateStr().replace(" ", "&nbsp;") );
01891     else
01892       ret += tmp.arg( event->dtEndStr().replace(" ", "&nbsp;") );
01893 
01894   } else {
01895 
01896     ret += "<br>"+i18n("<i>Date:</i>&nbsp;%1").
01897         arg( event->dtStartDateStr().replace(" ", "&nbsp;") );
01898     if ( !event->doesFloat() ) {
01899       const QString dtStartTime = event->dtStartTimeStr().replace( " ", "&nbsp;" );
01900       const QString dtEndTime = event->dtEndTimeStr().replace( " ", "&nbsp;" );
01901       if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00'
01902         tmp = "<br>" + i18n("time for event, &nbsp; to prevent ugly line breaks",
01903         "<i>Time:</i>&nbsp;%1").
01904         arg( dtStartTime );
01905       } else {
01906         tmp = "<br>" + i18n("time range for event, &nbsp; to prevent ugly line breaks",
01907         "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2").
01908         arg( dtStartTime, dtEndTime );
01909       }
01910       ret += tmp;
01911     }
01912 
01913   }
01914   return ret;
01915 }
01916 
01917 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo*todo )
01918 {
01919   QString ret;
01920   bool floats( todo->doesFloat() );
01921   if (todo->hasStartDate())
01922     // No need to add <i> here. This is separated issue and each line
01923     // is very visible on its own. On the other hand... Yes, I like it
01924     // italics here :)
01925     ret += "<br>" + i18n("<i>Start:</i>&nbsp;%1").arg(
01926       (floats)
01927         ?(todo->dtStartDateStr().replace(" ", "&nbsp;"))
01928         :(todo->dtStartStr().replace(" ", "&nbsp;")) ) ;
01929   if (todo->hasDueDate())
01930     ret += "<br>" + i18n("<i>Due:</i>&nbsp;%1").arg(
01931       (floats)
01932         ?(todo->dtDueDateStr().replace(" ", "&nbsp;"))
01933         :(todo->dtDueStr().replace(" ", "&nbsp;")) );
01934   if (todo->isCompleted())
01935     ret += "<br>" + i18n("<i>Completed:</i>&nbsp;%1").arg( todo->completedStr().replace(" ", "&nbsp;") );
01936   else
01937     ret += "<br>" + i18n("%1 % completed").arg(todo->percentComplete());
01938 
01939   return ret;
01940 }
01941 
01942 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal )
01943 {
01944   QString ret;
01945   if (journal->dtStart().isValid() ) {
01946     ret += "<br>" + i18n("<i>Date:</i>&nbsp;%1").arg( journal->dtStartDateStr( false ) );
01947   }
01948   return ret;
01949 }
01950 
01951 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
01952 {
01953   QString tmp( "<br>" + i18n("<i>Period start:</i>&nbsp;%1") );
01954   QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) );
01955   tmp = "<br>" + i18n("<i>Period start:</i>&nbsp;%1");
01956   ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) );
01957   return ret;
01958 }
01959 
01960 
01961 
01962 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
01963 {
01964   mResult = generateToolTip( event, dateRangeText( event ) );
01965   return !mResult.isEmpty();
01966 }
01967 
01968 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
01969 {
01970   mResult = generateToolTip( todo, dateRangeText( todo ) );
01971   return !mResult.isEmpty();
01972 }
01973 
01974 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
01975 {
01976   mResult = generateToolTip( journal, dateRangeText( journal ) );
01977   return !mResult.isEmpty();
01978 }
01979 
01980 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
01981 {
01982   mResult = "<qt><b>" + i18n("Free/Busy information for %1")
01983         .arg(fb->organizer().fullName()) + "</b>";
01984   mResult += dateRangeText( fb );
01985   mResult += "</qt>";
01986   return !mResult.isEmpty();
01987 }
01988 
01989 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence* incidence, QString dtRangeText )
01990 {
01991   if ( !incidence )
01992     return QString::null;
01993 
01994   QString tmp = "<qt><b>"+ incidence->summary().replace("\n", "<br>")+"</b>";
01995 
01996   tmp += dtRangeText;
01997 
01998   if (!incidence->location().isEmpty()) {
01999     // Put Location: in italics
02000     tmp += "<br>"+i18n("<i>Location:</i>&nbsp;%1").
02001       arg( incidence->location().replace("\n", "<br>") );
02002   }
02003   if (!incidence->description().isEmpty()) {
02004     QString desc(incidence->description());
02005     if (desc.length()>120) {
02006       desc = desc.left(120) + "...";
02007     }
02008     tmp += "<br>----------<br>" + i18n("<i>Description:</i><br>") + desc.replace("\n", "<br>");
02009   }
02010   tmp += "</qt>";
02011   return tmp;
02012 }
02013 
02014 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
02015 {
02016   ToolTipVisitor v;
02017   if ( v.act( incidence, richText ) ) {
02018     return v.result();
02019   } else
02020     return QString::null;
02021 }
02022 
02023 
02024 
02025 
02026 /*******************************************************************
02027  *  Helper functions for the Incidence tooltips
02028  *******************************************************************/
02029 
02030 class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
02031 {
02032   public:
02033     MailBodyVisitor() : mResult( "" ) {}
02034 
02035     bool act( IncidenceBase *incidence )
02036     {
02037       mResult = "";
02038       return incidence ? incidence->accept( *this ) : false;
02039     }
02040     QString result() const { return mResult; }
02041 
02042   protected:
02043     bool visit( Event *event );
02044     bool visit( Todo *todo );
02045     bool visit( Journal *journal );
02046     bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); }
02047   protected:
02048     QString mResult;
02049 };
02050 
02051 
02052 static QString mailBodyIncidence( Incidence *incidence )
02053 {
02054   QString body;
02055   if ( !incidence->summary().isEmpty() ) {
02056     body += i18n("Summary: %1\n").arg( incidence->summary() );
02057   }
02058   if ( !incidence->organizer().isEmpty() ) {
02059     body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() );
02060   }
02061   if ( !incidence->location().isEmpty() ) {
02062     body += i18n("Location: %1\n").arg( incidence->location() );
02063   }
02064   return body;
02065 }
02066 
02067 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
02068 {
02069   QString recurrence[]= {i18n("no recurrence", "None"),
02070     i18n("Minutely"), i18n("Hourly"), i18n("Daily"),
02071     i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"),
02072     i18n("Yearly"), i18n("Yearly"), i18n("Yearly")};
02073 
02074   mResult = mailBodyIncidence( event );
02075   mResult += i18n("Start Date: %1\n").arg( event->dtStartDateStr() );
02076   if ( !event->doesFloat() ) {
02077     mResult += i18n("Start Time: %1\n").arg( event->dtStartTimeStr() );
02078   }
02079   if ( event->dtStart() != event->dtEnd() ) {
02080     mResult += i18n("End Date: %1\n").arg( event->dtEndDateStr() );
02081   }
02082   if ( !event->doesFloat() ) {
02083     mResult += i18n("End Time: %1\n").arg( event->dtEndTimeStr() );
02084   }
02085   if ( event->doesRecur() ) {
02086     Recurrence *recur = event->recurrence();
02087     // TODO: Merge these two to one of the form "Recurs every 3 days"
02088     mResult += i18n("Recurs: %1\n")
02089              .arg( recurrence[ recur->recurrenceType() ] );
02090     mResult += i18n("Frequency: %1\n")
02091              .arg( event->recurrence()->frequency() );
02092 
02093     if ( recur->duration() > 0 ) {
02094       mResult += i18n ("Repeats once", "Repeats %n times", recur->duration());
02095       mResult += '\n';
02096     } else {
02097       if ( recur->duration() != -1 ) {
02098 // TODO_Recurrence: What to do with floating
02099         QString endstr;
02100         if ( event->doesFloat() ) {
02101           endstr = KGlobal::locale()->formatDate( recur->endDate() );
02102         } else {
02103           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() );
02104         }
02105         mResult += i18n("Repeat until: %1\n").arg( endstr );
02106       } else {
02107         mResult += i18n("Repeats forever\n");
02108       }
02109     }
02110   }
02111   QString details = event->description();
02112   if ( !details.isEmpty() ) {
02113     mResult += i18n("Details:\n%1\n").arg( details );
02114   }
02115   return !mResult.isEmpty();
02116 }
02117 
02118 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
02119 {
02120   mResult = mailBodyIncidence( todo );
02121 
02122   if ( todo->hasStartDate() ) {
02123     mResult += i18n("Start Date: %1\n").arg( todo->dtStartDateStr() );
02124     if ( !todo->doesFloat() ) {
02125       mResult += i18n("Start Time: %1\n").arg( todo->dtStartTimeStr() );
02126     }
02127   }
02128   if ( todo->hasDueDate() ) {
02129     mResult += i18n("Due Date: %1\n").arg( todo->dtDueDateStr() );
02130     if ( !todo->doesFloat() ) {
02131       mResult += i18n("Due Time: %1\n").arg( todo->dtDueTimeStr() );
02132     }
02133   }
02134   QString details = todo->description();
02135   if ( !details.isEmpty() ) {
02136     mResult += i18n("Details:\n%1\n").arg( details );
02137   }
02138   return !mResult.isEmpty();
02139 }
02140 
02141 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
02142 {
02143   mResult = mailBodyIncidence( journal );
02144   mResult += i18n("Date: %1\n").arg( journal->dtStartDateStr() );
02145   if ( !journal->doesFloat() ) {
02146     mResult += i18n("Time: %1\n").arg( journal->dtStartTimeStr() );
02147   }
02148   if ( !journal->description().isEmpty() )
02149     mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() );
02150   return !mResult.isEmpty();
02151 }
02152 
02153 
02154 
02155 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
02156 {
02157   if ( !incidence )
02158     return QString::null;
02159 
02160   MailBodyVisitor v;
02161   if ( v.act( incidence ) ) {
02162     return v.result();
02163   }
02164   return QString::null;
02165 }
02166 
02167 static QString recurEnd( Incidence *incidence )
02168 {
02169   QString endstr;
02170   if ( incidence->doesFloat() ) {
02171     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
02172   } else {
02173     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
02174   }
02175   return endstr;
02176 }
02177 
02178 QString IncidenceFormatter::recurrenceString(Incidence * incidence)
02179 {
02180   if ( !incidence->doesRecur() )
02181     return i18n( "No recurrence" );
02182 
02183   Recurrence *recur = incidence->recurrence();
02184   switch ( recur->recurrenceType() ) {
02185     case Recurrence::rNone:
02186       return i18n( "No recurrence" );
02187     case Recurrence::rMinutely:
02188       if ( recur->duration() != -1 )
02189         return i18n( "Recurs every minute until %1", "Recurs every %n minutes until %1", recur->frequency() )
02190             .arg( recurEnd( incidence ) );
02191       return i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() );
02192     case Recurrence::rHourly:
02193       if ( recur->duration() != -1 )
02194         return i18n( "Recurs hourly until %1", "Recurs every %n hours until %1", recur->frequency() )
02195             .arg( recurEnd( incidence ) );
02196       return i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() );
02197     case Recurrence::rDaily:
02198       if ( recur->duration() != -1 )
02199         return i18n( "Recurs daily until %1", "Recurs every %n days until %1", recur->frequency() )
02200             .arg( recurEnd( incidence ) );
02201       return i18n( "Recurs daily", "Recurs every %n days", recur->frequency() );
02202     case Recurrence::rWeekly:
02203       if ( recur->duration() != -1 )
02204         return i18n( "Recurs weekly until %1", "Recurs every %n weeks until %1", recur->frequency() )
02205             .arg( recurEnd( incidence ) );
02206       return i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() );
02207     case Recurrence::rMonthlyPos:
02208     case Recurrence::rMonthlyDay:
02209       if ( recur->duration() != -1 )
02210         return i18n( "Recurs monthly until %1", "Recurs every %n month until %1", recur->frequency() )
02211             .arg( recurEnd( incidence ) );
02212       return i18n( "Recurs monthly", "Recurs every %n month", recur->frequency() );
02213     case Recurrence::rYearlyMonth:
02214     case Recurrence::rYearlyDay:
02215     case Recurrence::rYearlyPos:
02216       if ( recur->duration() != -1 )
02217         return i18n( "Recurs yearly until %1", "Recurs every %n years until %1", recur->frequency() )
02218             .arg( recurEnd( incidence ) );
02219       return i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
02220     default:
02221       return i18n( "Incidence recurs" );
02222   }
02223 }
KDE Home | KDE Accessibility Home | Description of Access Keys