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