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   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 
01205 QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar,
01206     InvitationFormatterHelper *helper )
01207 {
01208   if ( invitation.isEmpty() ) return QString::null;
01209 
01210   ICalFormat format;
01211   // parseScheduleMessage takes the tz from the calendar, no need to set it manually here for the format!
01212   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
01213 
01214   if( !msg ) {
01215     kdDebug( 5850 ) << "Failed to parse the scheduling message" << endl;
01216     Q_ASSERT( format.exception() );
01217     kdDebug( 5850 ) << format.exception()->message() << endl;
01218     return QString::null;
01219   }
01220 
01221   IncidenceBase *incBase = msg->event();
01222 
01223   Incidence* existingIncidence = 0;
01224   if ( helper->calendar() ) {
01225     existingIncidence = helper->calendar()->incidence( incBase->uid() );
01226     if ( !existingIncidence ) {
01227       const Incidence::List list = helper->calendar()->incidences();
01228       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
01229         if ( (*it)->schedulingID() == incBase->uid() ) {
01230           existingIncidence = *it;
01231           break;
01232         }
01233       }
01234     }
01235   }
01236 
01237   // First make the text of the message
01238   QString html;
01239 
01240   QString tableStyle = QString::fromLatin1(
01241     "style=\"border: solid 1px; margin: 0em;\"" );
01242   QString tableHead = QString::fromLatin1(
01243     "<div align=\"center\">"
01244     "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
01245     "<tr><td>").arg(tableStyle);
01246 
01247   html += tableHead;
01248   InvitationHeaderVisitor headerVisitor;
01249   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
01250   if ( !headerVisitor.act( incBase, msg ) )
01251     return QString::null;
01252   html += "<b>" + headerVisitor.result() + "</b>";
01253 
01254   InvitationBodyVisitor bodyVisitor;
01255   if ( !bodyVisitor.act( incBase, msg ) )
01256     return QString::null;
01257   html += bodyVisitor.result();
01258 
01259   if ( msg->method() == Scheduler::Request ) { // ### Scheduler::Publish/Refresh/Add as well?
01260     IncidenceCompareVisitor compareVisitor;
01261     if ( compareVisitor.act( incBase, existingIncidence ) ) {
01262       html += i18n("<p align=\"left\">The following changes have been made by the organizer:</p>");
01263       html += compareVisitor.result();
01264     }
01265   }
01266 
01267   html += "<br/>";
01268   html += "<table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td></tr><tr>";
01269 
01270 #if 0
01271   html += helper->makeLinkURL( "accept", i18n("[Enter this into my calendar]") );
01272   html += "</td><td> &nbsp; </td><td>";
01273 #endif
01274 
01275   // Add groupware links
01276 
01277   switch ( msg->method() ) {
01278     case Scheduler::Publish:
01279     case Scheduler::Request:
01280     case Scheduler::Refresh:
01281     case Scheduler::Add:
01282     {
01283         Incidence *inc = dynamic_cast<Incidence*>( incBase );
01284         if ( inc && inc->revision() > 0 && (existingIncidence || !helper->calendar()) ) {
01285             if ( incBase->type() == "Todo" ) {
01286                 html += "<td colspan=\"9\">";
01287                 html += helper->makeLink( "reply", i18n( "[Enter this into my task list]" ) );
01288             } else {
01289                 html += "<td colspan=\"13\">";
01290                 html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01291             }
01292             html += "</td></tr><tr>";
01293         }
01294         html += "<td>";
01295 
01296         if ( helper->calendar() && !existingIncidence ) {
01297           // Accept
01298           html += helper->makeLink( "accept", i18n( "[Accept]" ) );
01299           html += "</td><td> &nbsp; </td><td>";
01300           html += helper->makeLink( "accept_conditionally",
01301                             i18n( "Accept conditionally", "[Accept cond.]" ) );
01302           html += "</td><td> &nbsp; </td><td>";
01303           // counter proposal
01304           html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
01305           html += "</td><td> &nbsp; </td><td>";
01306           // Decline
01307           html += helper->makeLink( "decline", i18n( "[Decline]" ) );
01308           html += "</td><td> &nbsp; </td><td>";
01309 
01310           // Delegate
01311           html += helper->makeLink( "delegate", i18n( "[Delegate]" ) );
01312           html += "</td><td> &nbsp; </td><td>";
01313 
01314           // Forward
01315           html += helper->makeLink( "forward", i18n( "[Forward]" ) );
01316 
01317           if ( incBase->type() == "Event" ) {
01318               html += "</b></a></td><td> &nbsp; </td><td>";
01319               html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
01320           }
01321         }
01322         break;
01323     }
01324 
01325     case Scheduler::Cancel:
01326         // Cancel event from my calendar
01327         html += helper->makeLink( "cancel", i18n( "[Remove this from my calendar]" ) );
01328         break;
01329 
01330     case Scheduler::Reply:
01331         // Enter this into my calendar
01332         if ( incBase->type() == "Todo" ) {
01333           html += helper->makeLink( "reply", i18n( "[Enter this into my task list]" ) );
01334         } else {
01335           html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01336         }
01337         break;
01338 
01339     case Scheduler::Counter:
01340     case Scheduler::Declinecounter:
01341     case Scheduler::NoMethod:
01342         break;
01343   }
01344 
01345   html += "</td></tr></table>";
01346 
01347   html += "</td></tr></table><br></div>";
01348 
01349   return html;
01350 }
01351 
01352 
01353 
01354 
01355 /*******************************************************************
01356  *  Helper functions for the msTNEF -> VPart converter
01357  *******************************************************************/
01358 
01359 
01360 //-----------------------------------------------------------------------------
01361 
01362 static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
01363                            const QString& fallback = QString::null)
01364 {
01365   return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
01366                             fallback );
01367 }
01368 
01369 static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
01370                            const QString& fallback = QString::null )
01371 {
01372   return tnefMsg->findNamedProp( name, fallback );
01373 }
01374 
01375 struct save_tz { char* old_tz; char* tz_env_str; };
01376 
01377 /* temporarily go to a different timezone */
01378 static struct save_tz set_tz( const char* _tc )
01379 {
01380   const char *tc = _tc?_tc:"UTC";
01381 
01382   struct save_tz rv;
01383 
01384   rv.old_tz = 0;
01385   rv.tz_env_str = 0;
01386 
01387   //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;
01388 
01389   char* tz_env = 0;
01390   if( getenv( "TZ" ) ) {
01391     tz_env = strdup( getenv( "TZ" ) );
01392     rv.old_tz = tz_env;
01393   }
01394   char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
01395   strcpy( tmp_env, "TZ=" );
01396   strcpy( tmp_env+3, tc );
01397   putenv( tmp_env );
01398 
01399   rv.tz_env_str = tmp_env;
01400 
01401   /* tmp_env is not free'ed -- it is part of the environment */
01402 
01403   tzset();
01404   //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;
01405 
01406   return rv;
01407 }
01408 
01409 /* restore previous timezone */
01410 static void unset_tz( struct save_tz old_tz )
01411 {
01412   if( old_tz.old_tz ) {
01413     char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
01414     strcpy( tmp_env, "TZ=" );
01415     strcpy( tmp_env+3, old_tz.old_tz );
01416     putenv( tmp_env );
01417     /* tmp_env is not free'ed -- it is part of the environment */
01418     free( old_tz.old_tz );
01419   } else {
01420     /* clear TZ from env */
01421     putenv( strdup("TZ") );
01422   }
01423   tzset();
01424 
01425   /* is this OK? */
01426   if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
01427 }
01428 
01429 static QDateTime utc2Local( const QDateTime& utcdt )
01430 {
01431   struct tm tmL;
01432 
01433   save_tz tmp_tz = set_tz("UTC");
01434   time_t utc = utcdt.toTime_t();
01435   unset_tz( tmp_tz );
01436 
01437   localtime_r( &utc, &tmL );
01438   return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
01439                     QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
01440 }
01441 
01442 
01443 static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
01444                                           bool bDateOnly = false )
01445 {
01446   QDate tmpDate;
01447   QTime tmpTime;
01448   int year, month, day, hour, minute, second;
01449 
01450   if( bDateOnly ) {
01451     year = dtStr.left( 4 ).toInt();
01452     month = dtStr.mid( 4, 2 ).toInt();
01453     day = dtStr.mid( 6, 2 ).toInt();
01454     hour = 0;
01455     minute = 0;
01456     second = 0;
01457   } else {
01458     year = dtStr.left( 4 ).toInt();
01459     month = dtStr.mid( 4, 2 ).toInt();
01460     day = dtStr.mid( 6, 2 ).toInt();
01461     hour = dtStr.mid( 9, 2 ).toInt();
01462     minute = dtStr.mid( 11, 2 ).toInt();
01463     second = dtStr.mid( 13, 2 ).toInt();
01464   }
01465   tmpDate.setYMD( year, month, day );
01466   tmpTime.setHMS( hour, minute, second );
01467 
01468   if( tmpDate.isValid() && tmpTime.isValid() ) {
01469     QDateTime dT = QDateTime( tmpDate, tmpTime );
01470 
01471     if( !bDateOnly ) {
01472       // correct for GMT ( == Zulu time == UTC )
01473       if (dtStr.at(dtStr.length()-1) == 'Z') {
01474         //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
01475         //localUTCOffset( dT ) );
01476         dT = utc2Local( dT );
01477       }
01478     }
01479     return dT;
01480   } else
01481     return QDateTime();
01482 }
01483 
01484 
01485 
01486 QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef )
01487 {
01488   bool bOk = false;
01489 
01490   KTNEFParser parser;
01491   QBuffer buf( tnef );
01492   CalendarLocal cal ( QString::fromLatin1( "UTC" ) );
01493   KABC::Addressee addressee;
01494   KABC::VCardConverter cardConv;
01495   ICalFormat calFormat;
01496   Event* event = new Event();
01497 
01498   if( parser.openDevice( &buf ) ) {
01499     KTNEFMessage* tnefMsg = parser.message();
01500     //QMap<int,KTNEFProperty*> props = parser.message()->properties();
01501 
01502     // Everything depends from property PR_MESSAGE_CLASS
01503     // (this is added by KTNEFParser):
01504     QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
01505       .upper();
01506     if( !msgClass.isEmpty() ) {
01507       // Match the old class names that might be used by Outlook for
01508       // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
01509       bool bCompatClassAppointment = false;
01510       bool bCompatMethodRequest = false;
01511       bool bCompatMethodCancled = false;
01512       bool bCompatMethodAccepted = false;
01513       bool bCompatMethodAcceptedCond = false;
01514       bool bCompatMethodDeclined = false;
01515       if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
01516         bCompatClassAppointment = true;
01517         if( msgClass.endsWith( ".MTGREQ" ) )
01518           bCompatMethodRequest = true;
01519         if( msgClass.endsWith( ".MTGCNCL" ) )
01520           bCompatMethodCancled = true;
01521         if( msgClass.endsWith( ".MTGRESPP" ) )
01522           bCompatMethodAccepted = true;
01523         if( msgClass.endsWith( ".MTGRESPA" ) )
01524           bCompatMethodAcceptedCond = true;
01525         if( msgClass.endsWith( ".MTGRESPN" ) )
01526           bCompatMethodDeclined = true;
01527       }
01528       bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );
01529 
01530       if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
01531         // Compose a vCal
01532         bool bIsReply = false;
01533         QString prodID = "-//Microsoft Corporation//Outlook ";
01534         prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
01535         prodID += "MIMEDIR/EN\n";
01536         prodID += "VERSION:2.0\n";
01537         calFormat.setApplication( "Outlook", prodID );
01538 
01539         Scheduler::Method method;
01540         if( bCompatMethodRequest )
01541           method = Scheduler::Request;
01542         else if( bCompatMethodCancled )
01543           method = Scheduler::Cancel;
01544         else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
01545                  bCompatMethodDeclined ) {
01546           method = Scheduler::Reply;
01547           bIsReply = true;
01548         } else {
01549           // pending(khz): verify whether "0x0c17" is the right tag ???
01550           //
01551           // at the moment we think there are REQUESTS and UPDATES
01552           //
01553           // but WHAT ABOUT REPLIES ???
01554           //
01555           //
01556 
01557           if( tnefMsg->findProp(0x0c17) == "1" )
01558             bIsReply = true;
01559           method = Scheduler::Request;
01560         }
01561 
01563         ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );
01564 
01565         QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );
01566 
01567         if( !sSenderSearchKeyEmail.isEmpty() ) {
01568           int colon = sSenderSearchKeyEmail.find( ':' );
01569           // May be e.g. "SMTP:KHZ@KDE.ORG"
01570           if( sSenderSearchKeyEmail.find( ':' ) == -1 )
01571             sSenderSearchKeyEmail.remove( 0, colon+1 );
01572         }
01573 
01574         QString s( tnefMsg->findProp( 0x0e04 ) );
01575         QStringList attendees = QStringList::split( ';', s );
01576         if( attendees.count() ) {
01577           for( QStringList::Iterator it = attendees.begin();
01578                it != attendees.end(); ++it ) {
01579             // Skip all entries that have no '@' since these are
01580             // no mail addresses
01581             if( (*it).find('@') == -1 ) {
01582               s = (*it).stripWhiteSpace();
01583 
01584               Attendee *attendee = new Attendee( s, s, true );
01585               if( bIsReply ) {
01586                 if( bCompatMethodAccepted )
01587                   attendee->setStatus( Attendee::Accepted );
01588                 if( bCompatMethodDeclined )
01589                   attendee->setStatus( Attendee::Declined );
01590                 if( bCompatMethodAcceptedCond )
01591                   attendee->setStatus(Attendee::Tentative);
01592               } else {
01593                 attendee->setStatus( Attendee::NeedsAction );
01594                 attendee->setRole( Attendee::ReqParticipant );
01595               }
01596               event->addAttendee(attendee);
01597             }
01598           }
01599         } else {
01600           // Oops, no attendees?
01601           // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
01602           s = sSenderSearchKeyEmail;
01603           if( !s.isEmpty() ) {
01604             Attendee *attendee = new Attendee( QString::null, QString::null,
01605                                                true );
01606             if( bIsReply ) {
01607               if( bCompatMethodAccepted )
01608                 attendee->setStatus( Attendee::Accepted );
01609               if( bCompatMethodAcceptedCond )
01610                 attendee->setStatus( Attendee::Declined );
01611               if( bCompatMethodDeclined )
01612                 attendee->setStatus( Attendee::Tentative );
01613             } else {
01614               attendee->setStatus(Attendee::NeedsAction);
01615               attendee->setRole(Attendee::ReqParticipant);
01616             }
01617             event->addAttendee(attendee);
01618           }
01619         }
01620         s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
01621         if( s.isEmpty() && !bIsReply )
01622           s = sSenderSearchKeyEmail;
01623         // TODO: Use the common name?
01624         if( !s.isEmpty() )
01625           event->setOrganizer( s );
01626 
01627         s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
01628           .replace( QChar( ':' ), QString::null );
01629         event->setDtStart( QDateTime::fromString( s ) ); // ## Format??
01630 
01631         s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
01632           .replace( QChar( ':' ), QString::null );
01633         event->setDtEnd( QDateTime::fromString( s ) );
01634 
01635         s = tnefMsg->findProp( 0x8208 );
01636         event->setLocation( s );
01637 
01638         // is it OK to set this to OPAQUE always ??
01639         //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
01640         //vPart += "SEQUENCE:0\n";
01641 
01642         // is "0x0023" OK  -  or should we look for "0x0003" ??
01643         s = tnefMsg->findProp( 0x0023 );
01644         event->setUid( s );
01645 
01646         // PENDING(khz): is this value in local timezone? Must it be
01647         // adjusted? Most likely this is a bug in the server or in
01648         // Outlook - we ignore it for now.
01649         s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
01650           .replace( QChar( ':' ), QString::null );
01651         // ### libkcal always uses currentDateTime()
01652         // event->setDtStamp(QDateTime::fromString(s));
01653 
01654         s = tnefMsg->findNamedProp( "Keywords" );
01655         event->setCategories( s );
01656 
01657         s = tnefMsg->findProp( 0x1000 );
01658         event->setDescription( s );
01659 
01660         s = tnefMsg->findProp( 0x0070 );
01661         event->setSummary( s );
01662 
01663         s = tnefMsg->findProp( 0x0026 );
01664         event->setPriority( s.toInt() );
01665 
01666         // is reminder flag set ?
01667         if(!tnefMsg->findProp(0x8503).isEmpty()) {
01668           Alarm *alarm = new Alarm(event);
01669           QDateTime highNoonTime =
01670             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
01671                                      .replace( QChar( '-' ), "" )
01672                                      .replace( QChar( ':' ), "" ) );
01673           QDateTime wakeMeUpTime =
01674             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
01675                                      .replace( QChar( '-' ), "" )
01676                                      .replace( QChar( ':' ), "" ) );
01677           alarm->setTime(wakeMeUpTime);
01678 
01679           if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
01680             alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
01681           else
01682             // default: wake them up 15 minutes before the appointment
01683             alarm->setStartOffset( Duration( 15*60 ) );
01684           alarm->setDisplayAlarm( i18n( "Reminder" ) );
01685 
01686           // Sorry: the different action types are not known (yet)
01687           //        so we always set 'DISPLAY' (no sounds, no images...)
01688           event->addAlarm( alarm );
01689         }
01690         cal.addEvent( event );
01691         bOk = true;
01692         // we finished composing a vCal
01693       } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
01694         addressee.setUid( stringProp( tnefMsg, attMSGID ) );
01695         addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) );
01696         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
01697         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
01698         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
01699         addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) );
01700         addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) );
01701         addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
01702         addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
01703         addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) );
01704         addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) );
01705         addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) );
01706 
01707         QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
01708           .replace( QChar( '-' ), QString::null )
01709           .replace( QChar( ':' ), QString::null );
01710         if( !s.isEmpty() )
01711           addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );
01712 
01713         addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );
01714 
01715         // collect parts of Name entry
01716         addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
01717         addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
01718         addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) );
01719         addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) );
01720         addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );
01721 
01722         addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
01723         addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
01724         addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
01725         /*
01726         the MAPI property ID of this (multiline) )field is unknown:
01727         vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
01728         */
01729 
01730         KABC::Address adr;
01731         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) );
01732         adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
01733         adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
01734         adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
01735         adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) );
01736         adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
01737         adr.setType(KABC::Address::Home);
01738         addressee.insertAddress(adr);
01739 
01740         adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
01741         adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) );
01742         adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) );
01743         adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) );
01744         adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
01745         adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) );
01746         adr.setType( KABC::Address::Work );
01747         addressee.insertAddress( adr );
01748 
01749         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) );
01750         adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
01751         adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
01752         adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
01753         adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) );
01754         adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
01755         adr.setType( KABC::Address::Dom );
01756         addressee.insertAddress(adr);
01757 
01758         // problem: the 'other' address was stored by KOrganizer in
01759         //          a line looking like the following one:
01760         // 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
01761 
01762         QString nr;
01763         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
01764         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) );
01765         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
01766         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) );
01767         nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
01768         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) );
01769         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
01770         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) );
01771         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
01772         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) );
01773 
01774         s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
01775           .replace( QChar( '-' ), QString::null )
01776           .replace( QChar( ':' ), QString::null );
01777         if( !s.isEmpty() )
01778           addressee.setBirthday( QDateTime::fromString( s ) );
01779 
01780         bOk = ( !addressee.isEmpty() );
01781       } else if( "IPM.NOTE" == msgClass ) {
01782 
01783       } // else if ... and so on ...
01784     }
01785   }
01786 
01787   // Compose return string
01788   QString iCal = calFormat.toString( &cal );
01789   if( !iCal.isEmpty() )
01790     // This was an iCal
01791     return iCal;
01792 
01793   // Not an iCal - try a vCard
01794   KABC::VCardConverter converter;
01795   return converter.createVCard( addressee );
01796 }
01797 
01798 
01799 QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef,
01800         Calendar *mCalendar, InvitationFormatterHelper *helper )
01801 {
01802   QString vPart = IncidenceFormatter::msTNEFToVPart( tnef );
01803   QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper );
01804   if( !iCal.isEmpty() )
01805     return iCal;
01806   return vPart;
01807 }
01808 
01809 
01810 
01811 
01812 /*******************************************************************
01813  *  Helper functions for the Incidence tooltips
01814  *******************************************************************/
01815 
01816 class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
01817 {
01818   public:
01819     ToolTipVisitor() : mRichText( true ), mResult( "" ) {}
01820 
01821     bool act( IncidenceBase *incidence, bool richText=true)
01822     {
01823       mRichText = richText;
01824       mResult = "";
01825       return incidence ? incidence->accept( *this ) : false;
01826     }
01827     QString result() const { return mResult; }
01828 
01829   protected:
01830     bool visit( Event *event );
01831     bool visit( Todo *todo );
01832     bool visit( Journal *journal );
01833     bool visit( FreeBusy *fb );
01834 
01835     QString dateRangeText( Event*event );
01836     QString dateRangeText( Todo *todo );
01837     QString dateRangeText( Journal *journal );
01838     QString dateRangeText( FreeBusy *fb );
01839 
01840     QString generateToolTip( Incidence* incidence, QString dtRangeText );
01841 
01842   protected:
01843     bool mRichText;
01844     QString mResult;
01845 };
01846 
01847 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event*event )
01848 {
01849   QString ret;
01850   QString tmp;
01851   if ( event->isMultiDay() ) {
01852 
01853     tmp = "<br>" + i18n("Event start", "<i>From:</i>&nbsp;%1");
01854     if (event->doesFloat())
01855       ret += tmp.arg( event->dtStartDateStr().replace(" ", "&nbsp;") );
01856     else
01857       ret += tmp.arg( event->dtStartStr().replace(" ", "&nbsp;") );
01858 
01859     tmp = "<br>" + i18n("Event end","<i>To:</i>&nbsp;%1");
01860     if (event->doesFloat())
01861       ret += tmp.arg( event->dtEndDateStr().replace(" ", "&nbsp;") );
01862     else
01863       ret += tmp.arg( event->dtEndStr().replace(" ", "&nbsp;") );
01864 
01865   } else {
01866 
01867     ret += "<br>"+i18n("<i>Date:</i>&nbsp;%1").
01868         arg( event->dtStartDateStr().replace(" ", "&nbsp;") );
01869     if ( !event->doesFloat() ) {
01870       if ( event->dtStartTimeStr() == event->dtEndTimeStr() ) { // to prevent 'Time: 17:00 - 17:00'
01871         tmp = "<br>" + i18n("time for event, &nbsp; to prevent ugly line breaks",
01872         "<i>Time:</i>&nbsp;%1").
01873         arg( event->dtStartTimeStr().replace(" ", "&nbsp;") );
01874       } else {
01875         tmp = "<br>" + i18n("time range for event, &nbsp; to prevent ugly line breaks",
01876         "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2").
01877         arg( event->dtStartTimeStr().replace(" ", "&nbsp;") ).
01878         arg( event->dtEndTimeStr().replace(" ", "&nbsp;") );
01879       }
01880       ret += tmp;
01881     }
01882 
01883   }
01884   return ret;
01885 }
01886 
01887 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo*todo )
01888 {
01889   QString ret;
01890   bool floats( todo->doesFloat() );
01891   if (todo->hasStartDate())
01892     // No need to add <i> here. This is separated issue and each line
01893     // is very visible on its own. On the other hand... Yes, I like it
01894     // italics here :)
01895     ret += "<br>" + i18n("<i>Start:</i>&nbsp;%1").arg(
01896       (floats)
01897         ?(todo->dtStartDateStr().replace(" ", "&nbsp;"))
01898         :(todo->dtStartStr().replace(" ", "&nbsp;")) ) ;
01899   if (todo->hasDueDate())
01900     ret += "<br>" + i18n("<i>Due:</i>&nbsp;%1").arg(
01901       (floats)
01902         ?(todo->dtDueDateStr().replace(" ", "&nbsp;"))
01903         :(todo->dtDueStr().replace(" ", "&nbsp;")) );
01904   if (todo->isCompleted())
01905     ret += "<br>" + i18n("<i>Completed:</i>&nbsp;%1").arg( todo->completedStr().replace(" ", "&nbsp;") );
01906   else
01907     ret += "<br>" + i18n("%1 % completed").arg(todo->percentComplete());
01908 
01909   return ret;
01910 }
01911 
01912 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal )
01913 {
01914   QString ret;
01915   if (journal->dtStart().isValid() ) {
01916     ret += "<br>" + i18n("<i>Date:</i>&nbsp;%1").arg( journal->dtStartDateStr( false ) );
01917   }
01918   return ret;
01919 }
01920 
01921 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
01922 {
01923   QString tmp( "<br>" + i18n("<i>Period start:</i>&nbsp;%1") );
01924   QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) );
01925   tmp = "<br>" + i18n("<i>Period start:</i>&nbsp;%1");
01926   ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) );
01927   return ret;
01928 }
01929 
01930 
01931 
01932 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
01933 {
01934   mResult = generateToolTip( event, dateRangeText( event ) );
01935   return !mResult.isEmpty();
01936 }
01937 
01938 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
01939 {
01940   mResult = generateToolTip( todo, dateRangeText( todo ) );
01941   return !mResult.isEmpty();
01942 }
01943 
01944 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
01945 {
01946   mResult = generateToolTip( journal, dateRangeText( journal ) );
01947   return !mResult.isEmpty();
01948 }
01949 
01950 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
01951 {
01952   mResult = "<qt><b>" + i18n("Free/Busy information for %1")
01953         .arg(fb->organizer().fullName()) + "</b>";
01954   mResult += dateRangeText( fb );
01955   mResult += "</qt>";
01956   return !mResult.isEmpty();
01957 }
01958 
01959 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence* incidence, QString dtRangeText )
01960 {
01961   if ( !incidence )
01962     return QString::null;
01963 
01964   QString tmp = "<qt><b>"+ incidence->summary().replace("\n", "<br>")+"</b>";
01965 
01966   tmp += dtRangeText;
01967 
01968   if (!incidence->location().isEmpty()) {
01969     // Put Location: in italics
01970     tmp += "<br>"+i18n("<i>Location:</i>&nbsp;%1").
01971       arg( incidence->location().replace("\n", "<br>") );
01972   }
01973   if (!incidence->description().isEmpty()) {
01974     QString desc(incidence->description());
01975     if (desc.length()>120) {
01976       desc = desc.left(120) + "...";
01977     }
01978     tmp += "<br>----------<br>" + i18n("<i>Description:</i><br>") + desc.replace("\n", "<br>");
01979   }
01980   tmp += "</qt>";
01981   return tmp;
01982 }
01983 
01984 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
01985 {
01986   ToolTipVisitor v;
01987   if ( v.act( incidence, richText ) ) {
01988     return v.result();
01989   } else
01990     return QString::null;
01991 }
01992 
01993 
01994 
01995 
01996 /*******************************************************************
01997  *  Helper functions for the Incidence tooltips
01998  *******************************************************************/
01999 
02000 class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
02001 {
02002   public:
02003     MailBodyVisitor() : mResult( "" ) {}
02004 
02005     bool act( IncidenceBase *incidence )
02006     {
02007       mResult = "";
02008       return incidence ? incidence->accept( *this ) : false;
02009     }
02010     QString result() const { return mResult; }
02011 
02012   protected:
02013     bool visit( Event *event );
02014     bool visit( Todo *todo );
02015     bool visit( Journal *journal );
02016     bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); }
02017   protected:
02018     QString mResult;
02019 };
02020 
02021 
02022 static QString mailBodyIncidence( Incidence *incidence )
02023 {
02024   QString body;
02025   if ( !incidence->summary().isEmpty() ) {
02026     body += i18n("Summary: %1\n").arg( incidence->summary() );
02027   }
02028   if ( !incidence->organizer().isEmpty() ) {
02029     body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() );
02030   }
02031   if ( !incidence->location().isEmpty() ) {
02032     body += i18n("Location: %1\n").arg( incidence->location() );
02033   }
02034   return body;
02035 }
02036 
02037 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
02038 {
02039   QString recurrence[]= {i18n("no recurrence", "None"),
02040     i18n("Minutely"), i18n("Hourly"), i18n("Daily"),
02041     i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"),
02042     i18n("Yearly"), i18n("Yearly"), i18n("Yearly")};
02043 
02044   mResult = mailBodyIncidence( event );
02045   mResult += i18n("Start Date: %1\n").arg( event->dtStartDateStr() );
02046   if ( !event->doesFloat() ) {
02047     mResult += i18n("Start Time: %1\n").arg( event->dtStartTimeStr() );
02048   }
02049   if ( event->dtStart() != event->dtEnd() ) {
02050     mResult += i18n("End Date: %1\n").arg( event->dtEndDateStr() );
02051   }
02052   if ( !event->doesFloat() ) {
02053     mResult += i18n("End Time: %1\n").arg( event->dtEndTimeStr() );
02054   }
02055   if ( event->doesRecur() ) {
02056     Recurrence *recur = event->recurrence();
02057     // TODO: Merge these two to one of the form "Recurs every 3 days"
02058     mResult += i18n("Recurs: %1\n")
02059              .arg( recurrence[ recur->recurrenceType() ] );
02060     mResult += i18n("Frequency: %1\n")
02061              .arg( event->recurrence()->frequency() );
02062 
02063     if ( recur->duration() > 0 ) {
02064       mResult += i18n ("Repeats once", "Repeats %n times", recur->duration());
02065       mResult += '\n';
02066     } else {
02067       if ( recur->duration() != -1 ) {
02068 // TODO_Recurrence: What to do with floating
02069         QString endstr;
02070         if ( event->doesFloat() ) {
02071           endstr = KGlobal::locale()->formatDate( recur->endDate() );
02072         } else {
02073           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() );
02074         }
02075         mResult += i18n("Repeat until: %1\n").arg( endstr );
02076       } else {
02077         mResult += i18n("Repeats forever\n");
02078       }
02079     }
02080   }
02081   QString details = event->description();
02082   if ( !details.isEmpty() ) {
02083     mResult += i18n("Details:\n%1\n").arg( details );
02084   }
02085   return !mResult.isEmpty();
02086 }
02087 
02088 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
02089 {
02090   mResult = mailBodyIncidence( todo );
02091 
02092   if ( todo->hasStartDate() ) {
02093     mResult += i18n("Start Date: %1\n").arg( todo->dtStartDateStr() );
02094     if ( !todo->doesFloat() ) {
02095       mResult += i18n("Start Time: %1\n").arg( todo->dtStartTimeStr() );
02096     }
02097   }
02098   if ( todo->hasDueDate() ) {
02099     mResult += i18n("Due Date: %1\n").arg( todo->dtDueDateStr() );
02100     if ( !todo->doesFloat() ) {
02101       mResult += i18n("Due Time: %1\n").arg( todo->dtDueTimeStr() );
02102     }
02103   }
02104   QString details = todo->description();
02105   if ( !details.isEmpty() ) {
02106     mResult += i18n("Details:\n%1\n").arg( details );
02107   }
02108   return !mResult.isEmpty();
02109 }
02110 
02111 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
02112 {
02113   mResult = mailBodyIncidence( journal );
02114   mResult += i18n("Date: %1\n").arg( journal->dtStartDateStr() );
02115   if ( !journal->doesFloat() ) {
02116     mResult += i18n("Time: %1\n").arg( journal->dtStartTimeStr() );
02117   }
02118   if ( !journal->description().isEmpty() )
02119     mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() );
02120   return !mResult.isEmpty();
02121 }
02122 
02123 
02124 
02125 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
02126 {
02127   if ( !incidence )
02128     return QString::null;
02129 
02130   MailBodyVisitor v;
02131   if ( v.act( incidence ) ) {
02132     return v.result();
02133   }
02134   return QString::null;
02135 }
02136 
02137 static QString recurEnd( Incidence *incidence )
02138 {
02139   QString endstr;
02140   if ( incidence->doesFloat() ) {
02141     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
02142   } else {
02143     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
02144   }
02145   return endstr;
02146 }
02147 
02148 QString IncidenceFormatter::recurrenceString(Incidence * incidence)
02149 {
02150   if ( !incidence->doesRecur() )
02151     return i18n( "No recurrence" );
02152 
02153   Recurrence *recur = incidence->recurrence();
02154   switch ( recur->recurrenceType() ) {
02155     case Recurrence::rNone:
02156       return i18n( "No recurrence" );
02157     case Recurrence::rMinutely:
02158       if ( recur->duration() != -1 )
02159         return i18n( "Recurs every minute until %1", "Recurs every %n minutes until %1", recur->frequency() )
02160             .arg( recurEnd( incidence ) );
02161       return i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() );
02162     case Recurrence::rHourly:
02163       if ( recur->duration() != -1 )
02164         return i18n( "Recurs hourly until %1", "Recurs every %n hours until %1", recur->frequency() )
02165             .arg( recurEnd( incidence ) );
02166       return i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() );
02167     case Recurrence::rDaily:
02168       if ( recur->duration() != -1 )
02169         return i18n( "Recurs daily until %1", "Recurs every %n days until %1", recur->frequency() )
02170             .arg( recurEnd( incidence ) );
02171       return i18n( "Recurs daily", "Recurs every %n days", recur->frequency() );
02172     case Recurrence::rWeekly:
02173       if ( recur->duration() != -1 )
02174         return i18n( "Recurs weekly until %1", "Recurs every %n weeks until %1", recur->frequency() )
02175             .arg( recurEnd( incidence ) );
02176       return i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() );
02177     case Recurrence::rMonthlyPos:
02178     case Recurrence::rMonthlyDay:
02179       if ( recur->duration() != -1 )
02180         return i18n( "Recurs monthly until %1" ).arg( recurEnd( incidence ) );
02181       return i18n( "Recurs monthly" );
02182     case Recurrence::rYearlyMonth:
02183     case Recurrence::rYearlyDay:
02184     case Recurrence::rYearlyPos:
02185       if ( recur->duration() != -1 )
02186         return i18n( "Recurs yearly until %1" ).arg( recurEnd( incidence ) );
02187       return i18n( "Recurs yearly" );
02188     default:
02189       return i18n( "Incidence recurs" );
02190   }
02191 }
KDE Home | KDE Accessibility Home | Description of Access Keys