libkcal

incidenceformatter.cpp

00001 /*
00002     This file is part of libkcal.
00003 
00004     Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
00005     Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
00006 
00007     This library is free software; you can redistribute it and/or
00008     modify it under the terms of the GNU Library General Public
00009     License as published by the Free Software Foundation; either
00010     version 2 of the License, or (at your option) any later version.
00011 
00012     This library is distributed in the hope that it will be useful,
00013     but WITHOUT ANY WARRANTY; without even the implied warranty of
00014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015     Library General Public License for more details.
00016 
00017     You should have received a copy of the GNU Library General Public License
00018     along with this library; see the file COPYING.LIB.  If not, write to
00019     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00020     Boston, MA 02110-1301, USA.
00021 */
00022 
00023 #include "incidenceformatter.h"
00024 
00025 #include <libkcal/attachment.h>
00026 #include <libkcal/event.h>
00027 #include <libkcal/todo.h>
00028 #include <libkcal/journal.h>
00029 #include <libkcal/calendar.h>
00030 #include <libkcal/calendarlocal.h>
00031 #include <libkcal/icalformat.h>
00032 #include <libkcal/freebusy.h>
00033 #include <libkcal/calendarresources.h>
00034 
00035 #include <libemailfunctions/email.h>
00036 
00037 #include <ktnef/ktnefparser.h>
00038 #include <ktnef/ktnefmessage.h>
00039 #include <ktnef/ktnefdefs.h>
00040 #include <kabc/phonenumber.h>
00041 #include <kabc/vcardconverter.h>
00042 #include <kabc/stdaddressbook.h>
00043 
00044 #include <kapplication.h>
00045 #include <kemailsettings.h>
00046 // #include <kdebug.h>
00047 
00048 #include <klocale.h>
00049 #include <kglobal.h>
00050 #include <kiconloader.h>
00051 #include <kcalendarsystem.h>
00052 
00053 #include <qbuffer.h>
00054 #include <qstylesheet.h>
00055 #include <qdatetime.h>
00056 
00057 #include <time.h>
00058 
00059 
00060 using namespace KCal;
00061 
00062 
00063 /*******************************************************************
00064  *  Helper functions for the extensive display (event viewer)
00065  *******************************************************************/
00066 
00067 static QString eventViewerAddLink( const QString &ref, const QString &text,
00068                              bool newline = true )
00069 {
00070   QString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" );
00071   if ( newline ) tmpStr += "\n";
00072   return tmpStr;
00073 }
00074 
00075 static QString eventViewerAddTag( const QString & tag, const QString & text )
00076 {
00077   int numLineBreaks = text.contains( "\n" );
00078   QString str = "<" + tag + ">";
00079   QString tmpText = text;
00080   QString tmpStr = str;
00081   if( numLineBreaks >= 0 ) {
00082     if ( numLineBreaks > 0) {
00083       int pos = 0;
00084       QString tmp;
00085       for( int i = 0; i <= numLineBreaks; i++ ) {
00086         pos = tmpText.find( "\n" );
00087         tmp = tmpText.left( pos );
00088         tmpText = tmpText.right( tmpText.length() - pos - 1 );
00089         tmpStr += tmp + "<br>";
00090       }
00091     } else {
00092       tmpStr += tmpText;
00093     }
00094   }
00095   tmpStr += "</" + tag + ">";
00096   return tmpStr;
00097 }
00098 
00099 static QString linkPerson( const QString& email, QString name, QString uid )
00100 {
00101   // Make the search, if there is an email address to search on,
00102   // and either name or uid is missing
00103   if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
00104     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
00105     KABC::Addressee::List addressList = add_book->findByEmail( email );
00106     KABC::Addressee o = addressList.first();
00107     if ( !o.isEmpty() && addressList.size() < 2 ) {
00108       if ( name.isEmpty() )
00109         // No name set, so use the one from the addressbook
00110         name = o.formattedName();
00111       uid = o.uid();
00112     } else
00113       // Email not found in the addressbook. Don't make a link
00114       uid = QString::null;
00115   }
00116   kdDebug(5850) << "formatAttendees: uid = " << uid << endl;
00117 
00118   // Show the attendee
00119   QString tmpString = "<li>";
00120   if ( !uid.isEmpty() ) {
00121     // There is a UID, so make a link to the addressbook
00122     if ( name.isEmpty() )
00123       // Use the email address for text
00124       tmpString += eventViewerAddLink( "uid:" + uid, email );
00125     else
00126       tmpString += eventViewerAddLink( "uid:" + uid, name );
00127   } else {
00128     // No UID, just show some text
00129     tmpString += ( name.isEmpty() ? email : name );
00130   }
00131   tmpString += '\n';
00132 
00133   // Make the mailto link
00134   if ( !email.isEmpty() ) {
00135     KCal::Person person( name, email );
00136     KURL mailto;
00137     mailto.setProtocol( "mailto" );
00138     mailto.setPath( person.fullName() );
00139     tmpString += eventViewerAddLink( mailto.url(), QString::null );
00140   }
00141   tmpString += "</li>\n";
00142 
00143   return tmpString;
00144 }
00145 
00146 static QString eventViewerFormatAttendees( Incidence *event )
00147 {
00148   QString tmpStr;
00149   Attendee::List attendees = event->attendees();
00150   if ( attendees.count() ) {
00151 
00152     // Add organizer link
00153     tmpStr += eventViewerAddTag( "i", i18n("Organizer") );
00154     tmpStr += "<ul>";
00155     tmpStr += linkPerson( event->organizer().email(),
00156                           event->organizer().name(), QString::null );
00157     tmpStr += "</ul>";
00158 
00159     // Add attendees links
00160     tmpStr += eventViewerAddTag( "i", i18n("Attendees") );
00161     tmpStr += "<ul>";
00162     Attendee::List::ConstIterator it;
00163     for( it = attendees.begin(); it != attendees.end(); ++it ) {
00164       Attendee *a = *it;
00165       tmpStr += linkPerson( a->email(), a->name(), a->uid() );
00166       if ( !a->delegator().isEmpty() ) {
00167           tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
00168       }
00169       if ( !a->delegate().isEmpty() ) {
00170           tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
00171       }
00172     }
00173     tmpStr += "</ul>";
00174   }
00175   return tmpStr;
00176 }
00177 
00178 static QString eventViewerFormatAttachments( Incidence *i )
00179 {
00180   QString tmpStr;
00181   Attachment::List as = i->attachments();
00182   if ( as.count() > 0 ) {
00183     Attachment::List::ConstIterator it;
00184     for( it = as.begin(); it != as.end(); ++it ) {
00185       if ( (*it)->isUri() ) {
00186         QString name;
00187         if ( (*it)->uri().startsWith( "kmail:" ) )
00188           name = i18n( "Show mail" );
00189         else
00190           name = (*it)->uri();
00191         tmpStr += eventViewerAddLink( (*it)->uri(), name );
00192         tmpStr += "<br>";
00193       }
00194     }
00195   }
00196   return tmpStr;
00197 }
00198 
00199 /*
00200   FIXME:This function depends of kaddressbook. Is necessary a new
00201   type of event?
00202 */
00203 static QString eventViewerFormatBirthday( Event *event )
00204 {
00205   if ( !event) return  QString::null;
00206   if ( event->customProperty("KABC","BIRTHDAY") != "YES" ) return QString::null;
00207 
00208   QString uid = event->customProperty("KABC","UID-1");
00209   QString name = event->customProperty("KABC","NAME-1");
00210   QString email= event->customProperty("KABC","EMAIL-1");
00211 
00212   QString tmpString = "<ul>";
00213   tmpString += linkPerson( email, name, uid );
00214 
00215   if ( event->customProperty( "KABC", "ANNIVERSARY") == "YES" ) {
00216     uid = event->customProperty("KABC","UID-2");
00217     name = event->customProperty("KABC","NAME-2");
00218     email= event->customProperty("KABC","EMAIL-2");
00219     tmpString += linkPerson( email, name, uid );
00220   }
00221 
00222   tmpString += "</ul>";
00223   return tmpString;
00224 }
00225 
00226 static QString eventViewerFormatHeader( Incidence *incidence )
00227 {
00228   QString tmpStr = "<table><tr>";
00229 
00230   // show icons
00231   {
00232     tmpStr += "<td>";
00233 
00234     if ( incidence->type() == "Event" ) {
00235       tmpStr += "<img src=\"" +
00236                 KGlobal::iconLoader()->iconPath( "appointment", KIcon::Small ) +
00237                 "\">";
00238     }
00239     if ( incidence->type() == "Todo" ) {
00240       tmpStr += "<img src=\"" +
00241                 KGlobal::iconLoader()->iconPath( "todo", KIcon::Small ) +
00242                 "\">";
00243     }
00244     if ( incidence->type() == "Journal" ) {
00245       tmpStr += "<img src=\"" +
00246                 KGlobal::iconLoader()->iconPath( "journal", KIcon::Small ) +
00247                 "\">";
00248     }
00249     if ( incidence->isAlarmEnabled() ) {
00250       tmpStr += "<img src=\"" +
00251                 KGlobal::iconLoader()->iconPath( "bell", KIcon::Small ) +
00252                 "\">";
00253     }
00254     if ( incidence->doesRecur() ) {
00255       tmpStr += "<img src=\"" +
00256                 KGlobal::iconLoader()->iconPath( "recur", KIcon::Small ) +
00257                 "\">";
00258     }
00259     if ( incidence->isReadOnly() ) {
00260       tmpStr += "<img src=\"" +
00261                 KGlobal::iconLoader()->iconPath( "readonlyevent", KIcon::Small ) +
00262                 "\">";
00263     }
00264 
00265     tmpStr += "</td>";
00266   }
00267 
00268   tmpStr += "<td>"
00269             + eventViewerAddTag( "u",
00270                                  eventViewerAddTag( "b", incidence->summary() ) )
00271             + "</td>";
00272   tmpStr += "</tr></table><br>";
00273 
00274   return tmpStr;
00275 }
00276 
00277 static QString eventViewerFormatEvent( Event *event )
00278 {
00279   if ( !event ) return QString::null;
00280   QString tmpStr = eventViewerFormatHeader( event );
00281 
00282   tmpStr += "<table>";
00283 
00284   tmpStr += "<tr>";
00285   if ( event->doesFloat() ) {
00286     if ( event->isMultiDay() ) {
00287       tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>";
00288       tmpStr += "<td>" + i18n("<beginTime> - <endTime>","%1 - %2")
00289                     .arg( event->dtStartDateStr() )
00290                     .arg( event->dtEndDateStr() ) + "</td>";
00291     } else {
00292       tmpStr += "<td align=\"right\"><b>" + i18n( "Date" ) + "</b></td>";
00293       tmpStr += "<td>" + i18n("date as string","%1").arg( event->dtStartDateStr() ) + "</td>";
00294     }
00295   } else {
00296     if ( event->isMultiDay() ) {
00297       tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>";
00298       tmpStr += "<td>" + i18n("<beginTime> - <endTime>","%1 - %2")
00299                     .arg( event->dtStartStr() )
00300                     .arg( event->dtEndStr() ) + "</td>";
00301     } else {
00302       tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>";
00303       if ( event->hasEndDate() && event->dtStart() != event->dtEnd()) {
00304         tmpStr += "<td>" + i18n("<beginTime> - <endTime>","%1 - %2")
00305                       .arg( event->dtStartTimeStr() )
00306                       .arg( event->dtEndTimeStr() ) + "</td>";
00307       } else {
00308         tmpStr += "<td>" + event->dtStartTimeStr() + "</td>";
00309       }
00310       tmpStr += "</tr><tr>";
00311       tmpStr += "<td align=\"right\"><b>" + i18n( "Date" ) + "</b></td>";
00312       tmpStr += "<td>" + i18n("date as string","%1")
00313                     .arg( event->dtStartDateStr() ) + "</td>";
00314     }
00315   }
00316   tmpStr += "</tr>";
00317 
00318   if ( event->customProperty("KABC","BIRTHDAY")== "YES" ) {
00319     tmpStr += "<tr>";
00320     tmpStr += "<td align=\"right\"><b>" + i18n( "Birthday" ) + "</b></td>";
00321     tmpStr += "<td>" + eventViewerFormatBirthday( event ) + "</td>";
00322     tmpStr += "</tr>";
00323     tmpStr += "</table>";
00324     return tmpStr;
00325   }
00326 
00327   if ( !event->description().isEmpty() ) {
00328     tmpStr += "<tr>";
00329     tmpStr += "<td align=\"right\"><b>" + i18n( "Description" ) + "</b></td>";
00330     tmpStr += "<td>" + eventViewerAddTag( "p", event->description() ) + "</td>";
00331     tmpStr += "</tr>";
00332   }
00333 
00334   if ( !event->location().isEmpty() ) {
00335     tmpStr += "<tr>";
00336     tmpStr += "<td align=\"right\"><b>" + i18n( "Location" ) + "</b></td>";
00337     tmpStr += "<td>" + event->location() + "</td>";
00338     tmpStr += "</tr>";
00339   }
00340 
00341   if ( event->categories().count() > 0 ) {
00342     tmpStr += "<tr>";
00343     tmpStr += "<td align=\"right\"><b>" + i18n( "1 Category", "%n Categories", event->categories().count() )+ "</b></td>";
00344     tmpStr += "<td>" + event->categoriesStr() + "</td>";
00345     tmpStr += "</tr>";
00346   }
00347 
00348   if ( event->doesRecur() ) {
00349     QDateTime dt =
00350       event->recurrence()->getNextDateTime( QDateTime::currentDateTime() );
00351     tmpStr += "<tr>";
00352     tmpStr += "<td align=\"right\"><b>" + i18n( "Next on" ) + "</b></td>";
00353     if ( !event->doesFloat() ) {
00354       tmpStr += "<td>" +
00355                 KGlobal::locale()->formatDateTime( dt, true ) + "</td>";
00356     } else {
00357       tmpStr += "<td>" +
00358                 KGlobal::locale()->formatDate( dt.date(), true ) + "</td>";
00359     }
00360     tmpStr += "</tr>";
00361   }
00362 
00363   int attendeeCount = event->attendees().count();
00364   if ( attendeeCount > 0 ) {
00365     tmpStr += "<tr><td colspan=\"2\">";
00366     tmpStr += eventViewerFormatAttendees( event );
00367     tmpStr += "</td></tr>";
00368   }
00369 
00370   int attachmentCount = event->attachments().count();
00371   if ( attachmentCount > 0 ) {
00372     tmpStr += "<tr>";
00373     tmpStr += "<td align=\"right\"><b>" + i18n( "1 attachment", "%n attachments", attachmentCount )+ "</b></td>";
00374     tmpStr += "<td>" + eventViewerFormatAttachments( event ) + "</td>";
00375     tmpStr += "</tr>";
00376   }
00377 
00378   tmpStr += "</table>";
00379   tmpStr += "<em>" + i18n( "Creation date: %1.").arg(
00380     KGlobal::locale()->formatDateTime( event->created() , true ) ) + "</em>";
00381   return tmpStr;
00382 }
00383 
00384 static QString eventViewerFormatTodo( Todo *todo )
00385 {
00386   if ( !todo ) return QString::null;
00387   QString tmpStr = eventViewerFormatHeader( todo );
00388 
00389   tmpStr += "<table>";
00390 
00391   if ( todo->hasDueDate() ) {
00392     tmpStr += "<tr>";
00393     tmpStr += "<td align=\"right\"><b>" + i18n( "Due on" ) + "</b></td>";
00394     if ( !todo->doesFloat() ) {
00395       tmpStr += "<td>" +
00396                 KGlobal::locale()->formatDateTime( todo->dtDue(), true ) +
00397                 "</td>";
00398     } else {
00399       tmpStr += "<td>" +
00400                 KGlobal::locale()->formatDate( todo->dtDue().date(), true ) +
00401                 "</td>";
00402     }
00403     tmpStr += "</tr>";
00404   }
00405 
00406   if ( !todo->description().isEmpty() ) {
00407     tmpStr += "<tr>";
00408     tmpStr += "<td align=\"right\"><b>" + i18n( "Description" ) + "</b></td>";
00409     tmpStr += "<td>" + todo->description() + "</td>";
00410     tmpStr += "</tr>";
00411   }
00412 
00413   if ( !todo->location().isEmpty() ) {
00414     tmpStr += "<tr>";
00415     tmpStr += "<td align=\"right\"><b>" + i18n( "Location" ) + "</b></td>";
00416     tmpStr += "<td>" + todo->location() + "</td>";
00417     tmpStr += "</tr>";
00418   }
00419 
00420   if ( todo->categories().count() > 0 ) {
00421     tmpStr += "<tr>";
00422     tmpStr += "<td align=\"right\"><b>" + i18n( "1 Category", "%n Categories", todo->categories().count() )+ "</b></td>";
00423     tmpStr += "<td>" + todo->categoriesStr() + "</td>";
00424     tmpStr += "</tr>";
00425   }
00426 
00427   tmpStr += "<tr>";
00428   tmpStr += "<td align=\"right\"><b>" + i18n( "Priority" ) + "</b></td>";
00429   if ( todo->priority() > 0 ) {
00430     tmpStr += "<td>" + QString::number( todo->priority() ) + "</td>";
00431   } else {
00432     tmpStr += "<td>" + i18n( "Unspecified" ) + "</td>";
00433   }
00434   tmpStr += "</tr>";
00435 
00436   tmpStr += "<tr>";
00437   tmpStr += "<td align=\"right\"><b>" + i18n( "Completed" ) + "</b></td>";
00438   tmpStr += "<td>" + i18n( "%1 %" ).arg( todo->percentComplete() ) + "</td>";
00439   tmpStr += "</tr>";
00440 
00441   if ( todo->doesRecur() ) {
00442     QDateTime dt =
00443       todo->recurrence()->getNextDateTime( QDateTime::currentDateTime() );
00444     tmpStr += "<tr>";
00445     tmpStr += "<td align=\"right\"><b>" + i18n( "Next on" ) + "</b></td>";
00446     if ( !todo->doesFloat() ) {
00447       tmpStr += "<td>" +
00448                 KGlobal::locale()->formatDateTime( dt, true ) + "</td>";
00449     } else {
00450       tmpStr += "<td>" +
00451                 KGlobal::locale()->formatDate( dt.date(), true ) + "</td>";
00452     }
00453     tmpStr += "</tr>";
00454   }
00455 
00456   int attendeeCount = todo->attendees().count();
00457   if ( attendeeCount > 0 ) {
00458     tmpStr += "<tr><td colspan=\"2\">";
00459     tmpStr += eventViewerFormatAttendees( todo );
00460     tmpStr += "</td></tr>";
00461   }
00462 
00463   int attachmentCount = todo->attachments().count();
00464   if ( attachmentCount > 0 ) {
00465     tmpStr += "<tr>";
00466     tmpStr += "<td align=\"right\"><b>" + i18n( "1 attachment", "%n attachments", attachmentCount )+ "</b></td>";
00467     tmpStr += "<td>" + eventViewerFormatAttachments( todo ) + "</td>";
00468     tmpStr += "</tr>";
00469   }
00470 
00471   tmpStr += "</table>";
00472   tmpStr += "<em>" + i18n( "Creation date: %1.").arg(
00473     KGlobal::locale()->formatDateTime( todo->created(), true ) ) + "</em>";
00474   return tmpStr;
00475 }
00476 
00477 static QString eventViewerFormatJournal( Journal *journal )
00478 {
00479   if ( !journal ) return QString::null;
00480 
00481   QString tmpStr;
00482   if ( !journal->summary().isEmpty() ) {
00483     tmpStr += eventViewerAddTag( "u",
00484                                  eventViewerAddTag( "b", journal->summary() ) );
00485   }
00486   tmpStr += eventViewerAddTag( "b", i18n("Journal for %1").arg( journal->dtStartDateStr( false ) ) );
00487   if ( !journal->description().isEmpty() )
00488     tmpStr += eventViewerAddTag( "p", journal->description() );
00489   return tmpStr;
00490 }
00491 
00492 static QString eventViewerFormatFreeBusy( FreeBusy *fb )
00493 {
00494   if ( !fb ) return QString::null;
00495 
00496   QString tmpStr =
00497     eventViewerAddTag( "u",
00498                        eventViewerAddTag( "b", i18n("Free/Busy information for %1")
00499                                           .arg( fb->organizer().fullName() ) ) );
00500   tmpStr += eventViewerAddTag( "i", i18n("Busy times in date range %1 - %2:")
00501       .arg( KGlobal::locale()->formatDate( fb->dtStart().date(), true ) )
00502       .arg( KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) ) );
00503 
00504   QValueList<Period> periods = fb->busyPeriods();
00505 
00506   QString text = eventViewerAddTag( "em", eventViewerAddTag( "b", i18n("Busy:") ) );
00507   QValueList<Period>::iterator it;
00508   for ( it = periods.begin(); it != periods.end(); ++it ) {
00509     Period per = *it;
00510     if ( per.hasDuration() ) {
00511       int dur = per.duration().asSeconds();
00512       QString cont;
00513       if ( dur >= 3600 ) {
00514         cont += i18n("1 hour ", "%n hours ", dur / 3600 );
00515         dur %= 3600;
00516       }
00517       if ( dur >= 60 ) {
00518         cont += i18n("1 minute ", "%n minutes ", dur / 60);
00519         dur %= 60;
00520       }
00521       if ( dur > 0 ) {
00522         cont += i18n("1 second", "%n seconds", dur);
00523       }
00524       text += i18n("startDate for duration", "%1 for %2")
00525           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
00526           .arg( cont );
00527       text += "<br>";
00528     } else {
00529       if ( per.start().date() == per.end().date() ) {
00530         text += i18n("date, fromTime - toTime ", "%1, %2 - %3")
00531             .arg( KGlobal::locale()->formatDate( per.start().date() ) )
00532             .arg( KGlobal::locale()->formatTime( per.start().time() ) )
00533             .arg( KGlobal::locale()->formatTime( per.end().time() ) );
00534       } else {
00535         text += i18n("fromDateTime - toDateTime", "%1 - %2")
00536           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
00537           .arg( KGlobal::locale()->formatDateTime( per.end(), false ) );
00538       }
00539       text += "<br>";
00540     }
00541   }
00542   tmpStr += eventViewerAddTag( "p", text );
00543   return tmpStr;
00544 }
00545 
00546 class IncidenceFormatter::EventViewerVisitor : public IncidenceBase::Visitor
00547 {
00548   public:
00549     EventViewerVisitor() { mResult = ""; }
00550     bool act( IncidenceBase *incidence ) { return incidence->accept( *this ); }
00551     QString result() const { return mResult; }
00552   protected:
00553     bool visit( Event *event )
00554     {
00555       mResult = eventViewerFormatEvent( event );
00556       return !mResult.isEmpty();
00557     }
00558     bool visit( Todo *todo )
00559     {
00560       mResult = eventViewerFormatTodo( todo );
00561       return !mResult.isEmpty();
00562     }
00563     bool visit( Journal *journal )
00564     {
00565       mResult = eventViewerFormatJournal( journal );
00566       return !mResult.isEmpty();
00567     }
00568     bool visit( FreeBusy *fb )
00569     {
00570       mResult = eventViewerFormatFreeBusy( fb );
00571       return !mResult.isEmpty();
00572     }
00573 
00574   protected:
00575     QString mResult;
00576 };
00577 
00578 QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence )
00579 {
00580   if ( !incidence ) return QString::null;
00581   EventViewerVisitor v;
00582   if ( v.act( incidence ) ) {
00583     return v.result();
00584   } else
00585     return QString::null;
00586 }
00587 
00588 
00589 
00590 
00591 /*******************************************************************
00592  *  Helper functions for the body part formatter of kmail
00593  *******************************************************************/
00594 
00595 static QString string2HTML( const QString& str )
00596 {
00597   return QStyleSheet::escape( str );
00598 }
00599 
00600 static QString eventStartTimeStr( Event *event )
00601 {
00602   QString tmp;
00603   if ( !event->doesFloat() ) {
00604     tmp = i18n( "%1: Start Date, %2: Start Time", "%1 %2" ).
00605           arg( event->dtStartDateStr( false ), event->dtStartTimeStr() );
00606   } else {
00607     tmp = i18n( "%1: Start Date", "%1" ).
00608           arg( event->dtStartDateStr( false ) );
00609   }
00610   return tmp;
00611 }
00612 
00613 static QString eventEndTimeStr( Event *event )
00614 {
00615   QString tmp;
00616   if ( event->hasEndDate() && event->dtEnd().isValid() ) {
00617     if ( !event->doesFloat() ) {
00618       tmp = i18n( "%1: End Date, %2: End Time", "%1 %2" ).
00619             arg( event->dtEndDateStr( false ), event->dtEndTimeStr() );
00620     } else {
00621       tmp = i18n( "%1: End Date", "%1" ).arg( event->dtEndDateStr( false ) );
00622     }
00623   }
00624   return tmp;
00625 }
00626 
00627 static QString invitationRow( const QString &cell1, const QString &cell2 )
00628 {
00629   return "<tr><td>" + cell1 + "</td><td>" + cell2 + "</td></tr>\n";
00630 }
00631 
00632 static bool iamOrganizer( Incidence *incidence )
00633 {
00634   // Check if I'm the organizer for this incidence
00635 
00636   if ( !incidence ) {
00637     return false;
00638   }
00639 
00640   bool iam = false;
00641   KEMailSettings settings;
00642   QStringList profiles = settings.profiles();
00643   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
00644     settings.setProfile( *it );
00645     if ( settings.getSetting( KEMailSettings::EmailAddress ) == incidence->organizer().email() ) {
00646       iam = true;
00647       break;
00648     }
00649   }
00650   return iam;
00651 }
00652 
00653 static bool iamAttendee( Attendee *attendee )
00654 {
00655   // Check if I'm this attendee
00656 
00657   bool iam = false;
00658   KEMailSettings settings;
00659   QStringList profiles = settings.profiles();
00660   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
00661     settings.setProfile( *it );
00662     if ( settings.getSetting( KEMailSettings::EmailAddress ) == attendee->email() ) {
00663       iam = true;
00664       break;
00665     }
00666   }
00667   return iam;
00668 }
00669 
00670 static Attendee *findMyAttendee( Incidence *incidence )
00671 {
00672   // Return the attendee for the incidence that is probably me
00673 
00674   Attendee *attendee = 0;
00675   if ( !incidence ) {
00676     return attendee;
00677   }
00678 
00679   KEMailSettings settings;
00680   QStringList profiles = settings.profiles();
00681   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
00682     settings.setProfile( *it );
00683 
00684     Attendee::List attendees = incidence->attendees();
00685     Attendee::List::ConstIterator it2;
00686     for ( it2 = attendees.begin(); it2 != attendees.end(); ++it2 ) {
00687       Attendee *a = *it2;
00688       if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) {
00689         attendee = a;
00690         break;
00691       }
00692     }
00693   }
00694   return attendee;
00695 }
00696 
00697 static Attendee *findAttendee( Incidence *incidence, const QString &email )
00698 {
00699   // Search for an attendee by email address
00700 
00701   Attendee *attendee = 0;
00702   if ( !incidence ) {
00703     return attendee;
00704   }
00705 
00706   Attendee::List attendees = incidence->attendees();
00707   Attendee::List::ConstIterator it;
00708   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00709     Attendee *a = *it;
00710     if ( email == a->email() ) {
00711       attendee = a;
00712       break;
00713     }
00714   }
00715   return attendee;
00716 }
00717 
00718 static bool rsvpRequested( Incidence *incidence )
00719 {
00720   //use a heuristic to determine if a response is requested.
00721 
00722   bool rsvp = true; // better send superfluously than not at all
00723   Attendee::List attendees = incidence->attendees();
00724   Attendee::List::ConstIterator it;
00725   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00726     if ( it == attendees.begin() ) {
00727       rsvp = (*it)->RSVP(); // use what the first one has
00728     } else {
00729       if ( (*it)->RSVP() != rsvp ) {
00730         rsvp = true; // they differ, default
00731         break;
00732       }
00733     }
00734   }
00735   return rsvp;
00736 }
00737 
00738 static QString rsvpRequestedStr( bool rsvpRequested )
00739 {
00740   if ( rsvpRequested ) {
00741     return i18n( "Your response is requested" );
00742   } else {
00743     return i18n( "A response is not necessary" );
00744   }
00745 }
00746 
00747 static QString invitationPerson( const QString& email, QString name, QString uid )
00748 {
00749   // Make the search, if there is an email address to search on,
00750   // and either name or uid is missing
00751   if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
00752     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
00753     KABC::Addressee::List addressList = add_book->findByEmail( email );
00754     KABC::Addressee o = addressList.first();
00755     if ( !o.isEmpty() && addressList.size() < 2 ) {
00756       if ( name.isEmpty() )
00757         // No name set, so use the one from the addressbook
00758         name = o.formattedName();
00759       uid = o.uid();
00760     } else
00761       // Email not found in the addressbook. Don't make a link
00762       uid = QString::null;
00763   }
00764 
00765   // Show the attendee
00766   QString tmpString;
00767   if ( !uid.isEmpty() ) {
00768     // There is a UID, so make a link to the addressbook
00769     if ( name.isEmpty() )
00770       // Use the email address for text
00771       tmpString += eventViewerAddLink( "uid:" + uid, email );
00772     else
00773       tmpString += eventViewerAddLink( "uid:" + uid, name );
00774   } else {
00775     // No UID, just show some text
00776     tmpString += ( name.isEmpty() ? email : name );
00777   }
00778   tmpString += '\n';
00779 
00780   // Make the mailto link
00781   if ( !email.isEmpty() ) {
00782     KCal::Person person( name, email );
00783     KURL mailto;
00784     mailto.setProtocol( "mailto" );
00785     mailto.setPath( person.fullName() );
00786     tmpString += eventViewerAddLink( mailto.url(), QString::null );
00787   }
00788   tmpString += "\n";
00789 
00790   return tmpString;
00791 }
00792 
00793 static QString invitationsDetailsIncidence( Incidence *incidence )
00794 {
00795   QString html;
00796   QString descr;
00797   QStringList comments;
00798   if ( incidence->comments().isEmpty() && !incidence->description().isEmpty() ) {
00799     comments << incidence->description();
00800   } else {
00801     descr = incidence->description();
00802     comments = incidence->comments();
00803   }
00804 
00805   if( !descr.isEmpty() ) {
00806     html += "<br/><u>" + i18n("Description:")
00807       + "</u><table border=\"0\"><tr><td>&nbsp;</td><td>";
00808     html += string2HTML(descr) + "</td></tr></table>";
00809   }
00810   if ( !comments.isEmpty() ) {
00811     html += "<br><u>" + i18n("Comments:")
00812           + "</u><table border=\"0\"><tr><td>&nbsp;</td><td>";
00813     if ( comments.count() > 1 ) {
00814       html += "<ul>";
00815       for ( uint i = 0; i < comments.count(); ++i )
00816         html += "<li>" + string2HTML( comments[i] ) + "</li>";
00817       html += "</ul>";
00818     } else {
00819       html += string2HTML( comments[0] );
00820     }
00821     html += "</td></tr></table>";
00822   }
00823   return html;
00824 }
00825 
00826 static QString invitationDetailsEvent( Event* event )
00827 {
00828   // Invitation details are formatted into an HTML table
00829   if ( !event )
00830     return QString::null;
00831 
00832   QString html;
00833   QString tmp;
00834 
00835   QString sSummary = i18n( "Summary unspecified" );
00836   if ( ! event->summary().isEmpty() ) {
00837     sSummary = string2HTML( event->summary() );
00838   }
00839 
00840   QString sLocation = i18n( "Location unspecified" );
00841   if ( ! event->location().isEmpty() ) {
00842     sLocation = string2HTML( event->location() );
00843   }
00844 
00845   QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
00846   html = QString("<div dir=\"%1\">\n").arg(dir);
00847 
00848   html += "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n";
00849 
00850   // Invitation summary & location rows
00851   html += invitationRow( i18n( "What:" ), sSummary );
00852   html += invitationRow( i18n( "Where:" ), sLocation );
00853 
00854   // If a 1 day event
00855   if ( event->dtStart().date() == event->dtEnd().date() ) {
00856     html += invitationRow( i18n( "Date:" ), event->dtStartDateStr( false ) );
00857     if ( !event->doesFloat() ) {
00858       html += invitationRow( i18n( "Time:" ),
00859                              event->dtStartTimeStr() + " - " + event->dtEndTimeStr() );
00860     }
00861   } else {
00862     html += invitationRow( i18n( "Starting date of an event", "From:" ), event->dtStartDateStr( false ) );
00863     if ( !event->doesFloat() ) {
00864       html += invitationRow( i18n( "Starting time of an event", "At:" ), event->dtStartTimeStr() );
00865     }
00866     if ( event->hasEndDate() ) {
00867       html += invitationRow( i18n( "Ending date of an event", "To:" ), event->dtEndDateStr( false ) );
00868       if ( !event->doesFloat() ) {
00869         html += invitationRow( i18n( "Starting time of an event", "At:" ), event->dtEndTimeStr() );
00870       }
00871     } else {
00872       html += invitationRow( i18n( "Ending date of an event", "To:" ), i18n( "no end date specified" ) );
00873     }
00874   }
00875 
00876   // Invitation Duration Row
00877   if ( !event->doesFloat() && event->hasEndDate() ) {
00878     tmp = QString::null;
00879     QTime sDuration(0,0,0), t;
00880     int secs = event->dtStart().secsTo( event->dtEnd() );
00881     t = sDuration.addSecs( secs );
00882     if ( t.hour() > 0 ) {
00883       tmp += i18n( "1 hour ", "%n hours ", t.hour() );
00884     }
00885     if ( t.minute() > 0 ) {
00886       tmp += i18n( "1 minute ", "%n minutes ",  t.minute() );
00887     }
00888 
00889     html += invitationRow( i18n( "Duration:" ), tmp );
00890   }
00891 
00892   if ( event->doesRecur() )
00893     html += invitationRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( event ) );
00894 
00895   html += "</table>\n";
00896   html += invitationsDetailsIncidence( event );
00897   html += "</div>\n";
00898 
00899   return html;
00900 }
00901 
00902 static QString invitationDetailsTodo( Todo *todo )
00903 {
00904   // Task details are formatted into an HTML table
00905   if ( !todo )
00906     return QString::null;
00907 
00908   QString sSummary = i18n( "Summary unspecified" );
00909   QString sDescr = i18n( "Description unspecified" );
00910   if ( ! todo->summary().isEmpty() ) {
00911     sSummary = todo->summary();
00912   }
00913   if ( ! todo->description().isEmpty() ) {
00914     sDescr = todo->description();
00915   }
00916   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
00917   html += invitationRow( i18n( "Summary:" ), sSummary );
00918   html += invitationRow( i18n( "Description:" ), sDescr );
00919   html += "</table>\n";
00920   html += invitationsDetailsIncidence( todo );
00921 
00922   return html;
00923 }
00924 
00925 static QString invitationDetailsJournal( Journal *journal )
00926 {
00927   if ( !journal )
00928     return QString::null;
00929 
00930   QString sSummary = i18n( "Summary unspecified" );
00931   QString sDescr = i18n( "Description unspecified" );
00932   if ( ! journal->summary().isEmpty() ) {
00933     sSummary = journal->summary();
00934   }
00935   if ( ! journal->description().isEmpty() ) {
00936     sDescr = journal->description();
00937   }
00938   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
00939   html += invitationRow( i18n( "Summary:" ), sSummary );
00940   html += invitationRow( i18n( "Date:" ), journal->dtStartDateStr( false ) );
00941   html += invitationRow( i18n( "Description:" ), sDescr );
00942   html += "</table>\n";
00943   html += invitationsDetailsIncidence( journal );
00944 
00945   return html;
00946 }
00947 
00948 static QString invitationDetailsFreeBusy( FreeBusy *fb )
00949 {
00950   if ( !fb )
00951     return QString::null;
00952   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
00953 
00954   html += invitationRow( i18n("Person:"), fb->organizer().fullName() );
00955   html += invitationRow( i18n("Start date:"), fb->dtStartDateStr() );
00956   html += invitationRow( i18n("End date:"),
00957       KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) );
00958   html += "<tr><td colspan=2><hr></td></tr>\n";
00959   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
00960 
00961   QValueList<Period> periods = fb->busyPeriods();
00962 
00963   QValueList<Period>::iterator it;
00964   for ( it = periods.begin(); it != periods.end(); ++it ) {
00965     Period per = *it;
00966     if ( per.hasDuration() ) {
00967       int dur = per.duration().asSeconds();
00968       QString cont;
00969       if ( dur >= 3600 ) {
00970         cont += i18n("1 hour ", "%n hours ", dur / 3600);
00971         dur %= 3600;
00972       }
00973       if ( dur >= 60 ) {
00974         cont += i18n("1 minute", "%n minutes ", dur / 60);
00975         dur %= 60;
00976       }
00977       if ( dur > 0 ) {
00978         cont += i18n("1 second", "%n seconds", dur);
00979       }
00980       html += invitationRow( QString::null, i18n("startDate for duration", "%1 for %2")
00981           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
00982           .arg(cont) );
00983     } else {
00984       QString cont;
00985       if ( per.start().date() == per.end().date() ) {
00986         cont = i18n("date, fromTime - toTime ", "%1, %2 - %3")
00987             .arg( KGlobal::locale()->formatDate( per.start().date() ) )
00988             .arg( KGlobal::locale()->formatTime( per.start().time() ) )
00989             .arg( KGlobal::locale()->formatTime( per.end().time() ) );
00990       } else {
00991         cont = i18n("fromDateTime - toDateTime", "%1 - %2")
00992           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
00993           .arg( KGlobal::locale()->formatDateTime( per.end(), false ) );
00994       }
00995 
00996       html += invitationRow( QString::null, cont );
00997     }
00998   }
00999 
01000   html += "</table>\n";
01001   return html;
01002 }
01003 
01004 static QString invitationHeaderEvent( Event *event, ScheduleMessage *msg )
01005 {
01006   if ( !msg || !event )
01007     return QString::null;
01008 
01009   switch ( msg->method() ) {
01010   case Scheduler::Publish:
01011     return i18n( "This event has been published" );
01012   case Scheduler::Request:
01013     if ( event->revision() > 0 ) {
01014       return i18n( "This invitation has been updated" );
01015     }
01016     if ( iamOrganizer( event ) ) {
01017       return i18n( "I sent this invitation" );
01018     } else {
01019       if ( !event->organizer().fullName().isEmpty() ) {
01020         return i18n( "You received an invitation from %1" ).
01021           arg( event->organizer().fullName() );
01022       } else {
01023         return i18n( "You received an invitation" );
01024       }
01025     }
01026   case Scheduler::Refresh:
01027     return i18n( "This invitation was refreshed" );
01028   case Scheduler::Cancel:
01029     return i18n( "This invitation has been canceled" );
01030   case Scheduler::Add:
01031     return i18n( "Addition to the invitation" );
01032   case Scheduler::Reply: {
01033     Attendee::List attendees = event->attendees();
01034     if( attendees.count() == 0 ) {
01035       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01036       return QString::null;
01037     }
01038     if( attendees.count() != 1 ) {
01039       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01040                     << "but is " << attendees.count() << endl;
01041     }
01042     Attendee* attendee = *attendees.begin();
01043     QString attendeeName = attendee->name();
01044     if ( attendeeName.isEmpty() ) {
01045       attendeeName = attendee->email();
01046     }
01047     if ( attendeeName.isEmpty() ) {
01048       attendeeName = i18n( "Sender" );
01049     }
01050 
01051     QString delegatorName, dummy;
01052     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
01053     if ( delegatorName.isEmpty() ) {
01054       delegatorName = attendee->delegator();
01055     }
01056 
01057     switch( attendee->status() ) {
01058     case Attendee::NeedsAction:
01059       return i18n( "%1 indicates this invitation still needs some action" ).arg( attendeeName );
01060     case Attendee::Accepted:
01061       if ( delegatorName.isEmpty() ) {
01062         return i18n( "%1 accepts this invitation" ).arg( attendeeName );
01063       } else {
01064         return i18n( "%1 accepts this invitation on behalf of %2" ).
01065           arg( attendeeName ).arg( delegatorName );
01066       }
01067     case Attendee::Tentative:
01068       if ( delegatorName.isEmpty() ) {
01069         return i18n( "%1 tentatively accepts this invitation" ).
01070           arg( attendeeName );
01071       } else {
01072         return i18n( "%1 tentatively accepts this invitation on behalf of %2" ).
01073           arg( attendeeName ).arg( delegatorName );
01074       }
01075     case Attendee::Declined:
01076       if ( delegatorName.isEmpty() ) {
01077         return i18n( "%1 declines this invitation" ).arg( attendeeName );
01078       } else {
01079         return i18n( "%1 declines this invitation on behalf of %2" ).
01080           arg( attendeeName ).arg( delegatorName );
01081       }
01082     case Attendee::Delegated: {
01083       QString delegate, dummy;
01084       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
01085       if ( delegate.isEmpty() ) {
01086         delegate = attendee->delegate();
01087       }
01088       if ( !delegate.isEmpty() ) {
01089         return i18n( "%1 has delegated this invitation to %2" ).
01090           arg( attendeeName ) .arg( delegate );
01091       } else {
01092         return i18n( "%1 has delegated this invitation" ).arg( attendeeName );
01093       }
01094     }
01095     case Attendee::Completed:
01096       return i18n( "This invitation is now completed" );
01097     case Attendee::InProcess:
01098       return i18n( "%1 is still processing the invitation" ).
01099         arg( attendeeName );
01100     default:
01101       return i18n( "Unknown response to this invitation" );
01102     }
01103     break; }
01104   case Scheduler::Counter:
01105     return i18n( "Sender makes this counter proposal" );
01106   case Scheduler::Declinecounter:
01107     return i18n( "Sender declines the counter proposal" );
01108   case Scheduler::NoMethod:
01109     return i18n("Error: iMIP message with unknown method: '%1'").
01110       arg( msg->method() );
01111   }
01112   return QString::null;
01113 }
01114 
01115 static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg )
01116 {
01117   if ( !msg || !todo ) {
01118     return QString::null;
01119   }
01120 
01121   switch ( msg->method() ) {
01122   case Scheduler::Publish:
01123     return i18n("This task has been published");
01124   case Scheduler::Request:
01125     if ( todo->revision() > 0 ) {
01126       return i18n( "This task has been updated" );
01127     } else {
01128       return i18n( "You have been assigned this task" );
01129     }
01130   case Scheduler::Refresh:
01131     return i18n( "This task was refreshed" );
01132   case Scheduler::Cancel:
01133     return i18n( "This task was canceled" );
01134   case Scheduler::Add:
01135     return i18n( "Addition to the task" );
01136   case Scheduler::Reply: {
01137     Attendee::List attendees = todo->attendees();
01138     if( attendees.count() == 0 ) {
01139       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01140       return QString::null;
01141     }
01142     if( attendees.count() != 1 ) {
01143       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01144                     << "but is " << attendees.count() << endl;
01145     }
01146     Attendee* attendee = *attendees.begin();
01147 
01148     switch( attendee->status() ) {
01149     case Attendee::NeedsAction:
01150       return i18n( "Sender indicates this task assignment still needs some action" );
01151     case Attendee::Accepted:
01152       return i18n( "Sender accepts this task" );
01153     case Attendee::Tentative:
01154       return i18n( "Sender tentatively accepts this task" );
01155     case Attendee::Declined:
01156       return i18n( "Sender declines this task" );
01157     case Attendee::Delegated: {
01158       QString delegate, dummy;
01159       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
01160       if ( delegate.isEmpty() ) {
01161         delegate = attendee->delegate();
01162       }
01163       if ( !delegate.isEmpty() ) {
01164         return i18n( "Sender has delegated this request for the task to %1" ).arg( delegate );
01165       } else {
01166         return i18n( "Sender has delegated this request for the task " );
01167       }
01168     }
01169     case Attendee::Completed:
01170       return i18n( "The request for this task is now completed" );
01171     case Attendee::InProcess:
01172       return i18n( "Sender is still processing the invitation" );
01173     default:
01174       return i18n( "Unknown response to this task" );
01175     }
01176     break; }
01177   case Scheduler::Counter:
01178     return i18n( "Sender makes this counter proposal" );
01179   case Scheduler::Declinecounter:
01180     return i18n( "Sender declines the counter proposal" );
01181   case Scheduler::NoMethod:
01182     return i18n("Error: iMIP message with unknown method: '%1'").
01183       arg( msg->method() );
01184   }
01185   return QString::null;
01186 }
01187 
01188 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
01189 {
01190   // TODO: Several of the methods are not allowed for journals, so remove them.
01191   if ( !msg || !journal ) {
01192     return QString::null;
01193   }
01194 
01195   switch ( msg->method() ) {
01196   case Scheduler::Publish:
01197     return i18n("This journal has been published");
01198   case Scheduler::Request:
01199     return i18n( "You have been assigned this journal" );
01200   case Scheduler::Refresh:
01201     return i18n( "This journal was refreshed" );
01202   case Scheduler::Cancel:
01203     return i18n( "This journal was canceled" );
01204   case Scheduler::Add:
01205     return i18n( "Addition to the journal" );
01206   case Scheduler::Reply: {
01207     Attendee::List attendees = journal->attendees();
01208     if( attendees.count() == 0 ) {
01209       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01210       return QString::null;
01211     }
01212     if( attendees.count() != 1 ) {
01213       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01214                     << "but is " << attendees.count() << endl;
01215     }
01216     Attendee* attendee = *attendees.begin();
01217 
01218     switch( attendee->status() ) {
01219     case Attendee::NeedsAction:
01220       return i18n( "Sender indicates this journal assignment still needs some action" );
01221     case Attendee::Accepted:
01222       return i18n( "Sender accepts this journal" );
01223     case Attendee::Tentative:
01224       return i18n( "Sender tentatively accepts this journal" );
01225     case Attendee::Declined:
01226       return i18n( "Sender declines this journal" );
01227     case Attendee::Delegated:
01228       return i18n( "Sender has delegated this request for the journal" );
01229     case Attendee::Completed:
01230       return i18n( "The request for this journal is now completed" );
01231     case Attendee::InProcess:
01232       return i18n( "Sender is still processing the invitation" );
01233     default:
01234       return i18n( "Unknown response to this journal" );
01235     }
01236     break;
01237   }
01238   case Scheduler::Counter:
01239     return i18n( "Sender makes this counter proposal" );
01240   case Scheduler::Declinecounter:
01241     return i18n( "Sender declines the counter proposal" );
01242   case Scheduler::NoMethod:
01243     return i18n("Error: iMIP message with unknown method: '%1'").
01244       arg( msg->method() );
01245   }
01246   return QString::null;
01247 }
01248 
01249 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
01250 {
01251   if ( !msg || !fb ) {
01252     return QString::null;
01253   }
01254 
01255   switch ( msg->method() ) {
01256   case Scheduler::Publish:
01257     return i18n("This free/busy list has been published");
01258   case Scheduler::Request:
01259     return i18n( "The free/busy list has been requested" );
01260   case Scheduler::Refresh:
01261     return i18n( "This free/busy list was refreshed" );
01262   case Scheduler::Cancel:
01263     return i18n( "This free/busy list was canceled" );
01264   case Scheduler::Add:
01265     return i18n( "Addition to the free/busy list" );
01266   case Scheduler::NoMethod:
01267   default:
01268     return i18n("Error: Free/Busy iMIP message with unknown method: '%1'").
01269       arg( msg->method() );
01270   }
01271 }
01272 
01273 static QString invitationAttendees( Incidence *incidence )
01274 {
01275   QString tmpStr;
01276   if ( !incidence ) {
01277     return tmpStr;
01278   }
01279 
01280   tmpStr += eventViewerAddTag( "u", i18n( "Attendee List" ) );
01281   tmpStr += "<br/>";
01282 
01283   int count=0;
01284   Attendee::List attendees = incidence->attendees();
01285   if ( !attendees.isEmpty() ) {
01286 
01287     Attendee::List::ConstIterator it;
01288     for( it = attendees.begin(); it != attendees.end(); ++it ) {
01289       Attendee *a = *it;
01290       if ( !iamAttendee( a ) ) {
01291         count++;
01292         if ( count == 1 ) {
01293           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\" columns=\"2\">";
01294         }
01295         tmpStr += "<tr>";
01296         tmpStr += "<td>";
01297         tmpStr += invitationPerson( a->email(), a->name(), QString::null );
01298         if ( !a->delegator().isEmpty() ) {
01299           tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
01300         }
01301         if ( !a->delegate().isEmpty() ) {
01302           tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
01303         }
01304         tmpStr += "</td>";
01305         tmpStr += "<td>" + a->statusStr() + "</td>";
01306         tmpStr += "</tr>";
01307       }
01308     }
01309   }
01310   if ( count ) {
01311     tmpStr += "</table>";
01312   } else {
01313     tmpStr += "<i>" + i18n( "No attendee", "None" ) + "</i>";
01314   }
01315 
01316   return tmpStr;
01317 }
01318 
01319 class IncidenceFormatter::ScheduleMessageVisitor : public IncidenceBase::Visitor
01320 {
01321   public:
01322     ScheduleMessageVisitor() : mMessage(0) { mResult = ""; }
01323     bool act( IncidenceBase *incidence, ScheduleMessage *msg ) { mMessage = msg; return incidence->accept( *this ); }
01324     QString result() const { return mResult; }
01325 
01326   protected:
01327     QString mResult;
01328     ScheduleMessage *mMessage;
01329 };
01330 
01331 class IncidenceFormatter::InvitationHeaderVisitor :
01332       public IncidenceFormatter::ScheduleMessageVisitor
01333 {
01334   protected:
01335     bool visit( Event *event )
01336     {
01337       mResult = invitationHeaderEvent( event, mMessage );
01338       return !mResult.isEmpty();
01339     }
01340     bool visit( Todo *todo )
01341     {
01342       mResult = invitationHeaderTodo( todo, mMessage );
01343       return !mResult.isEmpty();
01344     }
01345     bool visit( Journal *journal )
01346     {
01347       mResult = invitationHeaderJournal( journal, mMessage );
01348       return !mResult.isEmpty();
01349     }
01350     bool visit( FreeBusy *fb )
01351     {
01352       mResult = invitationHeaderFreeBusy( fb, mMessage );
01353       return !mResult.isEmpty();
01354     }
01355 };
01356 
01357 class IncidenceFormatter::InvitationBodyVisitor :
01358       public IncidenceFormatter::ScheduleMessageVisitor
01359 {
01360   protected:
01361     bool visit( Event *event )
01362     {
01363       mResult = invitationDetailsEvent( event );
01364       return !mResult.isEmpty();
01365     }
01366     bool visit( Todo *todo )
01367     {
01368       mResult = invitationDetailsTodo( todo );
01369       return !mResult.isEmpty();
01370     }
01371     bool visit( Journal *journal )
01372     {
01373       mResult = invitationDetailsJournal( journal );
01374       return !mResult.isEmpty();
01375     }
01376     bool visit( FreeBusy *fb )
01377     {
01378       mResult = invitationDetailsFreeBusy( fb );
01379       return !mResult.isEmpty();
01380     }
01381 };
01382 
01383 class IncidenceFormatter::IncidenceCompareVisitor :
01384   public IncidenceBase::Visitor
01385 {
01386   public:
01387     IncidenceCompareVisitor() : mExistingIncidence(0) {}
01388     bool act( IncidenceBase *incidence, Incidence* existingIncidence )
01389     {
01390       Incidence *inc = dynamic_cast<Incidence*>( incidence );
01391       if ( !inc || !existingIncidence || inc->revision() <= existingIncidence->revision() )
01392         return false;
01393       mExistingIncidence = existingIncidence;
01394       return incidence->accept( *this );
01395     }
01396 
01397     QString result() const
01398     {
01399       if ( mChanges.isEmpty() )
01400         return QString();
01401       QString html = "<div align=\"left\"><ul><li>";
01402       html += mChanges.join( "</li><li>" );
01403       html += "</li><ul></div>";
01404       return html;
01405     }
01406 
01407   protected:
01408     bool visit( Event *event )
01409     {
01410       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
01411       compareIncidences( event, mExistingIncidence );
01412       return !mChanges.isEmpty();
01413     }
01414     bool visit( Todo *todo )
01415     {
01416       compareIncidences( todo, mExistingIncidence );
01417       return !mChanges.isEmpty();
01418     }
01419     bool visit( Journal *journal )
01420     {
01421       compareIncidences( journal, mExistingIncidence );
01422       return !mChanges.isEmpty();
01423     }
01424     bool visit( FreeBusy *fb )
01425     {
01426       Q_UNUSED( fb );
01427       return !mChanges.isEmpty();
01428     }
01429 
01430   private:
01431     void compareEvents( Event *newEvent, Event *oldEvent )
01432     {
01433       if ( !oldEvent || !newEvent )
01434         return;
01435       if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->doesFloat() != newEvent->doesFloat() )
01436         mChanges += i18n( "The invitation starting time has been changed from %1 to %2" )
01437             .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) );
01438       if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->doesFloat() != newEvent->doesFloat() )
01439         mChanges += i18n( "The invitation ending time has been changed from %1 to %2" )
01440             .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) );
01441     }
01442 
01443     void compareIncidences( Incidence *newInc, Incidence *oldInc )
01444     {
01445       if ( !oldInc || !newInc )
01446         return;
01447       if ( oldInc->summary() != newInc->summary() )
01448         mChanges += i18n( "The summary has been changed to: \"%1\"" ).arg( newInc->summary() );
01449       if ( oldInc->location() != newInc->location() )
01450         mChanges += i18n( "The location has been changed to: \"%1\"" ).arg( newInc->location() );
01451       if ( oldInc->description() != newInc->description() )
01452         mChanges += i18n( "The description has been changed to: \"%1\"" ).arg( newInc->description() );
01453       Attendee::List oldAttendees = oldInc->attendees();
01454       Attendee::List newAttendees = newInc->attendees();
01455       for ( Attendee::List::ConstIterator it = newAttendees.constBegin(); it != newAttendees.constEnd(); ++it ) {
01456         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
01457         if ( !oldAtt ) {
01458           mChanges += i18n( "Attendee %1 has been added" ).arg( (*it)->fullName() );
01459         } else {
01460           if ( oldAtt->status() != (*it)->status() )
01461             mChanges += i18n( "The status of attendee %1 has been changed to: %2" ).arg( (*it)->fullName() )
01462                 .arg( (*it)->statusStr() );
01463         }
01464       }
01465       for ( Attendee::List::ConstIterator it = oldAttendees.constBegin(); it != oldAttendees.constEnd(); ++it ) {
01466         Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
01467         if ( !newAtt )
01468           mChanges += i18n( "Attendee %1 has been removed" ).arg( (*it)->fullName() );
01469       }
01470     }
01471 
01472   private:
01473     Incidence* mExistingIncidence;
01474     QStringList mChanges;
01475 };
01476 
01477 
01478 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
01479 {
01480   QString res( "<a href=\"%1\"><b>%2</b></a>" );
01481   return res.arg( generateLinkURL( id ) ).arg( text );
01482   return res;
01483 }
01484 
01485 QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar,
01486     InvitationFormatterHelper *helper )
01487 {
01488   if ( invitation.isEmpty() ) return QString::null;
01489 
01490   ICalFormat format;
01491   // parseScheduleMessage takes the tz from the calendar, no need to set it manually here for the format!
01492   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
01493 
01494   if( !msg ) {
01495     kdDebug( 5850 ) << "Failed to parse the scheduling message" << endl;
01496     Q_ASSERT( format.exception() );
01497     kdDebug( 5850 ) << format.exception()->message() << endl;
01498     return QString::null;
01499   }
01500 
01501   IncidenceBase *incBase = msg->event();
01502 
01503   // Determine if this incidence is in my calendar
01504   Incidence *existingIncidence = 0;
01505   if ( incBase && helper->calendar() ) {
01506     existingIncidence = helper->calendar()->incidence( incBase->uid() );
01507     if ( !existingIncidence ) {
01508       const Incidence::List list = helper->calendar()->incidences();
01509       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
01510         if ( (*it)->schedulingID() == incBase->uid() ) {
01511           existingIncidence = *it;
01512           break;
01513         }
01514       }
01515     }
01516   }
01517 
01518   // First make the text of the message
01519   QString html;
01520 
01521   QString tableStyle = QString::fromLatin1(
01522     "style=\"border: solid 1px; margin: 0em;\"" );
01523   QString tableHead = QString::fromLatin1(
01524     "<div align=\"center\">"
01525     "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
01526     "<tr><td>").arg(tableStyle);
01527 
01528   html += tableHead;
01529   InvitationHeaderVisitor headerVisitor;
01530   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
01531   if ( !headerVisitor.act( incBase, msg ) )
01532     return QString::null;
01533   html += "<b>" + headerVisitor.result() + "</b>";
01534 
01535   InvitationBodyVisitor bodyVisitor;
01536   if ( !bodyVisitor.act( incBase, msg ) )
01537     return QString::null;
01538   html += bodyVisitor.result();
01539 
01540   if ( msg->method() == Scheduler::Request ) { // ### Scheduler::Publish/Refresh/Add as well?
01541     IncidenceCompareVisitor compareVisitor;
01542     if ( compareVisitor.act( incBase, existingIncidence ) ) {
01543       html += i18n("<p align=\"left\">The following changes have been made by the organizer:</p>");
01544       html += compareVisitor.result();
01545     }
01546   }
01547 
01548   Incidence *inc = dynamic_cast<Incidence*>( incBase );
01549 
01550   // determine if I am the organizer for this invitation
01551   bool myInc = iamOrganizer( inc );
01552 
01553   // determine if the invitation response has already been recorded
01554   bool rsvpRec = false;
01555   Attendee *ea = 0;
01556   if ( !myInc ) {
01557     if ( existingIncidence ) {
01558       ea = findMyAttendee( existingIncidence );
01559     }
01560     if ( ea && ( ea->status() == Attendee::Accepted || ea->status() == Attendee::Declined ) ) {
01561       rsvpRec = true;
01562     }
01563   }
01564 
01565   // Print if RSVP needed, not-needed, or response already recorded
01566   bool rsvpReq = rsvpRequested( inc );
01567   if ( !myInc ) {
01568     html += "<br/>";
01569     html += "<i><u>";
01570     if ( rsvpRec && ( inc && inc->revision() == 0 ) ) {
01571       html += i18n( "Your response has already been recorded [%1]" ).
01572               arg( ea->statusStr() );
01573       rsvpReq = false;
01574     } else {
01575       html += rsvpRequestedStr( rsvpReq );
01576     }
01577     html += "</u></i><br>";
01578   }
01579 
01580   html += "<table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td></tr><tr>";
01581 
01582   // Add groupware links
01583 
01584   switch ( msg->method() ) {
01585     case Scheduler::Publish:
01586     case Scheduler::Request:
01587     case Scheduler::Refresh:
01588     case Scheduler::Add:
01589     {
01590       if ( inc && inc->revision() > 0 && existingIncidence ) {
01591         if ( inc->type() == "Todo" ) {
01592           html += "<td colspan=\"9\">";
01593           html += helper->makeLink( "reply", i18n( "[Record invitation to my task list]" ) );
01594         } else {
01595           html += "<td colspan=\"13\">";
01596           html += helper->makeLink( "reply", i18n( "[Record invitation to my calendar]" ) );
01597         }
01598         html += "</td></tr><tr>";
01599       }
01600       html += "<td>";
01601 
01602       if ( !myInc ) {
01603         if ( rsvpReq ) {
01604           // Accept
01605           html += helper->makeLink( "accept", i18n( "[Accept]" ) );
01606           html += "</td><td> &nbsp; </td><td>";
01607           html += helper->makeLink( "accept_conditionally",
01608                                     i18n( "Accept conditionally", "[Accept cond.]" ) );
01609           html += "</td><td> &nbsp; </td><td>";
01610         }
01611 
01612         if ( rsvpReq ) {
01613           // Counter proposal
01614           html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
01615           html += "</td><td> &nbsp; </td><td>";
01616         }
01617 
01618         if ( rsvpReq ) {
01619           // Decline
01620           html += helper->makeLink( "decline", i18n( "[Decline]" ) );
01621           html += "</td><td> &nbsp; </td><td>";
01622         }
01623 
01624         if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
01625           // Delegate
01626           html += helper->makeLink( "delegate", i18n( "[Delegate]" ) );
01627           html += "</td><td> &nbsp; </td><td>";
01628 
01629           // Forward
01630           html += helper->makeLink( "forward", i18n( "[Forward]" ) );
01631 
01632           // Check calendar
01633           if ( inc->type() == "Event" ) {
01634             html += "</td><td> &nbsp; </td><td>";
01635             html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
01636           }
01637         }
01638       }
01639       break;
01640     }
01641 
01642     case Scheduler::Cancel:
01643       // Remove invitation
01644       if ( existingIncidence ) {
01645         if ( inc->type() == "Todo" ) {
01646           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my task list]" ) );
01647         } else {
01648           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my calendar]" ) );
01649         }
01650       }
01651       break;
01652 
01653     case Scheduler::Reply:
01654     {
01655       // Record invitation response
01656       Attendee *a = 0;
01657       Attendee *ea = 0;
01658       if ( inc ) {
01659         a = inc->attendees().first();
01660         if ( a && helper->calendar() ) {
01661           ea = findAttendee( existingIncidence, a->email() );
01662         }
01663       }
01664       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
01665         html += "<br/>";
01666         html += eventViewerAddTag( "i", i18n( "The response has already been recorded" ) );
01667       } else {
01668         if ( inc->type() == "Todo" ) {
01669           html += helper->makeLink( "reply", i18n( "[Record response into my task list]" ) );
01670         } else {
01671           html += helper->makeLink( "reply", i18n( "[Record response into my calendar]" ) );
01672         }
01673       }
01674       break;
01675     }
01676 
01677     case Scheduler::Counter:
01678       // Counter proposal
01679       html += helper->makeLink( "accept_counter", i18n("[Accept]") );
01680       html += "&nbsp;";
01681       html += helper->makeLink( "decline_counter", i18n("[Decline]") );
01682       html += "&nbsp;";
01683       html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
01684       break;
01685 
01686     case Scheduler::Declinecounter:
01687     case Scheduler::NoMethod:
01688       break;
01689   }
01690 
01691   // close the groupware table
01692   html += "</td></tr></table>";
01693 
01694   // Add the attendee list if I am the organizer
01695   if ( myInc && helper->calendar() ) {
01696     html += invitationAttendees( helper->calendar()->incidence( inc->uid() ) );
01697  }
01698 
01699   // close the top-level table
01700   html += "</td></tr></table><br></div>";
01701   return html;
01702 }
01703 
01704 
01705 
01706 
01707 /*******************************************************************
01708  *  Helper functions for the msTNEF -> VPart converter
01709  *******************************************************************/
01710 
01711 
01712 //-----------------------------------------------------------------------------
01713 
01714 static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
01715                            const QString& fallback = QString::null)
01716 {
01717   return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
01718                             fallback );
01719 }
01720 
01721 static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
01722                            const QString& fallback = QString::null )
01723 {
01724   return tnefMsg->findNamedProp( name, fallback );
01725 }
01726 
01727 struct save_tz { char* old_tz; char* tz_env_str; };
01728 
01729 /* temporarily go to a different timezone */
01730 static struct save_tz set_tz( const char* _tc )
01731 {
01732   const char *tc = _tc?_tc:"UTC";
01733 
01734   struct save_tz rv;
01735 
01736   rv.old_tz = 0;
01737   rv.tz_env_str = 0;
01738 
01739   //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;
01740 
01741   char* tz_env = 0;
01742   if( getenv( "TZ" ) ) {
01743     tz_env = strdup( getenv( "TZ" ) );
01744     rv.old_tz = tz_env;
01745   }
01746   char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
01747   strcpy( tmp_env, "TZ=" );
01748   strcpy( tmp_env+3, tc );
01749   putenv( tmp_env );
01750 
01751   rv.tz_env_str = tmp_env;
01752 
01753   /* tmp_env is not free'ed -- it is part of the environment */
01754 
01755   tzset();
01756   //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;
01757 
01758   return rv;
01759 }
01760 
01761 /* restore previous timezone */
01762 static void unset_tz( struct save_tz old_tz )
01763 {
01764   if( old_tz.old_tz ) {
01765     char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
01766     strcpy( tmp_env, "TZ=" );
01767     strcpy( tmp_env+3, old_tz.old_tz );
01768     putenv( tmp_env );
01769     /* tmp_env is not free'ed -- it is part of the environment */
01770     free( old_tz.old_tz );
01771   } else {
01772     /* clear TZ from env */
01773     putenv( strdup("TZ") );
01774   }
01775   tzset();
01776 
01777   /* is this OK? */
01778   if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
01779 }
01780 
01781 static QDateTime utc2Local( const QDateTime& utcdt )
01782 {
01783   struct tm tmL;
01784 
01785   save_tz tmp_tz = set_tz("UTC");
01786   time_t utc = utcdt.toTime_t();
01787   unset_tz( tmp_tz );
01788 
01789   localtime_r( &utc, &tmL );
01790   return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
01791                     QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
01792 }
01793 
01794 
01795 static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
01796                                           bool bDateOnly = false )
01797 {
01798   QDate tmpDate;
01799   QTime tmpTime;
01800   int year, month, day, hour, minute, second;
01801 
01802   if( bDateOnly ) {
01803     year = dtStr.left( 4 ).toInt();
01804     month = dtStr.mid( 4, 2 ).toInt();
01805     day = dtStr.mid( 6, 2 ).toInt();
01806     hour = 0;
01807     minute = 0;
01808     second = 0;
01809   } else {
01810     year = dtStr.left( 4 ).toInt();
01811     month = dtStr.mid( 4, 2 ).toInt();
01812     day = dtStr.mid( 6, 2 ).toInt();
01813     hour = dtStr.mid( 9, 2 ).toInt();
01814     minute = dtStr.mid( 11, 2 ).toInt();
01815     second = dtStr.mid( 13, 2 ).toInt();
01816   }
01817   tmpDate.setYMD( year, month, day );
01818   tmpTime.setHMS( hour, minute, second );
01819 
01820   if( tmpDate.isValid() && tmpTime.isValid() ) {
01821     QDateTime dT = QDateTime( tmpDate, tmpTime );
01822 
01823     if( !bDateOnly ) {
01824       // correct for GMT ( == Zulu time == UTC )
01825       if (dtStr.at(dtStr.length()-1) == 'Z') {
01826         //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
01827         //localUTCOffset( dT ) );
01828         dT = utc2Local( dT );
01829       }
01830     }
01831     return dT;
01832   } else
01833     return QDateTime();
01834 }
01835 
01836 
01837 
01838 QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef )
01839 {
01840   bool bOk = false;
01841 
01842   KTNEFParser parser;
01843   QBuffer buf( tnef );
01844   CalendarLocal cal ( QString::fromLatin1( "UTC" ) );
01845   KABC::Addressee addressee;
01846   KABC::VCardConverter cardConv;
01847   ICalFormat calFormat;
01848   Event* event = new Event();
01849 
01850   if( parser.openDevice( &buf ) ) {
01851     KTNEFMessage* tnefMsg = parser.message();
01852     //QMap<int,KTNEFProperty*> props = parser.message()->properties();
01853 
01854     // Everything depends from property PR_MESSAGE_CLASS
01855     // (this is added by KTNEFParser):
01856     QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
01857       .upper();
01858     if( !msgClass.isEmpty() ) {
01859       // Match the old class names that might be used by Outlook for
01860       // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
01861       bool bCompatClassAppointment = false;
01862       bool bCompatMethodRequest = false;
01863       bool bCompatMethodCancled = false;
01864       bool bCompatMethodAccepted = false;
01865       bool bCompatMethodAcceptedCond = false;
01866       bool bCompatMethodDeclined = false;
01867       if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
01868         bCompatClassAppointment = true;
01869         if( msgClass.endsWith( ".MTGREQ" ) )
01870           bCompatMethodRequest = true;
01871         if( msgClass.endsWith( ".MTGCNCL" ) )
01872           bCompatMethodCancled = true;
01873         if( msgClass.endsWith( ".MTGRESPP" ) )
01874           bCompatMethodAccepted = true;
01875         if( msgClass.endsWith( ".MTGRESPA" ) )
01876           bCompatMethodAcceptedCond = true;
01877         if( msgClass.endsWith( ".MTGRESPN" ) )
01878           bCompatMethodDeclined = true;
01879       }
01880       bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );
01881 
01882       if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
01883         // Compose a vCal
01884         bool bIsReply = false;
01885         QString prodID = "-//Microsoft Corporation//Outlook ";
01886         prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
01887         prodID += "MIMEDIR/EN\n";
01888         prodID += "VERSION:2.0\n";
01889         calFormat.setApplication( "Outlook", prodID );
01890 
01891         Scheduler::Method method;
01892         if( bCompatMethodRequest )
01893           method = Scheduler::Request;
01894         else if( bCompatMethodCancled )
01895           method = Scheduler::Cancel;
01896         else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
01897                  bCompatMethodDeclined ) {
01898           method = Scheduler::Reply;
01899           bIsReply = true;
01900         } else {
01901           // pending(khz): verify whether "0x0c17" is the right tag ???
01902           //
01903           // at the moment we think there are REQUESTS and UPDATES
01904           //
01905           // but WHAT ABOUT REPLIES ???
01906           //
01907           //
01908 
01909           if( tnefMsg->findProp(0x0c17) == "1" )
01910             bIsReply = true;
01911           method = Scheduler::Request;
01912         }
01913 
01915         ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );
01916 
01917         QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );
01918 
01919         if( !sSenderSearchKeyEmail.isEmpty() ) {
01920           int colon = sSenderSearchKeyEmail.find( ':' );
01921           // May be e.g. "SMTP:KHZ@KDE.ORG"
01922           if( sSenderSearchKeyEmail.find( ':' ) == -1 )
01923             sSenderSearchKeyEmail.remove( 0, colon+1 );
01924         }
01925 
01926         QString s( tnefMsg->findProp( 0x0e04 ) );
01927         QStringList attendees = QStringList::split( ';', s );
01928         if( attendees.count() ) {
01929           for( QStringList::Iterator it = attendees.begin();
01930                it != attendees.end(); ++it ) {
01931             // Skip all entries that have no '@' since these are
01932             // no mail addresses
01933             if( (*it).find('@') == -1 ) {
01934               s = (*it).stripWhiteSpace();
01935 
01936               Attendee *attendee = new Attendee( s, s, true );
01937               if( bIsReply ) {
01938                 if( bCompatMethodAccepted )
01939                   attendee->setStatus( Attendee::Accepted );
01940                 if( bCompatMethodDeclined )
01941                   attendee->setStatus( Attendee::Declined );
01942                 if( bCompatMethodAcceptedCond )
01943                   attendee->setStatus(Attendee::Tentative);
01944               } else {
01945                 attendee->setStatus( Attendee::NeedsAction );
01946                 attendee->setRole( Attendee::ReqParticipant );
01947               }
01948               event->addAttendee(attendee);
01949             }
01950           }
01951         } else {
01952           // Oops, no attendees?
01953           // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
01954           s = sSenderSearchKeyEmail;
01955           if( !s.isEmpty() ) {
01956             Attendee *attendee = new Attendee( QString::null, QString::null,
01957                                                true );
01958             if( bIsReply ) {
01959               if( bCompatMethodAccepted )
01960                 attendee->setStatus( Attendee::Accepted );
01961               if( bCompatMethodAcceptedCond )
01962                 attendee->setStatus( Attendee::Declined );
01963               if( bCompatMethodDeclined )
01964                 attendee->setStatus( Attendee::Tentative );
01965             } else {
01966               attendee->setStatus(Attendee::NeedsAction);
01967               attendee->setRole(Attendee::ReqParticipant);
01968             }
01969             event->addAttendee(attendee);
01970           }
01971         }
01972         s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
01973         if( s.isEmpty() && !bIsReply )
01974           s = sSenderSearchKeyEmail;
01975         // TODO: Use the common name?
01976         if( !s.isEmpty() )
01977           event->setOrganizer( s );
01978 
01979         s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
01980           .replace( QChar( ':' ), QString::null );
01981         event->setDtStart( QDateTime::fromString( s ) ); // ## Format??
01982 
01983         s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
01984           .replace( QChar( ':' ), QString::null );
01985         event->setDtEnd( QDateTime::fromString( s ) );
01986 
01987         s = tnefMsg->findProp( 0x8208 );
01988         event->setLocation( s );
01989 
01990         // is it OK to set this to OPAQUE always ??
01991         //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
01992         //vPart += "SEQUENCE:0\n";
01993 
01994         // is "0x0023" OK  -  or should we look for "0x0003" ??
01995         s = tnefMsg->findProp( 0x0023 );
01996         event->setUid( s );
01997 
01998         // PENDING(khz): is this value in local timezone? Must it be
01999         // adjusted? Most likely this is a bug in the server or in
02000         // Outlook - we ignore it for now.
02001         s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
02002           .replace( QChar( ':' ), QString::null );
02003         // ### libkcal always uses currentDateTime()
02004         // event->setDtStamp(QDateTime::fromString(s));
02005 
02006         s = tnefMsg->findNamedProp( "Keywords" );
02007         event->setCategories( s );
02008 
02009         s = tnefMsg->findProp( 0x1000 );
02010         event->setDescription( s );
02011 
02012         s = tnefMsg->findProp( 0x0070 );
02013         event->setSummary( s );
02014 
02015         s = tnefMsg->findProp( 0x0026 );
02016         event->setPriority( s.toInt() );
02017 
02018         // is reminder flag set ?
02019         if(!tnefMsg->findProp(0x8503).isEmpty()) {
02020           Alarm *alarm = new Alarm(event);
02021           QDateTime highNoonTime =
02022             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
02023                                      .replace( QChar( '-' ), "" )
02024                                      .replace( QChar( ':' ), "" ) );
02025           QDateTime wakeMeUpTime =
02026             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
02027                                      .replace( QChar( '-' ), "" )
02028                                      .replace( QChar( ':' ), "" ) );
02029           alarm->setTime(wakeMeUpTime);
02030 
02031           if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
02032             alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
02033           else
02034             // default: wake them up 15 minutes before the appointment
02035             alarm->setStartOffset( Duration( 15*60 ) );
02036           alarm->setDisplayAlarm( i18n( "Reminder" ) );
02037 
02038           // Sorry: the different action types are not known (yet)
02039           //        so we always set 'DISPLAY' (no sounds, no images...)
02040           event->addAlarm( alarm );
02041         }
02042         cal.addEvent( event );
02043         bOk = true;
02044         // we finished composing a vCal
02045       } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
02046         addressee.setUid( stringProp( tnefMsg, attMSGID ) );
02047         addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) );
02048         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
02049         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
02050         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
02051         addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) );
02052         addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) );
02053         addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
02054         addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
02055         addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) );
02056         addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) );
02057         addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) );
02058 
02059         QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
02060           .replace( QChar( '-' ), QString::null )
02061           .replace( QChar( ':' ), QString::null );
02062         if( !s.isEmpty() )
02063           addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );
02064 
02065         addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );
02066 
02067         // collect parts of Name entry
02068         addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
02069         addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
02070         addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) );
02071         addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) );
02072         addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );
02073 
02074         addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
02075         addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
02076         addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
02077         /*
02078         the MAPI property ID of this (multiline) )field is unknown:
02079         vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
02080         */
02081 
02082         KABC::Address adr;
02083         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) );
02084         adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
02085         adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
02086         adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
02087         adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) );
02088         adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
02089         adr.setType(KABC::Address::Home);
02090         addressee.insertAddress(adr);
02091 
02092         adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
02093         adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) );
02094         adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) );
02095         adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) );
02096         adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
02097         adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) );
02098         adr.setType( KABC::Address::Work );
02099         addressee.insertAddress( adr );
02100 
02101         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) );
02102         adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
02103         adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
02104         adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
02105         adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) );
02106         adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
02107         adr.setType( KABC::Address::Dom );
02108         addressee.insertAddress(adr);
02109 
02110         // problem: the 'other' address was stored by KOrganizer in
02111         //          a line looking like the following one:
02112         // 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
02113 
02114         QString nr;
02115         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
02116         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) );
02117         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
02118         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) );
02119         nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
02120         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) );
02121         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
02122         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) );
02123         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
02124         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) );
02125 
02126         s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
02127           .replace( QChar( '-' ), QString::null )
02128           .replace( QChar( ':' ), QString::null );
02129         if( !s.isEmpty() )
02130           addressee.setBirthday( QDateTime::fromString( s ) );
02131 
02132         bOk = ( !addressee.isEmpty() );
02133       } else if( "IPM.NOTE" == msgClass ) {
02134 
02135       } // else if ... and so on ...
02136     }
02137   }
02138 
02139   // Compose return string
02140   QString iCal = calFormat.toString( &cal );
02141   if( !iCal.isEmpty() )
02142     // This was an iCal
02143     return iCal;
02144 
02145   // Not an iCal - try a vCard
02146   KABC::VCardConverter converter;
02147   return converter.createVCard( addressee );
02148 }
02149 
02150 
02151 QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef,
02152         Calendar *mCalendar, InvitationFormatterHelper *helper )
02153 {
02154   QString vPart = IncidenceFormatter::msTNEFToVPart( tnef );
02155   QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper );
02156   if( !iCal.isEmpty() )
02157     return iCal;
02158   return vPart;
02159 }
02160 
02161 
02162 
02163 
02164 /*******************************************************************
02165  *  Helper functions for the Incidence tooltips
02166  *******************************************************************/
02167 
02168 class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
02169 {
02170   public:
02171     ToolTipVisitor() : mRichText( true ), mResult( "" ) {}
02172 
02173     bool act( IncidenceBase *incidence, bool richText=true)
02174     {
02175       mRichText = richText;
02176       mResult = "";
02177       return incidence ? incidence->accept( *this ) : false;
02178     }
02179     QString result() const { return mResult; }
02180 
02181   protected:
02182     bool visit( Event *event );
02183     bool visit( Todo *todo );
02184     bool visit( Journal *journal );
02185     bool visit( FreeBusy *fb );
02186 
02187     QString dateRangeText( Event*event );
02188     QString dateRangeText( Todo *todo );
02189     QString dateRangeText( Journal *journal );
02190     QString dateRangeText( FreeBusy *fb );
02191 
02192     QString generateToolTip( Incidence* incidence, QString dtRangeText );
02193 
02194   protected:
02195     bool mRichText;
02196     QString mResult;
02197 };
02198 
02199 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event*event )
02200 {
02201   QString ret;
02202   QString tmp;
02203   if ( event->isMultiDay() ) {
02204 
02205     tmp = "<br>" + i18n("Event start", "<i>From:</i>&nbsp;%1");
02206     if (event->doesFloat())
02207       ret += tmp.arg( event->dtStartDateStr().replace(" ", "&nbsp;") );
02208     else
02209       ret += tmp.arg( event->dtStartStr().replace(" ", "&nbsp;") );
02210 
02211     tmp = "<br>" + i18n("Event end","<i>To:</i>&nbsp;%1");
02212     if (event->doesFloat())
02213       ret += tmp.arg( event->dtEndDateStr().replace(" ", "&nbsp;") );
02214     else
02215       ret += tmp.arg( event->dtEndStr().replace(" ", "&nbsp;") );
02216 
02217   } else {
02218 
02219     ret += "<br>"+i18n("<i>Date:</i>&nbsp;%1").
02220         arg( event->dtStartDateStr().replace(" ", "&nbsp;") );
02221     if ( !event->doesFloat() ) {
02222       const QString dtStartTime = event->dtStartTimeStr().replace( " ", "&nbsp;" );
02223       const QString dtEndTime = event->dtEndTimeStr().replace( " ", "&nbsp;" );
02224       if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00'
02225         tmp = "<br>" + i18n("time for event, &nbsp; to prevent ugly line breaks",
02226         "<i>Time:</i>&nbsp;%1").
02227         arg( dtStartTime );
02228       } else {
02229         tmp = "<br>" + i18n("time range for event, &nbsp; to prevent ugly line breaks",
02230         "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2").
02231         arg( dtStartTime, dtEndTime );
02232       }
02233       ret += tmp;
02234     }
02235 
02236   }
02237   return ret;
02238 }
02239 
02240 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo*todo )
02241 {
02242   QString ret;
02243   bool floats( todo->doesFloat() );
02244   if (todo->hasStartDate())
02245     // No need to add <i> here. This is separated issue and each line
02246     // is very visible on its own. On the other hand... Yes, I like it
02247     // italics here :)
02248     ret += "<br>" + i18n("<i>Start:</i>&nbsp;%1").arg(
02249       (floats)
02250         ?(todo->dtStartDateStr().replace(" ", "&nbsp;"))
02251         :(todo->dtStartStr().replace(" ", "&nbsp;")) ) ;
02252   if (todo->hasDueDate())
02253     ret += "<br>" + i18n("<i>Due:</i>&nbsp;%1").arg(
02254       (floats)
02255         ?(todo->dtDueDateStr().replace(" ", "&nbsp;"))
02256         :(todo->dtDueStr().replace(" ", "&nbsp;")) );
02257   if (todo->isCompleted())
02258     ret += "<br>" + i18n("<i>Completed:</i>&nbsp;%1").arg( todo->completedStr().replace(" ", "&nbsp;") );
02259   else
02260     ret += "<br>" + i18n("%1 % completed").arg(todo->percentComplete());
02261 
02262   return ret;
02263 }
02264 
02265 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal )
02266 {
02267   QString ret;
02268   if (journal->dtStart().isValid() ) {
02269     ret += "<br>" + i18n("<i>Date:</i>&nbsp;%1").arg( journal->dtStartDateStr( false ) );
02270   }
02271   return ret;
02272 }
02273 
02274 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
02275 {
02276   QString tmp( "<br>" + i18n("<i>Period start:</i>&nbsp;%1") );
02277   QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) );
02278   tmp = "<br>" + i18n("<i>Period start:</i>&nbsp;%1");
02279   ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) );
02280   return ret;
02281 }
02282 
02283 
02284 
02285 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
02286 {
02287   mResult = generateToolTip( event, dateRangeText( event ) );
02288   return !mResult.isEmpty();
02289 }
02290 
02291 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
02292 {
02293   mResult = generateToolTip( todo, dateRangeText( todo ) );
02294   return !mResult.isEmpty();
02295 }
02296 
02297 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
02298 {
02299   mResult = generateToolTip( journal, dateRangeText( journal ) );
02300   return !mResult.isEmpty();
02301 }
02302 
02303 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
02304 {
02305   mResult = "<qt><b>" + i18n("Free/Busy information for %1")
02306         .arg(fb->organizer().fullName()) + "</b>";
02307   mResult += dateRangeText( fb );
02308   mResult += "</qt>";
02309   return !mResult.isEmpty();
02310 }
02311 
02312 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence* incidence, QString dtRangeText )
02313 {
02314   if ( !incidence )
02315     return QString::null;
02316 
02317   QString tmp = "<qt><b>"+ incidence->summary().replace("\n", "<br>")+"</b>";
02318 
02319   tmp += dtRangeText;
02320 
02321   if (!incidence->location().isEmpty()) {
02322     // Put Location: in italics
02323     tmp += "<br>"+i18n("<i>Location:</i>&nbsp;%1").
02324       arg( incidence->location().replace("\n", "<br>") );
02325   }
02326   if (!incidence->description().isEmpty()) {
02327     QString desc(incidence->description());
02328     if (desc.length()>120) {
02329       desc = desc.left(120) + "...";
02330     }
02331     tmp += "<br>----------<br>" + i18n("<i>Description:</i><br>") + desc.replace("\n", "<br>");
02332   }
02333   tmp += "</qt>";
02334   return tmp;
02335 }
02336 
02337 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
02338 {
02339   ToolTipVisitor v;
02340   if ( v.act( incidence, richText ) ) {
02341     return v.result();
02342   } else
02343     return QString::null;
02344 }
02345 
02346 
02347 
02348 
02349 /*******************************************************************
02350  *  Helper functions for the Incidence tooltips
02351  *******************************************************************/
02352 
02353 class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
02354 {
02355   public:
02356     MailBodyVisitor() : mResult( "" ) {}
02357 
02358     bool act( IncidenceBase *incidence )
02359     {
02360       mResult = "";
02361       return incidence ? incidence->accept( *this ) : false;
02362     }
02363     QString result() const { return mResult; }
02364 
02365   protected:
02366     bool visit( Event *event );
02367     bool visit( Todo *todo );
02368     bool visit( Journal *journal );
02369     bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); }
02370   protected:
02371     QString mResult;
02372 };
02373 
02374 
02375 static QString mailBodyIncidence( Incidence *incidence )
02376 {
02377   QString body;
02378   if ( !incidence->summary().isEmpty() ) {
02379     body += i18n("Summary: %1\n").arg( incidence->summary() );
02380   }
02381   if ( !incidence->organizer().isEmpty() ) {
02382     body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() );
02383   }
02384   if ( !incidence->location().isEmpty() ) {
02385     body += i18n("Location: %1\n").arg( incidence->location() );
02386   }
02387   return body;
02388 }
02389 
02390 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
02391 {
02392   QString recurrence[]= {i18n("no recurrence", "None"),
02393     i18n("Minutely"), i18n("Hourly"), i18n("Daily"),
02394     i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"),
02395     i18n("Yearly"), i18n("Yearly"), i18n("Yearly")};
02396 
02397   mResult = mailBodyIncidence( event );
02398   mResult += i18n("Start Date: %1\n").arg( event->dtStartDateStr() );
02399   if ( !event->doesFloat() ) {
02400     mResult += i18n("Start Time: %1\n").arg( event->dtStartTimeStr() );
02401   }
02402   if ( event->dtStart() != event->dtEnd() ) {
02403     mResult += i18n("End Date: %1\n").arg( event->dtEndDateStr() );
02404   }
02405   if ( !event->doesFloat() ) {
02406     mResult += i18n("End Time: %1\n").arg( event->dtEndTimeStr() );
02407   }
02408   if ( event->doesRecur() ) {
02409     Recurrence *recur = event->recurrence();
02410     // TODO: Merge these two to one of the form "Recurs every 3 days"
02411     mResult += i18n("Recurs: %1\n")
02412              .arg( recurrence[ recur->recurrenceType() ] );
02413     mResult += i18n("Frequency: %1\n")
02414              .arg( event->recurrence()->frequency() );
02415 
02416     if ( recur->duration() > 0 ) {
02417       mResult += i18n ("Repeats once", "Repeats %n times", recur->duration());
02418       mResult += '\n';
02419     } else {
02420       if ( recur->duration() != -1 ) {
02421 // TODO_Recurrence: What to do with floating
02422         QString endstr;
02423         if ( event->doesFloat() ) {
02424           endstr = KGlobal::locale()->formatDate( recur->endDate() );
02425         } else {
02426           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() );
02427         }
02428         mResult += i18n("Repeat until: %1\n").arg( endstr );
02429       } else {
02430         mResult += i18n("Repeats forever\n");
02431       }
02432     }
02433   }
02434   QString details = event->description();
02435   if ( !details.isEmpty() ) {
02436     mResult += i18n("Details:\n%1\n").arg( details );
02437   }
02438   return !mResult.isEmpty();
02439 }
02440 
02441 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
02442 {
02443   mResult = mailBodyIncidence( todo );
02444 
02445   if ( todo->hasStartDate() ) {
02446     mResult += i18n("Start Date: %1\n").arg( todo->dtStartDateStr() );
02447     if ( !todo->doesFloat() ) {
02448       mResult += i18n("Start Time: %1\n").arg( todo->dtStartTimeStr() );
02449     }
02450   }
02451   if ( todo->hasDueDate() ) {
02452     mResult += i18n("Due Date: %1\n").arg( todo->dtDueDateStr() );
02453     if ( !todo->doesFloat() ) {
02454       mResult += i18n("Due Time: %1\n").arg( todo->dtDueTimeStr() );
02455     }
02456   }
02457   QString details = todo->description();
02458   if ( !details.isEmpty() ) {
02459     mResult += i18n("Details:\n%1\n").arg( details );
02460   }
02461   return !mResult.isEmpty();
02462 }
02463 
02464 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
02465 {
02466   mResult = mailBodyIncidence( journal );
02467   mResult += i18n("Date: %1\n").arg( journal->dtStartDateStr() );
02468   if ( !journal->doesFloat() ) {
02469     mResult += i18n("Time: %1\n").arg( journal->dtStartTimeStr() );
02470   }
02471   if ( !journal->description().isEmpty() )
02472     mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() );
02473   return !mResult.isEmpty();
02474 }
02475 
02476 
02477 
02478 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
02479 {
02480   if ( !incidence )
02481     return QString::null;
02482 
02483   MailBodyVisitor v;
02484   if ( v.act( incidence ) ) {
02485     return v.result();
02486   }
02487   return QString::null;
02488 }
02489 
02490 QString IncidenceFormatter::recurrenceString(Incidence * incidence)
02491 {
02492   if ( !incidence->doesRecur() )
02493     return i18n( "No recurrence" );
02494 
02495      // recurrence
02496   QStringList dayList;
02497   dayList.append( i18n( "31st Last" ) );
02498   dayList.append( i18n( "30th Last" ) );
02499   dayList.append( i18n( "29th Last" ) );
02500   dayList.append( i18n( "28th Last" ) );
02501   dayList.append( i18n( "27th Last" ) );
02502   dayList.append( i18n( "26th Last" ) );
02503   dayList.append( i18n( "25th Last" ) );
02504   dayList.append( i18n( "24th Last" ) );
02505   dayList.append( i18n( "23rd Last" ) );
02506   dayList.append( i18n( "22nd Last" ) );
02507   dayList.append( i18n( "21st Last" ) );
02508   dayList.append( i18n( "20th Last" ) );
02509   dayList.append( i18n( "19th Last" ) );
02510   dayList.append( i18n( "18th Last" ) );
02511   dayList.append( i18n( "17th Last" ) );
02512   dayList.append( i18n( "16th Last" ) );
02513   dayList.append( i18n( "15th Last" ) );
02514   dayList.append( i18n( "14th Last" ) );
02515   dayList.append( i18n( "13th Last" ) );
02516   dayList.append( i18n( "12th Last" ) );
02517   dayList.append( i18n( "11th Last" ) );
02518   dayList.append( i18n( "10th Last" ) );
02519   dayList.append( i18n( "9th Last" ) );
02520   dayList.append( i18n( "8th Last" ) );
02521   dayList.append( i18n( "7th Last" ) );
02522   dayList.append( i18n( "6th Last" ) );
02523   dayList.append( i18n( "5th Last" ) );
02524   dayList.append( i18n( "4th Last" ) );
02525   dayList.append( i18n( "3rd Last" ) );
02526   dayList.append( i18n( "2nd Last" ) );
02527   dayList.append( i18n( "last day of the month", "Last" ) );
02528   dayList.append( i18n( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
02529   dayList.append( i18n( "1st" ) );
02530   dayList.append( i18n( "2nd" ) );
02531   dayList.append( i18n( "3rd" ) );
02532   dayList.append( i18n( "4th" ) );
02533   dayList.append( i18n( "5th" ) );
02534 
02535   QString recurString;
02536   const KCalendarSystem *calSys = KGlobal::locale()->calendar();;
02537 
02538   Recurrence *recurs = incidence->recurrence();
02539   switch ( recurs->recurrenceType() ) {
02540 
02541       case Recurrence::rNone:
02542           recurString = i18n( "no recurrence", "None" );
02543           break;
02544       case Recurrence::rDaily:
02545           recurString = i18n( "Every day", "Every %1 days", recurs->frequency() );
02546           break;
02547       case Recurrence::rWeekly:
02548       {
02549           QString dayNames;
02550           // Respect start of week setting
02551           int weekStart = KGlobal::locale()->weekStartDay();
02552           bool addSpace = false;
02553           for ( int i = 0; i < 7; ++i ) {
02554               if ( recurs->days().testBit( (i+weekStart+6)%7 )) {
02555                   if (addSpace) dayNames.append(" ");
02556                   dayNames.append( calSys->weekDayName( ((i+weekStart+6)%7)+1, true ) );
02557                   addSpace=true;
02558               }
02559           }
02560           recurString = i18n( "Every week on %1",
02561                               "Every %n weeks on %1",
02562                               recurs->frequency()).arg( dayNames );
02563           break;
02564       }
02565       case Recurrence::rMonthlyPos:
02566       {
02567           KCal::RecurrenceRule::WDayPos rule = recurs->monthPositions()[0];
02568           recurString = i18n( "Every month on the %1 %2",
02569                               "Every %n months on the %1 %2",
02570                               recurs->frequency() ).arg(dayList[rule.pos() + 31]).arg(
02571                                       calSys->weekDayName( rule.day(),false ) );
02572           break;
02573       }
02574       case Recurrence::rMonthlyDay:
02575       {
02576           int days = recurs->monthDays()[0];
02577           if (days < 0) {
02578               recurString = i18n( "Every month on the %1 day",
02579                                   "Every %n months on the %1 day",
02580                                   recurs->frequency() ).arg( dayList[days + 31] );
02581           } else {
02582               recurString = i18n( "Every month on day %1",
02583                                   "Every %n months on day %1",
02584                                   recurs->frequency() ).arg( recurs->monthDays()[0] );
02585           }
02586           break;
02587       }
02588 
02589       case Recurrence::rYearlyMonth:
02590       {
02591           recurString = i18n( "Every year on day %1 of %2",
02592                               "Every %n years on day %1 of %2",
02593                               recurs->frequency() )
02594                   .arg(recurs->yearDates()[0])
02595                   .arg(calSys->monthName( recurs->yearMonths()[0], recurs->startDate().year() ) );
02596           break;
02597       }
02598       case Recurrence::rYearlyPos:
02599       {
02600           KCal::RecurrenceRule::WDayPos rule = recurs->yearPositions()[0];
02601           recurString = i18n( "Every year on the %1 %2 of %3",
02602                               "Every %n years on the %1 %2of %3",
02603                               recurs->frequency()).arg( dayList[rule.pos() + 31] )
02604                   .arg( calSys->weekDayName( rule.day(), false ))
02605                   .arg( calSys->monthName( recurs->yearMonths()[0], recurs->startDate().year() ) );
02606           break;
02607       }
02608       case Recurrence::rYearlyDay:
02609       {
02610           recurString = i18n( "Every year on day %1",
02611                               "Every %n years on day %1",
02612                               recurs->frequency()).arg( recurs->yearDays()[0] );
02613           break;
02614       }
02615 
02616       default:
02617           return i18n( "Incidence recurs" );
02618   }
02619 
02620   return recurString;
02621 }
KDE Home | KDE Accessibility Home | Description of Access Keys