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