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