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