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     Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
00007 
00008     This library is free software; you can redistribute it and/or
00009     modify it under the terms of the GNU Library General Public
00010     License as published by the Free Software Foundation; either
00011     version 2 of the License, or (at your option) any later version.
00012 
00013     This library is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016     Library General Public License for more details.
00017 
00018     You should have received a copy of the GNU Library General Public License
00019     along with this library; see the file COPYING.LIB.  If not, write to
00020     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00021     Boston, MA 02110-1301, USA.
00022 */
00023 
00024 #include "incidenceformatter.h"
00025 
00026 #include <libkcal/attachment.h>
00027 #include <libkcal/event.h>
00028 #include <libkcal/todo.h>
00029 #include <libkcal/journal.h>
00030 #include <libkcal/calendar.h>
00031 #include <libkcal/calendarlocal.h>
00032 #include <libkcal/icalformat.h>
00033 #include <libkcal/freebusy.h>
00034 #include <libkcal/calendarresources.h>
00035 
00036 #include <libemailfunctions/email.h>
00037 
00038 #include <ktnef/ktnefparser.h>
00039 #include <ktnef/ktnefmessage.h>
00040 #include <ktnef/ktnefdefs.h>
00041 #include <kabc/phonenumber.h>
00042 #include <kabc/vcardconverter.h>
00043 #include <kabc/stdaddressbook.h>
00044 
00045 #include <kapplication.h>
00046 #include <kemailsettings.h>
00047 
00048 #include <klocale.h>
00049 #include <kglobal.h>
00050 #include <kiconloader.h>
00051 #include <kcalendarsystem.h>
00052 #include <kmimetype.h>
00053 
00054 #include <qbuffer.h>
00055 #include <qstylesheet.h>
00056 #include <qdatetime.h>
00057 #include <qregexp.h>
00058 
00059 #include <time.h>
00060 
00061 using namespace KCal;
00062 
00063 /*******************
00064  *  General helpers
00065  *******************/
00066 
00067 static QString htmlAddLink( const QString &ref, const QString &text,
00068                              bool newline = true )
00069 {
00070   QString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" );
00071   if ( newline ) tmpStr += "\n";
00072   return tmpStr;
00073 }
00074 
00075 static QString htmlAddTag( const QString & tag, const QString & text )
00076 {
00077   int numLineBreaks = text.contains( "\n" );
00078   QString str = "<" + tag + ">";
00079   QString tmpText = text;
00080   QString tmpStr = str;
00081   if( numLineBreaks >= 0 ) {
00082     if ( numLineBreaks > 0) {
00083       int pos = 0;
00084       QString tmp;
00085       for( int i = 0; i <= numLineBreaks; i++ ) {
00086         pos = tmpText.find( "\n" );
00087         tmp = tmpText.left( pos );
00088         tmpText = tmpText.right( tmpText.length() - pos - 1 );
00089         tmpStr += tmp + "<br>";
00090       }
00091     } else {
00092       tmpStr += tmpText;
00093     }
00094   }
00095   tmpStr += "</" + tag + ">";
00096   return tmpStr;
00097 }
00098 
00099 static bool iamAttendee( Attendee *attendee )
00100 {
00101   // Check if I'm this attendee
00102 
00103   bool iam = false;
00104   KEMailSettings settings;
00105   QStringList profiles = settings.profiles();
00106   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
00107     settings.setProfile( *it );
00108     if ( settings.getSetting( KEMailSettings::EmailAddress ) == attendee->email() ) {
00109       iam = true;
00110       break;
00111     }
00112   }
00113   return iam;
00114 }
00115 
00116 static bool iamOrganizer( Incidence *incidence )
00117 {
00118   // Check if I'm the organizer for this incidence
00119 
00120   if ( !incidence ) {
00121     return false;
00122   }
00123 
00124   bool iam = false;
00125   KEMailSettings settings;
00126   QStringList profiles = settings.profiles();
00127   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
00128     settings.setProfile( *it );
00129     if ( settings.getSetting( KEMailSettings::EmailAddress ) == incidence->organizer().email() ) {
00130       iam = true;
00131       break;
00132     }
00133   }
00134   return iam;
00135 }
00136 
00137 static QString firstAttendeeName( Incidence *incidence, const QString &defName )
00138 {
00139   QString name;
00140   if ( !incidence ) {
00141     return name;
00142   }
00143 
00144   Attendee::List attendees = incidence->attendees();
00145   if( attendees.count() > 0 ) {
00146     Attendee *attendee = *attendees.begin();
00147     name = attendee->name();
00148     if ( name.isEmpty() ) {
00149       name = attendee->email();
00150     }
00151     if ( name.isEmpty() ) {
00152       name = defName;
00153     }
00154   }
00155   return name;
00156 }
00157 
00158 /*******************************************************************
00159  *  Helper functions for the extensive display (display viewer)
00160  *******************************************************************/
00161 
00162 static QString displayViewLinkPerson( const QString& email, QString name, QString uid )
00163 {
00164   // Make the search, if there is an email address to search on,
00165   // and either name or uid is missing
00166   if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
00167     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
00168     KABC::Addressee::List addressList = add_book->findByEmail( email );
00169     if ( !addressList.isEmpty() ) {
00170       KABC::Addressee o = addressList.first();
00171       if ( !o.isEmpty() && addressList.size() < 2 ) {
00172         if ( name.isEmpty() ) {
00173           // No name set, so use the one from the addressbook
00174           name = o.formattedName();
00175         }
00176         uid = o.uid();
00177       } else {
00178         // Email not found in the addressbook. Don't make a link
00179         uid = QString::null;
00180       }
00181     }
00182   }
00183 
00184   // Show the attendee
00185   QString tmpString;
00186   if ( !uid.isEmpty() ) {
00187     // There is a UID, so make a link to the addressbook
00188     if ( name.isEmpty() ) {
00189       // Use the email address for text
00190       tmpString += htmlAddLink( "uid:" + uid, email );
00191     } else {
00192       tmpString += htmlAddLink( "uid:" + uid, name );
00193     }
00194   } else {
00195     // No UID, just show some text
00196     tmpString += ( name.isEmpty() ? email : name );
00197   }
00198 
00199   // Make the mailto link
00200   if ( !email.isEmpty() ) {
00201     KURL mailto;
00202     mailto.setProtocol( "mailto" );
00203     mailto.setPath( email );
00204     const QString iconPath =
00205       KGlobal::iconLoader()->iconPath( "mail_new", KIcon::Small );
00206     tmpString += "&nbsp;" +
00207                  htmlAddLink( mailto.url(),
00208                               "<img valign=\"top\" src=\"" + iconPath + "\">" );
00209   }
00210 
00211   return tmpString;
00212 }
00213 
00214 static QString displayViewFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role )
00215 {
00216   QString tmpStr;
00217   Attendee::List::ConstIterator it;
00218   Attendee::List attendees = incidence->attendees();
00219 
00220   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00221     Attendee *a = *it;
00222     if ( a->role() != role ) {
00223       // skip this role
00224       continue;
00225     }
00226     if ( a->email() == incidence->organizer().email() ) {
00227       // skip attendee that is also the organizer
00228       continue;
00229     }
00230     tmpStr += displayViewLinkPerson( a->email(), a->name(), a->uid() );
00231     if ( !a->delegator().isEmpty() ) {
00232       tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
00233     }
00234     if ( !a->delegate().isEmpty() ) {
00235       tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
00236     }
00237     tmpStr += "<br>";
00238   }
00239   if ( tmpStr.endsWith( "<br>" ) ) {
00240     tmpStr.truncate( tmpStr.length() - 4 );
00241   }
00242   return tmpStr;
00243 }
00244 
00245 static QString displayViewFormatAttendees( Incidence *incidence )
00246 {
00247   QString tmpStr, str;
00248 
00249   // Add organizer link
00250   tmpStr += "<tr>";
00251   tmpStr += "<td><b>" + i18n( "Organizer:" ) + "</b></td>";
00252   tmpStr += "<td>" +
00253             displayViewLinkPerson( incidence->organizer().email(),
00254                                    incidence->organizer().name(),
00255                                    QString::null ) +
00256             "</td>";
00257   tmpStr += "</tr>";
00258 
00259   // Add "chair"
00260   str = displayViewFormatAttendeeRoleList( incidence, Attendee::Chair );
00261   if ( !str.isEmpty() ) {
00262     tmpStr += "<tr>";
00263     tmpStr += "<td><b>" + i18n( "Chair:" ) + "</b></td>";
00264     tmpStr += "<td>" + str + "</td>";
00265     tmpStr += "</tr>";
00266   }
00267 
00268   // Add required participants
00269   str = displayViewFormatAttendeeRoleList( incidence, Attendee::ReqParticipant );
00270   if ( !str.isEmpty() ) {
00271     tmpStr += "<tr>";
00272     tmpStr += "<td><b>" + i18n( "Required Participants:" ) + "</b></td>";
00273     tmpStr += "<td>" + str + "</td>";
00274     tmpStr += "</tr>";
00275   }
00276 
00277   // Add optional participants
00278   str = displayViewFormatAttendeeRoleList( incidence, Attendee::OptParticipant );
00279   if ( !str.isEmpty() ) {
00280     tmpStr += "<tr>";
00281     tmpStr += "<td><b>" + i18n( "Optional Participants:" ) + "</b></td>";
00282     tmpStr += "<td>" + str + "</td>";
00283     tmpStr += "</tr>";
00284   }
00285 
00286   // Add observers
00287   str = displayViewFormatAttendeeRoleList( incidence, Attendee::NonParticipant );
00288   if ( !str.isEmpty() ) {
00289     tmpStr += "<tr>";
00290     tmpStr += "<td><b>" + i18n( "Observers:" ) + "</b></td>";
00291     tmpStr += "<td>" + str + "</td>";
00292     tmpStr += "</tr>";
00293   }
00294 
00295   return tmpStr;
00296 }
00297 
00298 static QString displayViewFormatAttachments( Incidence *incidence )
00299 {
00300   QString tmpStr;
00301   Attachment::List as = incidence->attachments();
00302   Attachment::List::ConstIterator it;
00303   uint count = 0;
00304   for( it = as.begin(); it != as.end(); ++it ) {
00305     count++;
00306     if ( (*it)->isUri() ) {
00307       QString name;
00308       if ( (*it)->uri().startsWith( "kmail:" ) ) {
00309         name = i18n( "Show mail" );
00310       } else {
00311         name = (*it)->label();
00312       }
00313       tmpStr += htmlAddLink( (*it)->uri(), name );
00314     } else {
00315       tmpStr += (*it)->label();
00316     }
00317     if ( count < as.count() ) {
00318       tmpStr += "<br>";
00319     }
00320   }
00321   return tmpStr;
00322 }
00323 
00324 static QString displayViewFormatCategories( Incidence *incidence )
00325 {
00326   // We do not use Incidence::categoriesStr() since it does not have whitespace
00327   return incidence->categories().join( ", " );
00328 }
00329 
00330 static QString displayViewFormatCreationDate( Incidence *incidence )
00331 {
00332   return i18n( "Creation date: %1" ).
00333     arg( IncidenceFormatter::dateTimeToString( incidence->created(), false, true ) );
00334 }
00335 
00336 static QString displayViewFormatBirthday( Event *event )
00337 {
00338   if ( !event ) {
00339     return  QString::null;
00340   }
00341   if ( event->customProperty("KABC","BIRTHDAY") != "YES" ) {
00342     return QString::null;
00343   }
00344 
00345   QString uid = event->customProperty("KABC","UID-1");
00346   QString name = event->customProperty("KABC","NAME-1");
00347   QString email= event->customProperty("KABC","EMAIL-1");
00348 
00349   QString tmpStr = displayViewLinkPerson( email, name, uid );
00350 
00351   if ( event->customProperty( "KABC", "ANNIVERSARY") == "YES" ) {
00352     uid = event->customProperty("KABC","UID-2");
00353     name = event->customProperty("KABC","NAME-2");
00354     email= event->customProperty("KABC","EMAIL-2");
00355     tmpStr += "<br>";
00356     tmpStr += displayViewLinkPerson( email, name, uid );
00357   }
00358 
00359   return tmpStr;
00360 }
00361 
00362 static QString displayViewFormatHeader( Incidence *incidence )
00363 {
00364   QString tmpStr = "<table><tr>";
00365 
00366   // show icons
00367   {
00368     tmpStr += "<td>";
00369 
00370     if ( incidence->type() == "Event" ) {
00371       QString iconPath;
00372       if ( incidence->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) {
00373         if ( incidence->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00374           iconPath =
00375             KGlobal::iconLoader()->iconPath( "calendaranniversary", KIcon::Small );
00376         } else {
00377           iconPath = KGlobal::iconLoader()->iconPath( "calendarbirthday", KIcon::Small );
00378         }
00379       } else {
00380         iconPath = KGlobal::iconLoader()->iconPath( "appointment", KIcon::Small );
00381       }
00382       tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
00383     }
00384     if ( incidence->type() == "Todo" ) {
00385       tmpStr += "<img valign=\"top\" src=\"" +
00386                 KGlobal::iconLoader()->iconPath( "todo", KIcon::Small ) +
00387                 "\">";
00388     }
00389     if ( incidence->type() == "Journal" ) {
00390       tmpStr += "<img valign=\"top\" src=\"" +
00391                 KGlobal::iconLoader()->iconPath( "journal", KIcon::Small ) +
00392                 "\">";
00393     }
00394     if ( incidence->isAlarmEnabled() ) {
00395       tmpStr += "<img valign=\"top\" src=\"" +
00396                 KGlobal::iconLoader()->iconPath( "bell", KIcon::Small ) +
00397                 "\">";
00398     }
00399     if ( incidence->doesRecur() ) {
00400       tmpStr += "<img valign=\"top\" src=\"" +
00401                 KGlobal::iconLoader()->iconPath( "recur", KIcon::Small ) +
00402                 "\">";
00403     }
00404     if ( incidence->isReadOnly() ) {
00405       tmpStr += "<img valign=\"top\" src=\"" +
00406                 KGlobal::iconLoader()->iconPath( "readonlyevent", KIcon::Small ) +
00407                 "\">";
00408     }
00409 
00410     tmpStr += "</td>";
00411   }
00412 
00413   tmpStr += "<td>";
00414   tmpStr += "<b><u>" + incidence->summary() + "</u></b>";
00415   tmpStr += "</td>";
00416 
00417   tmpStr += "</tr></table>";
00418 
00419   return tmpStr;
00420 }
00421 
00422 static QString displayViewFormatEvent( Calendar *calendar, Event *event,
00423                                        const QDate &date )
00424 {
00425   if ( !event ) {
00426     return QString::null;
00427   }
00428 
00429   QString tmpStr = displayViewFormatHeader( event );
00430 
00431   tmpStr += "<table>";
00432   tmpStr += "<col width=\"25%\"/>";
00433   tmpStr += "<col width=\"75%\"/>";
00434 
00435   if ( calendar ) {
00436     QString calStr = IncidenceFormatter::resourceString( calendar, event );
00437     if ( !calStr.isEmpty() ) {
00438       tmpStr += "<tr>";
00439       tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00440       tmpStr += "<td>" + calStr + "</td>";
00441       tmpStr += "</tr>";
00442     }
00443   }
00444 
00445   if ( !event->location().isEmpty() ) {
00446     tmpStr += "<tr>";
00447     tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
00448     tmpStr += "<td>" + event->location() + "</td>";
00449     tmpStr += "</tr>";
00450   }
00451 
00452   QDateTime startDt = event->dtStart();
00453   QDateTime endDt = event->dtEnd();
00454   if ( event->doesRecur() ) {
00455     if ( date.isValid() ) {
00456       QDateTime dt( date, QTime( 0, 0, 0 ) );
00457       int diffDays = startDt.daysTo( dt );
00458       dt = dt.addSecs( -1 );
00459       startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() );
00460       if ( event->hasEndDate() ) {
00461         endDt = endDt.addDays( diffDays );
00462         if ( startDt > endDt ) {
00463           startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() );
00464           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
00465         }
00466       }
00467     }
00468   }
00469 
00470   tmpStr += "<tr>";
00471   if ( event->doesFloat() ) {
00472     if ( event->isMultiDay() ) {
00473       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00474       tmpStr += "<td>" +
00475                 i18n("<beginDate> - <endDate>","%1 - %2").
00476                 arg( IncidenceFormatter::dateToString( startDt, false ) ).
00477                 arg( IncidenceFormatter::dateToString( endDt, false ) ) +
00478                 "</td>";
00479     } else {
00480       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00481       tmpStr += "<td>" +
00482                 i18n("date as string","%1").
00483                 arg( IncidenceFormatter::dateToString( startDt, false ) ) +
00484                 "</td>";
00485     }
00486   } else {
00487     if ( event->isMultiDay() ) {
00488       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00489       tmpStr += "<td>" +
00490                 i18n("<beginDate> - <endDate>","%1 - %2").
00491                 arg( IncidenceFormatter::dateToString( startDt, false ) ).
00492                 arg( IncidenceFormatter::dateToString( endDt, false ) ) +
00493                 "</td>";
00494     } else {
00495       tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00496       tmpStr += "<td>" +
00497                 i18n("date as string","%1").
00498                 arg( IncidenceFormatter::dateToString( startDt, false ) ) +
00499                 "</td>";
00500 
00501       tmpStr += "</tr><tr>";
00502       tmpStr += "<td><b>" + i18n( "Time:" ) + "</b></td>";
00503       if ( event->hasEndDate() && startDt != endDt ) {
00504         tmpStr += "<td>" +
00505                   i18n("<beginTime> - <endTime>","%1 - %2").
00506                   arg( IncidenceFormatter::timeToString( startDt, true ) ).
00507                   arg( IncidenceFormatter::timeToString( endDt, true ) ) +
00508                   "</td>";
00509       } else {
00510         tmpStr += "<td>" +
00511                   IncidenceFormatter::timeToString( startDt, true ) +
00512                   "</td>";
00513       }
00514     }
00515   }
00516   tmpStr += "</tr>";
00517 
00518   QString durStr = IncidenceFormatter::durationString( event );
00519   if ( !durStr.isEmpty() ) {
00520     tmpStr += "<tr>";
00521     tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>";
00522     tmpStr += "<td>" + durStr + "</td>";
00523     tmpStr += "</tr>";
00524   }
00525 
00526   if ( event->doesRecur() ) {
00527     tmpStr += "<tr>";
00528     tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>";
00529     tmpStr += "<td>" +
00530               IncidenceFormatter::recurrenceString( event ) +
00531               "</td>";
00532     tmpStr += "</tr>";
00533   }
00534 
00535   if ( event->customProperty("KABC","BIRTHDAY")== "YES" ) {
00536     tmpStr += "<tr>";
00537     if ( event->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00538       tmpStr += "<td><b>" + i18n( "Anniversary:" ) + "</b></td>";
00539     } else {
00540       tmpStr += "<td><b>" + i18n( "Birthday:" ) + "</b></td>";
00541     }
00542     tmpStr += "<td>" + displayViewFormatBirthday( event ) + "</td>";
00543     tmpStr += "</tr>";
00544     tmpStr += "</table>";
00545     return tmpStr;
00546   }
00547 
00548   if ( !event->description().isEmpty() ) {
00549     tmpStr += "<tr>";
00550     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00551     tmpStr += "<td>" + event->description() + "</td>";
00552     tmpStr += "</tr>";
00553   }
00554 
00555   // TODO: print comments?
00556 
00557   int categoryCount = event->categories().count();
00558   if ( categoryCount > 0 ) {
00559     tmpStr += "<tr>";
00560     tmpStr += "<td><b>" +
00561               i18n( "Category:", "%n Categories:", categoryCount ) +
00562               "</b></td>";
00563     tmpStr += "<td>" + displayViewFormatCategories( event ) + "</td>";
00564     tmpStr += "</tr>";
00565   }
00566 
00567   if ( event->attendees().count() > 1 ) {
00568     tmpStr += displayViewFormatAttendees( event );
00569   }
00570 
00571   int attachmentCount = event->attachments().count();
00572   if ( attachmentCount > 0 ) {
00573     tmpStr += "<tr>";
00574     tmpStr += "<td><b>" +
00575               i18n( "Attachment:", "%n Attachments:", attachmentCount ) +
00576               "</b></td>";
00577     tmpStr += "<td>" + displayViewFormatAttachments( event ) + "</td>";
00578     tmpStr += "</tr>";
00579   }
00580   tmpStr += "</table>";
00581 
00582   tmpStr += "<em>" + displayViewFormatCreationDate( event ) + "</em>";
00583 
00584   return tmpStr;
00585 }
00586 
00587 static QString displayViewFormatTodo( Calendar *calendar, Todo *todo,
00588                                       const QDate &date )
00589 {
00590   if ( !todo ) {
00591     return QString::null;
00592   }
00593 
00594   QString tmpStr = displayViewFormatHeader( todo );
00595 
00596   tmpStr += "<table>";
00597   tmpStr += "<col width=\"25%\"/>";
00598   tmpStr += "<col width=\"75%\"/>";
00599 
00600   if ( calendar ) {
00601     QString calStr = IncidenceFormatter::resourceString( calendar, todo );
00602     if ( !calStr.isEmpty() ) {
00603       tmpStr += "<tr>";
00604       tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00605       tmpStr += "<td>" + calStr + "</td>";
00606       tmpStr += "</tr>";
00607     }
00608   }
00609 
00610   if ( !todo->location().isEmpty() ) {
00611     tmpStr += "<tr>";
00612     tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
00613     tmpStr += "<td>" + todo->location() + "</td>";
00614     tmpStr += "</tr>";
00615   }
00616 
00617   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
00618     QDateTime startDt = todo->dtStart();
00619     if ( todo->doesRecur() ) {
00620       if ( date.isValid() ) {
00621         startDt.setDate( date );
00622       }
00623     }
00624     tmpStr += "<tr>";
00625     tmpStr += "<td><b>" + i18n( "Start:" ) + "</b></td>";
00626     tmpStr += "<td>" +
00627               IncidenceFormatter::dateTimeToString( startDt,
00628                                                     todo->doesFloat(), false ) +
00629               "</td>";
00630     tmpStr += "</tr>";
00631   }
00632 
00633   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
00634     QDateTime dueDt = todo->dtDue();
00635     if ( todo->doesRecur() ) {
00636       if ( date.isValid() ) {
00637         QDateTime dt( date, QTime( 0, 0, 0 ) );
00638         dt = dt.addSecs( -1 );
00639         dueDt.setDate( todo->recurrence()->getNextDateTime( dt ).date() );
00640       }
00641     }
00642     tmpStr += "<tr>";
00643     tmpStr += "<td><b>" + i18n( "Due:" ) + "</b></td>";
00644     tmpStr += "<td>" +
00645               IncidenceFormatter::dateTimeToString( dueDt,
00646                                                     todo->doesFloat(), false ) +
00647               "</td>";
00648     tmpStr += "</tr>";
00649   }
00650 
00651   QString durStr = IncidenceFormatter::durationString( todo );
00652   if ( !durStr.isEmpty() ) {
00653     tmpStr += "<tr>";
00654     tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>";
00655     tmpStr += "<td>" + durStr + "</td>";
00656     tmpStr += "</tr>";
00657   }
00658 
00659   if ( todo->doesRecur() ) {
00660     tmpStr += "<tr>";
00661     tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>";
00662     tmpStr += "<td>" +
00663               IncidenceFormatter::recurrenceString( todo ) +
00664               "</td>";
00665     tmpStr += "</tr>";
00666   }
00667 
00668   if ( !todo->description().isEmpty() ) {
00669     tmpStr += "<tr>";
00670     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00671     tmpStr += "<td>" + todo->description() + "</td>";
00672     tmpStr += "</tr>";
00673   }
00674 
00675   // TODO: print comments?
00676 
00677   int categoryCount = todo->categories().count();
00678   if ( categoryCount > 0 ) {
00679     tmpStr += "<tr>";
00680     tmpStr += "<td><b>" +
00681               i18n( "Category:", "%n Categories:", categoryCount ) +
00682               "</b></td>";
00683     tmpStr += "<td>" + displayViewFormatCategories( todo ) + "</td>";
00684     tmpStr += "</tr>";
00685   }
00686 
00687   if ( todo->priority() > 0 ) {
00688     tmpStr += "<tr>";
00689     tmpStr += "<td><b>" + i18n( "Priority:" ) + "</b></td>";
00690     tmpStr += "<td>";
00691     tmpStr += QString::number( todo->priority() );
00692     tmpStr += "</td>";
00693     tmpStr += "</tr>";
00694   }
00695 
00696   tmpStr += "<tr>";
00697   if ( todo->isCompleted() ) {
00698     tmpStr += "<td><b>" + i18n( "Completed:" ) + "</b></td>";
00699     tmpStr += "<td>";
00700     tmpStr += todo->completedStr();
00701   } else {
00702     tmpStr += "<td><b>" + i18n( "Percent Done:" ) + "</b></td>";
00703     tmpStr += "<td>";
00704     tmpStr += i18n( "%1%" ).arg( todo->percentComplete() );
00705   }
00706   tmpStr += "</td>";
00707   tmpStr += "</tr>";
00708 
00709   if ( todo->attendees().count() > 1 ) {
00710     tmpStr += displayViewFormatAttendees( todo );
00711   }
00712 
00713   int attachmentCount = todo->attachments().count();
00714   if ( attachmentCount > 0 ) {
00715     tmpStr += "<tr>";
00716     tmpStr += "<td><b>" +
00717               i18n( "Attachment:", "Attachments:", attachmentCount ) +
00718               "</b></td>";
00719     tmpStr += "<td>" + displayViewFormatAttachments( todo ) + "</td>";
00720     tmpStr += "</tr>";
00721   }
00722 
00723   tmpStr += "</table>";
00724 
00725   tmpStr += "<em>" + displayViewFormatCreationDate( todo ) + "</em>";
00726 
00727   return tmpStr;
00728 }
00729 
00730 static QString displayViewFormatJournal( Calendar *calendar, Journal *journal )
00731 {
00732   if ( !journal ) {
00733     return QString::null;
00734   }
00735 
00736   QString tmpStr = displayViewFormatHeader( journal );
00737 
00738   tmpStr += "<table>";
00739   tmpStr += "<col width=\"25%\"/>";
00740   tmpStr += "<col width=\"75%\"/>";
00741 
00742   if ( calendar ) {
00743     QString calStr = IncidenceFormatter::resourceString( calendar, journal );
00744     if ( !calStr.isEmpty() ) {
00745       tmpStr += "<tr>";
00746       tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00747       tmpStr += "<td>" + calStr + "</td>";
00748       tmpStr += "</tr>";
00749     }
00750   }
00751 
00752   tmpStr += "<tr>";
00753   tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00754   tmpStr += "<td>" +
00755             IncidenceFormatter::dateToString( journal->dtStart(), false ) +
00756             "</td>";
00757   tmpStr += "</tr>";
00758 
00759   if ( !journal->description().isEmpty() ) {
00760     tmpStr += "<tr>";
00761     tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00762     tmpStr += "<td>" + journal->description() + "</td>";
00763     tmpStr += "</tr>";
00764   }
00765 
00766   int categoryCount = journal->categories().count();
00767   if ( categoryCount > 0 ) {
00768     tmpStr += "<tr>";
00769     tmpStr += "<td><b>" +
00770               i18n( "Category:", "%n Categories:", categoryCount ) +
00771               "</b></td>";
00772     tmpStr += "<td>" + displayViewFormatCategories( journal ) + "</td>";
00773     tmpStr += "</tr>";
00774   }
00775   tmpStr += "</table>";
00776 
00777   tmpStr += "<em>" + displayViewFormatCreationDate( journal ) + "</em>";
00778 
00779   return tmpStr;
00780 }
00781 
00782 static QString displayViewFormatFreeBusy( Calendar * /*calendar*/, FreeBusy *fb )
00783 {
00784   if ( !fb ) {
00785     return QString::null;
00786   }
00787 
00788   QString tmpStr = htmlAddTag( "h2",
00789                                htmlAddTag( "b",
00790                                            i18n("Free/Busy information for %1").
00791                                            arg( fb->organizer().fullName() ) ) );
00792 
00793   tmpStr += htmlAddTag( "h4", i18n("Busy times in date range %1 - %2:").
00794                         arg( IncidenceFormatter::dateToString( fb->dtStart(), true ) ).
00795                         arg( IncidenceFormatter::dateToString( fb->dtEnd(), true ) ) );
00796 
00797   QValueList<Period> periods = fb->busyPeriods();
00798 
00799   QString text = htmlAddTag( "em", htmlAddTag( "b", i18n("Busy:") ) );
00800   QValueList<Period>::iterator it;
00801   for ( it = periods.begin(); it != periods.end(); ++it ) {
00802     Period per = *it;
00803     if ( per.hasDuration() ) {
00804       int dur = per.duration().asSeconds();
00805       QString cont;
00806       if ( dur >= 3600 ) {
00807         cont += i18n("1 hour ", "%n hours ", dur / 3600 );
00808         dur %= 3600;
00809       }
00810       if ( dur >= 60 ) {
00811         cont += i18n("1 minute ", "%n minutes ", dur / 60);
00812         dur %= 60;
00813       }
00814       if ( dur > 0 ) {
00815         cont += i18n("1 second", "%n seconds", dur);
00816       }
00817       text += i18n("startDate for duration", "%1 for %2").
00818               arg( IncidenceFormatter::dateTimeToString( per.start(), false, true ) ).
00819               arg( cont );
00820       text += "<br>";
00821     } else {
00822       if ( per.start().date() == per.end().date() ) {
00823         text += i18n("date, fromTime - toTime ", "%1, %2 - %3").
00824                 arg( IncidenceFormatter::dateToString( per.start().date(), true ) ).
00825                 arg( IncidenceFormatter::timeToString( per.start(), true ) ).
00826                 arg( IncidenceFormatter::timeToString( per.end(), true ) );
00827       } else {
00828         text += i18n("fromDateTime - toDateTime", "%1 - %2").
00829                 arg( IncidenceFormatter::dateTimeToString( per.start(), false, true ) ).
00830                 arg( IncidenceFormatter::dateTimeToString( per.end(), false, true ) );
00831       }
00832       text += "<br>";
00833     }
00834   }
00835   tmpStr += htmlAddTag( "p", text );
00836   return tmpStr;
00837 }
00838 
00839 class IncidenceFormatter::EventViewerVisitor : public IncidenceBase::Visitor
00840 {
00841   public:
00842     EventViewerVisitor()
00843       : mCalendar( 0 ), mResult( "" ) {}
00844 
00845     bool act( Calendar *calendar, IncidenceBase *incidence, const QDate &date )
00846     {
00847       mCalendar = calendar;
00848       mDate = date;
00849       mResult = "";
00850       return incidence->accept( *this );
00851     }
00852     QString result() const { return mResult; }
00853 
00854   protected:
00855     bool visit( Event *event )
00856     {
00857       mResult = displayViewFormatEvent( mCalendar, event, mDate );
00858       return !mResult.isEmpty();
00859     }
00860     bool visit( Todo *todo )
00861     {
00862       mResult = displayViewFormatTodo( mCalendar, todo, mDate );
00863       return !mResult.isEmpty();
00864     }
00865     bool visit( Journal *journal )
00866     {
00867       mResult = displayViewFormatJournal( mCalendar, journal );
00868       return !mResult.isEmpty();
00869     }
00870     bool visit( FreeBusy *fb )
00871     {
00872       mResult = displayViewFormatFreeBusy( mCalendar, fb );
00873       return !mResult.isEmpty();
00874     }
00875 
00876   protected:
00877     Calendar *mCalendar;
00878     QDate mDate;
00879     QString mResult;
00880 };
00881 
00882 QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence )
00883 {
00884   return extensiveDisplayStr( 0, incidence, QDate() );
00885 }
00886 
00887 QString IncidenceFormatter::extensiveDisplayStr( Calendar *calendar,
00888                                                  IncidenceBase *incidence,
00889                                                  const QDate &date )
00890 {
00891   if ( !incidence ) {
00892     return QString::null;
00893   }
00894 
00895   EventViewerVisitor v;
00896   if ( v.act( calendar, incidence, date ) ) {
00897     return v.result();
00898   } else {
00899     return QString::null;
00900   }
00901 }
00902 
00903 /***********************************************************************
00904  *  Helper functions for the body part formatter of kmail (Invitations)
00905  ***********************************************************************/
00906 
00907 static QString string2HTML( const QString& str )
00908 {
00909   return QStyleSheet::convertFromPlainText(str, QStyleSheetItem::WhiteSpaceNormal);
00910 }
00911 
00912 static QString cleanHtml( const QString &html )
00913 {
00914   QRegExp rx( "<body[^>]*>(.*)</body>" );
00915   rx.setCaseSensitive( false );
00916   rx.search( html );
00917   QString body = rx.cap( 1 );
00918 
00919   return QStyleSheet::escape( body.remove( QRegExp( "<[^>]*>" ) ).stripWhiteSpace() );
00920 }
00921 
00922 static QString eventStartTimeStr( Event *event )
00923 {
00924   QString tmp;
00925   if ( !event->doesFloat() ) {
00926     tmp = i18n( "%1: Start Date, %2: Start Time", "%1 %2" ).
00927           arg( IncidenceFormatter::dateToString( event->dtStart(), true ),
00928                IncidenceFormatter::timeToString( event->dtStart(), true ) );
00929   } else {
00930     tmp = i18n( "%1: Start Date", "%1 (all day)" ).
00931           arg( IncidenceFormatter::dateToString( event->dtStart(), true ) );
00932   }
00933   return tmp;
00934 }
00935 
00936 static QString eventEndTimeStr( Event *event )
00937 {
00938   QString tmp;
00939   if ( event->hasEndDate() && event->dtEnd().isValid() ) {
00940     if ( !event->doesFloat() ) {
00941       tmp = i18n( "%1: End Date, %2: End Time", "%1 %2" ).
00942             arg( IncidenceFormatter::dateToString( event->dtEnd(), true ),
00943                  IncidenceFormatter::timeToString( event->dtEnd(), true ) );
00944     } else {
00945       tmp = i18n( "%1: End Date", "%1 (all day)" ).
00946             arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) );
00947     }
00948   }
00949   return tmp;
00950 }
00951 
00952 static QString invitationRow( const QString &cell1, const QString &cell2 )
00953 {
00954   return "<tr><td>" + cell1 + "</td><td>" + cell2 + "</td></tr>\n";
00955 }
00956 
00957 static Attendee *findDelegatedFromMyAttendee( Incidence *incidence )
00958 {
00959   // Return the first attendee that was delegated-from me
00960 
00961   Attendee *attendee = 0;
00962   if ( !incidence ) {
00963     return attendee;
00964   }
00965 
00966   KEMailSettings settings;
00967   QStringList profiles = settings.profiles();
00968   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
00969     settings.setProfile( *it );
00970 
00971     QString delegatorName, delegatorEmail;
00972     Attendee::List attendees = incidence->attendees();
00973     Attendee::List::ConstIterator it2;
00974     for ( it2 = attendees.begin(); it2 != attendees.end(); ++it2 ) {
00975       Attendee *a = *it2;
00976       KPIM::getNameAndMail( a->delegator(), delegatorName, delegatorEmail );
00977       if ( settings.getSetting( KEMailSettings::EmailAddress ) == delegatorEmail ) {
00978         attendee = a;
00979         break;
00980       }
00981     }
00982   }
00983   return attendee;
00984 }
00985 
00986 static Attendee *findMyAttendee( Incidence *incidence )
00987 {
00988   // Return the attendee for the incidence that is probably me
00989 
00990   Attendee *attendee = 0;
00991   if ( !incidence ) {
00992     return attendee;
00993   }
00994 
00995   KEMailSettings settings;
00996   QStringList profiles = settings.profiles();
00997   for( QStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) {
00998     settings.setProfile( *it );
00999 
01000     Attendee::List attendees = incidence->attendees();
01001     Attendee::List::ConstIterator it2;
01002     for ( it2 = attendees.begin(); it2 != attendees.end(); ++it2 ) {
01003       Attendee *a = *it2;
01004       if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) {
01005         attendee = a;
01006         break;
01007       }
01008     }
01009   }
01010   return attendee;
01011 }
01012 
01013 static Attendee *findAttendee( Incidence *incidence, const QString &email )
01014 {
01015   // Search for an attendee by email address
01016 
01017   Attendee *attendee = 0;
01018   if ( !incidence ) {
01019     return attendee;
01020   }
01021 
01022   Attendee::List attendees = incidence->attendees();
01023   Attendee::List::ConstIterator it;
01024   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
01025     Attendee *a = *it;
01026     if ( email == a->email() ) {
01027       attendee = a;
01028       break;
01029     }
01030   }
01031   return attendee;
01032 }
01033 
01034 static bool rsvpRequested( Incidence *incidence )
01035 {
01036   if ( !incidence ) {
01037     return false;
01038   }
01039 
01040   //use a heuristic to determine if a response is requested.
01041 
01042   bool rsvp = true; // better send superfluously than not at all
01043   Attendee::List attendees = incidence->attendees();
01044   Attendee::List::ConstIterator it;
01045   for ( it = attendees.begin(); it != attendees.end(); ++it ) {
01046     if ( it == attendees.begin() ) {
01047       rsvp = (*it)->RSVP(); // use what the first one has
01048     } else {
01049       if ( (*it)->RSVP() != rsvp ) {
01050         rsvp = true; // they differ, default
01051         break;
01052       }
01053     }
01054   }
01055   return rsvp;
01056 }
01057 
01058 static QString rsvpRequestedStr( bool rsvpRequested, const QString &role )
01059 {
01060   if ( rsvpRequested ) {
01061     if ( role.isEmpty() ) {
01062       return i18n( "Your response is requested" );
01063     } else {
01064       return i18n( "Your response as <b>%1</b> is requested" ).arg( role );
01065     }
01066   } else {
01067     if ( role.isEmpty() ) {
01068       return i18n( "No response is necessary" );
01069     } else {
01070       return i18n( "No response as <b>%1</b> is necessary" ).arg( role );
01071     }
01072   }
01073 }
01074 
01075 static QString myStatusStr( Incidence *incidence )
01076 {
01077   QString ret;
01078   Attendee *a = findMyAttendee( incidence );
01079   if ( a &&
01080        a->status() != Attendee::NeedsAction && a->status() != Attendee::Delegated ) {
01081     ret = i18n( "(<b>Note</b>: the Organizer preset your response to <b>%1</b>)" ).
01082           arg( Attendee::statusName( a->status() ) );
01083   }
01084   return ret;
01085 }
01086 
01087 static QString invitationPerson( const QString& email, QString name, QString uid )
01088 {
01089   // Make the search, if there is an email address to search on,
01090   // and either name or uid is missing
01091   if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
01092     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
01093     KABC::Addressee::List addressList = add_book->findByEmail( email );
01094     if ( !addressList.isEmpty() ) {
01095       KABC::Addressee o = addressList.first();
01096       if ( !o.isEmpty() && addressList.size() < 2 ) {
01097         if ( name.isEmpty() ) {
01098           // No name set, so use the one from the addressbook
01099           name = o.formattedName();
01100         }
01101         uid = o.uid();
01102       } else {
01103         // Email not found in the addressbook. Don't make a link
01104         uid = QString::null;
01105       }
01106     }
01107   }
01108 
01109   // Show the attendee
01110   QString tmpString;
01111   if ( !uid.isEmpty() ) {
01112     // There is a UID, so make a link to the addressbook
01113     if ( name.isEmpty() ) {
01114       // Use the email address for text
01115       tmpString += htmlAddLink( "uid:" + uid, email );
01116     } else {
01117       tmpString += htmlAddLink( "uid:" + uid, name );
01118     }
01119   } else {
01120     // No UID, just show some text
01121     tmpString += ( name.isEmpty() ? email : name );
01122   }
01123   tmpString += '\n';
01124 
01125   // Make the mailto link
01126   if ( !email.isEmpty() ) {
01127     KCal::Person person( name, email );
01128     KURL mailto;
01129     mailto.setProtocol( "mailto" );
01130     mailto.setPath( person.fullName() );
01131     const QString iconPath =
01132       KGlobal::iconLoader()->iconPath( "mail_new", KIcon::Small );
01133     tmpString += "&nbsp;" +
01134                  htmlAddLink( mailto.url(), "<img src=\"" + iconPath + "\">" )
01135 ;
01136   }
01137   tmpString += "\n";
01138 
01139   return tmpString;
01140 }
01141 
01142 static QString invitationsDetailsIncidence( Incidence *incidence, bool noHtmlMode )
01143 {
01144   // if description and comment -> use both
01145   // if description, but no comment -> use the desc as the comment (and no desc)
01146   // if comment, but no description -> use the comment and no description
01147 
01148   QString html;
01149   QString descr;
01150   QStringList comments;
01151 
01152   if ( incidence->comments().isEmpty() ) {
01153     if ( !incidence->description().isEmpty() ) {
01154       // use description as comments
01155       if ( !QStyleSheet::mightBeRichText( incidence->description() ) ) {
01156         comments << string2HTML( incidence->description() );
01157       } else {
01158         comments << incidence->description();
01159         if ( noHtmlMode ) {
01160           comments[0] = cleanHtml( comments[0] );
01161         }
01162         comments[0] = htmlAddTag( "p", comments[0] );
01163       }
01164     }
01165     //else desc and comments are empty
01166   } else {
01167     // non-empty comments
01168     QStringList cl = incidence->comments();
01169     uint i = 0;
01170     for( QStringList::Iterator it=cl.begin(); it!=cl.end(); ++it ) {
01171       if ( !QStyleSheet::mightBeRichText( *it ) ) {
01172         comments.append( string2HTML( *it ) );
01173       } else {
01174         if ( noHtmlMode ) {
01175           comments.append( cleanHtml( "<body>" + (*it) + "</body>" ) );
01176         } else {
01177           comments.append( *it );
01178         }
01179       }
01180       i++;
01181     }
01182     if ( !incidence->description().isEmpty() ) {
01183       // use description too
01184       if ( !QStyleSheet::mightBeRichText( incidence->description() ) ) {
01185         descr = string2HTML( incidence->description() );
01186       } else {
01187         descr = incidence->description();
01188         if ( noHtmlMode ) {
01189           descr = cleanHtml( descr );
01190         }
01191         descr = htmlAddTag( "p", descr );
01192       }
01193     }
01194   }
01195 
01196   if( !descr.isEmpty() ) {
01197     html += "<p>";
01198     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01199     html += "<tr><td><center>" +
01200             htmlAddTag( "u", i18n( "Description:" ) ) +
01201             "</center></td></tr>";
01202     html += "<tr><td>" + descr + "</td></tr>";
01203     html += "</table>";
01204   }
01205 
01206   if ( !comments.isEmpty() ) {
01207     html += "<p>";
01208     html += "<table border=\"0\" style=\"margin-top:4px;\">";
01209     html += "<tr><td><center>" +
01210             htmlAddTag( "u", i18n( "Comments:" ) ) +
01211             "</center></td></tr>";
01212     html += "<tr><td>";
01213     if ( comments.count() > 1 ) {
01214       html += "<ul>";
01215       for ( uint i=0; i < comments.count(); ++i ) {
01216         html += "<li>" + comments[i] + "</li>";
01217       }
01218       html += "</ul>";
01219     } else {
01220       html += comments[0];
01221     }
01222     html += "</td></tr>";
01223     html += "</table>";
01224   }
01225   return html;
01226 }
01227 
01228 static QString invitationDetailsEvent( Event* event, bool noHtmlMode )
01229 {
01230   // Invitation details are formatted into an HTML table
01231   if ( !event ) {
01232     return QString::null;
01233   }
01234 
01235   QString sSummary = i18n( "Summary unspecified" );
01236   if ( !event->summary().isEmpty() ) {
01237     if ( !QStyleSheet::mightBeRichText( event->summary() ) ) {
01238       sSummary = QStyleSheet::escape( event->summary() );
01239     } else {
01240       sSummary = event->summary();
01241       if ( noHtmlMode ) {
01242         sSummary = cleanHtml( sSummary );
01243       }
01244     }
01245   }
01246 
01247   QString sLocation = i18n( "Location unspecified" );
01248   if ( !event->location().isEmpty() ) {
01249     if ( !QStyleSheet::mightBeRichText( event->location() ) ) {
01250       sLocation = QStyleSheet::escape( event->location() );
01251     } else {
01252       sLocation = event->location();
01253       if ( noHtmlMode ) {
01254         sLocation = cleanHtml( sLocation );
01255       }
01256     }
01257   }
01258 
01259   QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
01260   QString html = QString("<div dir=\"%1\">\n").arg(dir);
01261   html += "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n";
01262 
01263   // Invitation summary & location rows
01264   html += invitationRow( i18n( "What:" ), sSummary );
01265   html += invitationRow( i18n( "Where:" ), sLocation );
01266 
01267   // If a 1 day event
01268   if ( event->dtStart().date() == event->dtEnd().date() ) {
01269     html += invitationRow( i18n( "Date:" ),
01270                            IncidenceFormatter::dateToString( event->dtStart(), false ) );
01271     if ( !event->doesFloat() ) {
01272       html += invitationRow( i18n( "Time:" ),
01273                              IncidenceFormatter::timeToString( event->dtStart(), true ) +
01274                              " - " +
01275                              IncidenceFormatter::timeToString( event->dtEnd(), true ) );
01276     }
01277   } else {
01278     html += invitationRow( i18n( "Starting date of an event", "From:" ),
01279                            IncidenceFormatter::dateToString( event->dtStart(), false ) );
01280     if ( !event->doesFloat() ) {
01281       html += invitationRow( i18n( "Starting time of an event", "At:" ),
01282                              IncidenceFormatter::timeToString( event->dtStart(), true ) );
01283     }
01284     if ( event->hasEndDate() ) {
01285       html += invitationRow( i18n( "Ending date of an event", "To:" ),
01286                              IncidenceFormatter::dateToString( event->dtEnd(), false ) );
01287       if ( !event->doesFloat() ) {
01288         html += invitationRow( i18n( "Starting time of an event", "At:" ),
01289                                IncidenceFormatter::timeToString( event->dtEnd(), true ) );
01290       }
01291     } else {
01292       html += invitationRow( i18n( "Ending date of an event", "To:" ),
01293                              i18n( "no end date specified" ) );
01294     }
01295   }
01296 
01297   // Invitation Duration Row
01298   QString durStr = IncidenceFormatter::durationString( event );
01299   if ( !durStr.isEmpty() ) {
01300     html += invitationRow( i18n( "Duration:" ), durStr );
01301   }
01302 
01303   if ( event->doesRecur() )
01304     html += invitationRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( event ) );
01305 
01306   html += "</table>\n";
01307   html += invitationsDetailsIncidence( event, noHtmlMode );
01308   html += "</div>\n";
01309 
01310   return html;
01311 }
01312 
01313 static QString invitationDetailsTodo( Todo *todo, bool noHtmlMode )
01314 {
01315   // Task details are formatted into an HTML table
01316   if ( !todo ) {
01317     return QString::null;
01318   }
01319 
01320   QString sSummary = i18n( "Summary unspecified" );
01321   if ( !todo->summary().isEmpty() ) {
01322     if ( !QStyleSheet::mightBeRichText( todo->summary() ) ) {
01323       sSummary = QStyleSheet::escape( todo->summary() );
01324     } else {
01325       sSummary = todo->summary();
01326       if ( noHtmlMode ) {
01327         sSummary = cleanHtml( sSummary );
01328       }
01329     }
01330   }
01331 
01332   QString sLocation = i18n( "Location unspecified" );
01333   if ( !todo->location().isEmpty() ) {
01334     if ( !QStyleSheet::mightBeRichText( todo->location() ) ) {
01335       sLocation = QStyleSheet::escape( todo->location() );
01336     } else {
01337       sLocation = todo->location();
01338       if ( noHtmlMode ) {
01339         sLocation = cleanHtml( sLocation );
01340       }
01341     }
01342   }
01343 
01344   QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
01345   QString html = QString("<div dir=\"%1\">\n").arg(dir);
01346   html += "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n";
01347 
01348   // Invitation summary & location rows
01349   html += invitationRow( i18n( "What:" ), sSummary );
01350   html += invitationRow( i18n( "Where:" ), sLocation );
01351 
01352   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01353     html += invitationRow( i18n( "Start Date:" ),
01354                            IncidenceFormatter::dateToString( todo->dtStart(), false ) );
01355   }
01356   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01357     html += invitationRow( i18n( "Due Date:" ),
01358                            IncidenceFormatter::dateToString( todo->dtDue(), false ) );
01359     if ( !todo->doesFloat() ) {
01360       html += invitationRow( i18n( "Due Time:" ),
01361                              KGlobal::locale()->formatTime( todo->dtDue().time() ) );
01362     }
01363 
01364   } else {
01365     html += invitationRow( i18n( "Due Date:" ),
01366                            i18n( "Due Date: None", "None" ) );
01367   }
01368 
01369   html += "</table></div>\n";
01370   html += invitationsDetailsIncidence( todo, noHtmlMode );
01371 
01372   return html;
01373 }
01374 
01375 static QString invitationDetailsJournal( Journal *journal, bool noHtmlMode )
01376 {
01377   if ( !journal ) {
01378     return QString::null;
01379   }
01380 
01381   QString sSummary = i18n( "Summary unspecified" );
01382   QString sDescr = i18n( "Description unspecified" );
01383   if ( ! journal->summary().isEmpty() ) {
01384     sSummary = journal->summary();
01385     if ( noHtmlMode ) {
01386       sSummary = cleanHtml( sSummary );
01387     }
01388   }
01389   if ( ! journal->description().isEmpty() ) {
01390     sDescr = journal->description();
01391     if ( noHtmlMode ) {
01392       sDescr = cleanHtml( sDescr );
01393     }
01394   }
01395   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
01396   html += invitationRow( i18n( "Summary:" ), sSummary );
01397   html += invitationRow( i18n( "Date:" ),
01398                          IncidenceFormatter::dateToString( journal->dtStart(), false ) );
01399   html += invitationRow( i18n( "Description:" ), sDescr );
01400   html += "</table>\n";
01401   html += invitationsDetailsIncidence( journal, noHtmlMode );
01402 
01403   return html;
01404 }
01405 
01406 static QString invitationDetailsFreeBusy( FreeBusy *fb, bool /*noHtmlMode*/ )
01407 {
01408   if ( !fb )
01409     return QString::null;
01410   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
01411 
01412   html += invitationRow( i18n("Person:"), fb->organizer().fullName() );
01413   html += invitationRow( i18n("Start date:"),
01414                          IncidenceFormatter::dateToString( fb->dtStart(), true ) );
01415   html += invitationRow( i18n("End date:"),
01416                          KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) );
01417   html += "<tr><td colspan=2><hr></td></tr>\n";
01418   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
01419 
01420   QValueList<Period> periods = fb->busyPeriods();
01421 
01422   QValueList<Period>::iterator it;
01423   for ( it = periods.begin(); it != periods.end(); ++it ) {
01424     Period per = *it;
01425     if ( per.hasDuration() ) {
01426       int dur = per.duration().asSeconds();
01427       QString cont;
01428       if ( dur >= 3600 ) {
01429         cont += i18n("1 hour ", "%n hours ", dur / 3600);
01430         dur %= 3600;
01431       }
01432       if ( dur >= 60 ) {
01433         cont += i18n("1 minute", "%n minutes ", dur / 60);
01434         dur %= 60;
01435       }
01436       if ( dur > 0 ) {
01437         cont += i18n("1 second", "%n seconds", dur);
01438       }
01439       html += invitationRow( QString::null, i18n("startDate for duration", "%1 for %2")
01440           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
01441           .arg(cont) );
01442     } else {
01443       QString cont;
01444       if ( per.start().date() == per.end().date() ) {
01445         cont = i18n("date, fromTime - toTime ", "%1, %2 - %3")
01446             .arg( KGlobal::locale()->formatDate( per.start().date() ) )
01447             .arg( KGlobal::locale()->formatTime( per.start().time() ) )
01448             .arg( KGlobal::locale()->formatTime( per.end().time() ) );
01449       } else {
01450         cont = i18n("fromDateTime - toDateTime", "%1 - %2")
01451           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
01452           .arg( KGlobal::locale()->formatDateTime( per.end(), false ) );
01453       }
01454 
01455       html += invitationRow( QString::null, cont );
01456     }
01457   }
01458 
01459   html += "</table>\n";
01460   return html;
01461 }
01462 
01463 static bool replyMeansCounter( Incidence */*incidence*/ )
01464 {
01465   return false;
01480 }
01481 
01482 static QString invitationHeaderEvent( Event *event, ScheduleMessage *msg )
01483 {
01484   if ( !msg || !event )
01485     return QString::null;
01486 
01487   switch ( msg->method() ) {
01488   case Scheduler::Publish:
01489     return i18n( "This invitation has been published" );
01490   case Scheduler::Request:
01491     if ( event->revision() > 0 ) {
01492       return i18n( "This invitation has been updated" );
01493     }
01494     if ( iamOrganizer( event ) ) {
01495       return i18n( "I sent this invitation" );
01496     } else {
01497       if ( !event->organizer().fullName().isEmpty() ) {
01498         return i18n( "You received an invitation from %1" ).
01499           arg( event->organizer().fullName() );
01500       } else {
01501         return i18n( "You received an invitation" );
01502       }
01503     }
01504   case Scheduler::Refresh:
01505     return i18n( "This invitation was refreshed" );
01506   case Scheduler::Cancel:
01507     return i18n( "This invitation has been canceled" );
01508   case Scheduler::Add:
01509     return i18n( "Addition to the invitation" );
01510   case Scheduler::Reply:
01511   {
01512     if ( replyMeansCounter( event ) ) {
01513       return i18n( "%1 makes this counter proposal" ).
01514         arg( firstAttendeeName( event, i18n( "Sender" ) ) );
01515     }
01516 
01517     Attendee::List attendees = event->attendees();
01518     if( attendees.count() == 0 ) {
01519       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01520       return QString::null;
01521     }
01522     if( attendees.count() != 1 ) {
01523       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01524                     << "but is " << attendees.count() << endl;
01525     }
01526     QString attendeeName = firstAttendeeName( event, i18n( "Sender" ) );
01527 
01528     QString delegatorName, dummy;
01529     Attendee* attendee = *attendees.begin();
01530     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
01531     if ( delegatorName.isEmpty() ) {
01532       delegatorName = attendee->delegator();
01533     }
01534 
01535     switch( attendee->status() ) {
01536     case Attendee::NeedsAction:
01537       return i18n( "%1 indicates this invitation still needs some action" ).arg( attendeeName );
01538     case Attendee::Accepted:
01539       if ( delegatorName.isEmpty() ) {
01540         return i18n( "%1 accepts this invitation" ).arg( attendeeName );
01541       } else {
01542         return i18n( "%1 accepts this invitation on behalf of %2" ).
01543           arg( attendeeName ).arg( delegatorName );
01544       }
01545     case Attendee::Tentative:
01546       if ( delegatorName.isEmpty() ) {
01547         return i18n( "%1 tentatively accepts this invitation" ).
01548           arg( attendeeName );
01549       } else {
01550         return i18n( "%1 tentatively accepts this invitation on behalf of %2" ).
01551           arg( attendeeName ).arg( delegatorName );
01552       }
01553     case Attendee::Declined:
01554       if ( delegatorName.isEmpty() ) {
01555         return i18n( "%1 declines this invitation" ).arg( attendeeName );
01556       } else {
01557         return i18n( "%1 declines this invitation on behalf of %2" ).
01558           arg( attendeeName ).arg( delegatorName );
01559       }
01560     case Attendee::Delegated: {
01561       QString delegate, dummy;
01562       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
01563       if ( delegate.isEmpty() ) {
01564         delegate = attendee->delegate();
01565       }
01566       if ( !delegate.isEmpty() ) {
01567         return i18n( "%1 has delegated this invitation to %2" ).
01568           arg( attendeeName ) .arg( delegate );
01569       } else {
01570         return i18n( "%1 has delegated this invitation" ).arg( attendeeName );
01571       }
01572     }
01573     case Attendee::Completed:
01574       return i18n( "This invitation is now completed" );
01575     case Attendee::InProcess:
01576       return i18n( "%1 is still processing the invitation" ).
01577         arg( attendeeName );
01578     default:
01579       return i18n( "Unknown response to this invitation" );
01580     }
01581     break;
01582   }
01583 
01584   case Scheduler::Counter:
01585     return i18n( "%1 makes this counter proposal" ).
01586       arg( firstAttendeeName( event, i18n( "Sender" ) ) );
01587 
01588   case Scheduler::Declinecounter:
01589     return i18n( "%1 declines the counter proposal" ).
01590       arg( firstAttendeeName( event, i18n( "Sender" ) ) );
01591 
01592   case Scheduler::NoMethod:
01593     return i18n("Error: iMIP message with unknown method: '%1'").
01594       arg( msg->method() );
01595   }
01596   return QString::null;
01597 }
01598 
01599 static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg )
01600 {
01601   if ( !msg || !todo ) {
01602     return QString::null;
01603   }
01604 
01605   switch ( msg->method() ) {
01606   case Scheduler::Publish:
01607     return i18n("This task has been published");
01608   case Scheduler::Request:
01609     if ( todo->revision() > 0 ) {
01610       return i18n( "This task has been updated" );
01611     } else {
01612       return i18n( "You have been assigned this task" );
01613     }
01614   case Scheduler::Refresh:
01615     return i18n( "This task was refreshed" );
01616   case Scheduler::Cancel:
01617     return i18n( "This task was canceled" );
01618   case Scheduler::Add:
01619     return i18n( "Addition to the task" );
01620   case Scheduler::Reply:
01621   {
01622     if ( replyMeansCounter( todo ) ) {
01623       return i18n( "%1 makes this counter proposal" ).
01624         arg( firstAttendeeName( todo, i18n( "Sender" ) ) );
01625     }
01626 
01627     Attendee::List attendees = todo->attendees();
01628     if( attendees.count() == 0 ) {
01629       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01630       return QString::null;
01631     }
01632     if( attendees.count() != 1 ) {
01633       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01634                     << "but is " << attendees.count() << endl;
01635     }
01636     QString attendeeName = firstAttendeeName( todo, i18n( "Sender" ) );
01637 
01638     QString delegatorName, dummy;
01639     Attendee* attendee = *attendees.begin();
01640     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
01641     if ( delegatorName.isEmpty() ) {
01642       delegatorName = attendee->delegator();
01643     }
01644 
01645     switch( attendee->status() ) {
01646     case Attendee::NeedsAction:
01647       return i18n( "%1 indicates this task assignment still needs some action" ).arg( attendeeName );
01648     case Attendee::Accepted:
01649       if ( delegatorName.isEmpty() ) {
01650         return i18n( "%1 accepts this task" ).arg( attendeeName );
01651       } else {
01652         return i18n( "%1 accepts this task on behalf of %2" ).
01653           arg( attendeeName ).arg( delegatorName );
01654       }
01655     case Attendee::Tentative:
01656       if ( delegatorName.isEmpty() ) {
01657         return i18n( "%1 tentatively accepts this task" ).
01658           arg( attendeeName );
01659       } else {
01660         return i18n( "%1 tentatively accepts this task on behalf of %2" ).
01661           arg( attendeeName ).arg( delegatorName );
01662       }
01663     case Attendee::Declined:
01664       if ( delegatorName.isEmpty() ) {
01665         return i18n( "%1 declines this task" ).arg( attendeeName );
01666       } else {
01667         return i18n( "%1 declines this task on behalf of %2" ).
01668           arg( attendeeName ).arg( delegatorName );
01669       }
01670     case Attendee::Delegated: {
01671       QString delegate, dummy;
01672       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
01673       if ( delegate.isEmpty() ) {
01674         delegate = attendee->delegate();
01675       }
01676       if ( !delegate.isEmpty() ) {
01677         return i18n( "%1 has delegated this request for the task to %2" ).
01678           arg( attendeeName ).arg( delegate );
01679       } else {
01680         return i18n( "%1 has delegated this request for the task" ).
01681           arg( attendeeName );
01682       }
01683     }
01684     case Attendee::Completed:
01685       return i18n( "The request for this task is now completed" );
01686     case Attendee::InProcess:
01687       return i18n( "%1 is still processing the task" ).
01688         arg( attendeeName );
01689     default:
01690       return i18n( "Unknown response to this task" );
01691     }
01692     break;
01693   }
01694 
01695   case Scheduler::Counter:
01696     return i18n( "%1 makes this counter proposal" ).
01697       arg( firstAttendeeName( todo, i18n( "Sender" ) ) );
01698 
01699   case Scheduler::Declinecounter:
01700     return i18n( "%1 declines the counter proposal" ).
01701       arg( firstAttendeeName( todo, i18n( "Sender" ) ) );
01702 
01703   case Scheduler::NoMethod:
01704     return i18n( "Error: iMIP message with unknown method: '%1'" ).
01705       arg( msg->method() );
01706   }
01707   return QString::null;
01708 }
01709 
01710 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
01711 {
01712   if ( !msg || !journal ) {
01713     return QString::null;
01714   }
01715 
01716   switch ( msg->method() ) {
01717   case Scheduler::Publish:
01718     return i18n("This journal has been published");
01719   case Scheduler::Request:
01720     return i18n( "You have been assigned this journal" );
01721   case Scheduler::Refresh:
01722     return i18n( "This journal was refreshed" );
01723   case Scheduler::Cancel:
01724     return i18n( "This journal was canceled" );
01725   case Scheduler::Add:
01726     return i18n( "Addition to the journal" );
01727   case Scheduler::Reply:
01728   {
01729     if ( replyMeansCounter( journal ) ) {
01730       return i18n( "Sender makes this counter proposal" );
01731     }
01732 
01733     Attendee::List attendees = journal->attendees();
01734     if( attendees.count() == 0 ) {
01735       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01736       return QString::null;
01737     }
01738     if( attendees.count() != 1 ) {
01739       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01740                     << "but is " << attendees.count() << endl;
01741     }
01742     Attendee* attendee = *attendees.begin();
01743 
01744     switch( attendee->status() ) {
01745     case Attendee::NeedsAction:
01746       return i18n( "Sender indicates this journal assignment still needs some action" );
01747     case Attendee::Accepted:
01748       return i18n( "Sender accepts this journal" );
01749     case Attendee::Tentative:
01750       return i18n( "Sender tentatively accepts this journal" );
01751     case Attendee::Declined:
01752       return i18n( "Sender declines this journal" );
01753     case Attendee::Delegated:
01754       return i18n( "Sender has delegated this request for the journal" );
01755     case Attendee::Completed:
01756       return i18n( "The request for this journal is now completed" );
01757     case Attendee::InProcess:
01758       return i18n( "Sender is still processing the invitation" );
01759     default:
01760       return i18n( "Unknown response to this journal" );
01761     }
01762     break;
01763   }
01764   case Scheduler::Counter:
01765     return i18n( "Sender makes this counter proposal" );
01766   case Scheduler::Declinecounter:
01767     return i18n( "Sender declines the counter proposal" );
01768   case Scheduler::NoMethod:
01769     return i18n("Error: iMIP message with unknown method: '%1'").
01770       arg( msg->method() );
01771   }
01772   return QString::null;
01773 }
01774 
01775 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
01776 {
01777   if ( !msg || !fb ) {
01778     return QString::null;
01779   }
01780 
01781   switch ( msg->method() ) {
01782   case Scheduler::Publish:
01783     return i18n("This free/busy list has been published");
01784   case Scheduler::Request:
01785     return i18n( "The free/busy list has been requested" );
01786   case Scheduler::Refresh:
01787     return i18n( "This free/busy list was refreshed" );
01788   case Scheduler::Cancel:
01789     return i18n( "This free/busy list was canceled" );
01790   case Scheduler::Add:
01791     return i18n( "Addition to the free/busy list" );
01792   case Scheduler::NoMethod:
01793   default:
01794     return i18n("Error: Free/Busy iMIP message with unknown method: '%1'").
01795       arg( msg->method() );
01796   }
01797 }
01798 
01799 static QString invitationAttendees( Incidence *incidence )
01800 {
01801   QString tmpStr;
01802   if ( !incidence ) {
01803     return tmpStr;
01804   }
01805 
01806   tmpStr += htmlAddTag( "u", i18n( "Attendee List" ) );
01807   tmpStr += "<br/>";
01808 
01809   int count=0;
01810   Attendee::List attendees = incidence->attendees();
01811   if ( !attendees.isEmpty() ) {
01812 
01813     Attendee::List::ConstIterator it;
01814     for( it = attendees.begin(); it != attendees.end(); ++it ) {
01815       Attendee *a = *it;
01816       if ( !iamAttendee( a ) ) {
01817         count++;
01818         if ( count == 1 ) {
01819           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\" columns=\"2\">";
01820         }
01821         tmpStr += "<tr>";
01822         tmpStr += "<td>";
01823         tmpStr += invitationPerson( a->email(), a->name(), QString::null );
01824         if ( !a->delegator().isEmpty() ) {
01825           tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
01826         }
01827         if ( !a->delegate().isEmpty() ) {
01828           tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
01829         }
01830         tmpStr += "</td>";
01831         tmpStr += "<td>" + a->statusStr() + "</td>";
01832         tmpStr += "</tr>";
01833       }
01834     }
01835   }
01836   if ( count ) {
01837     tmpStr += "</table>";
01838   } else {
01839     tmpStr += "<i>" + i18n( "No attendee", "None" ) + "</i>";
01840   }
01841 
01842   return tmpStr;
01843 }
01844 
01845 static QString invitationAttachments( InvitationFormatterHelper *helper, Incidence *incidence )
01846 {
01847   QString tmpStr;
01848   if ( !incidence ) {
01849     return tmpStr;
01850   }
01851 
01852   Attachment::List attachments = incidence->attachments();
01853   if ( !attachments.isEmpty() ) {
01854     tmpStr += i18n( "Attached Documents:" ) + "<ol>";
01855 
01856     Attachment::List::ConstIterator it;
01857     for( it = attachments.begin(); it != attachments.end(); ++it ) {
01858       Attachment *a = *it;
01859       tmpStr += "<li>";
01860       // Attachment icon
01861       KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
01862       const QString iconStr = mimeType ? mimeType->icon( a->uri(), false ) : QString( "application-octet-stream" );
01863       const QString iconPath = KGlobal::iconLoader()->iconPath( iconStr, KIcon::Small );
01864       if ( !iconPath.isEmpty() ) {
01865         tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
01866       }
01867       tmpStr += helper->makeLink( "ATTACH:" + a->label(), a->label() );
01868       tmpStr += "</li>";
01869     }
01870     tmpStr += "</ol>";
01871   }
01872 
01873   return tmpStr;
01874 }
01875 
01876 class IncidenceFormatter::ScheduleMessageVisitor
01877   : public IncidenceBase::Visitor
01878 {
01879   public:
01880     ScheduleMessageVisitor() : mMessage(0) { mResult = ""; }
01881     bool act( IncidenceBase *incidence, ScheduleMessage *msg )
01882     {
01883       mMessage = msg;
01884       return incidence->accept( *this );
01885     }
01886     QString result() const { return mResult; }
01887 
01888   protected:
01889     QString mResult;
01890     ScheduleMessage *mMessage;
01891 };
01892 
01893 class IncidenceFormatter::InvitationHeaderVisitor
01894   : public IncidenceFormatter::ScheduleMessageVisitor
01895 {
01896   protected:
01897     bool visit( Event *event )
01898     {
01899       mResult = invitationHeaderEvent( event, mMessage );
01900       return !mResult.isEmpty();
01901     }
01902     bool visit( Todo *todo )
01903     {
01904       mResult = invitationHeaderTodo( todo, mMessage );
01905       return !mResult.isEmpty();
01906     }
01907     bool visit( Journal *journal )
01908     {
01909       mResult = invitationHeaderJournal( journal, mMessage );
01910       return !mResult.isEmpty();
01911     }
01912     bool visit( FreeBusy *fb )
01913     {
01914       mResult = invitationHeaderFreeBusy( fb, mMessage );
01915       return !mResult.isEmpty();
01916     }
01917 };
01918 
01919 class IncidenceFormatter::InvitationBodyVisitor
01920   : public IncidenceFormatter::ScheduleMessageVisitor
01921 {
01922   public:
01923     InvitationBodyVisitor( bool noHtmlMode )
01924       : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ) {}
01925 
01926   protected:
01927     bool visit( Event *event )
01928     {
01929       mResult = invitationDetailsEvent( event, mNoHtmlMode );
01930       return !mResult.isEmpty();
01931     }
01932     bool visit( Todo *todo )
01933     {
01934       mResult = invitationDetailsTodo( todo, mNoHtmlMode );
01935       return !mResult.isEmpty();
01936     }
01937     bool visit( Journal *journal )
01938     {
01939       mResult = invitationDetailsJournal( journal, mNoHtmlMode );
01940       return !mResult.isEmpty();
01941     }
01942     bool visit( FreeBusy *fb )
01943     {
01944       mResult = invitationDetailsFreeBusy( fb, mNoHtmlMode );
01945       return !mResult.isEmpty();
01946     }
01947 
01948   private:
01949     bool mNoHtmlMode;
01950 };
01951 
01952 class IncidenceFormatter::IncidenceCompareVisitor
01953   : public IncidenceBase::Visitor
01954 {
01955   public:
01956     IncidenceCompareVisitor() : mExistingIncidence(0) {}
01957     bool act( IncidenceBase *incidence, Incidence* existingIncidence )
01958     {
01959       Incidence *inc = dynamic_cast<Incidence*>( incidence );
01960       if ( !inc || !existingIncidence || inc->revision() <= existingIncidence->revision() )
01961         return false;
01962       mExistingIncidence = existingIncidence;
01963       return incidence->accept( *this );
01964     }
01965 
01966     QString result() const
01967     {
01968       if ( mChanges.isEmpty() ) {
01969         return QString::null;
01970       }
01971       QString html = "<div align=\"left\"><ul><li>";
01972       html += mChanges.join( "</li><li>" );
01973       html += "</li><ul></div>";
01974       return html;
01975     }
01976 
01977   protected:
01978     bool visit( Event *event )
01979     {
01980       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
01981       compareIncidences( event, mExistingIncidence );
01982       return !mChanges.isEmpty();
01983     }
01984     bool visit( Todo *todo )
01985     {
01986       compareIncidences( todo, mExistingIncidence );
01987       return !mChanges.isEmpty();
01988     }
01989     bool visit( Journal *journal )
01990     {
01991       compareIncidences( journal, mExistingIncidence );
01992       return !mChanges.isEmpty();
01993     }
01994     bool visit( FreeBusy *fb )
01995     {
01996       Q_UNUSED( fb );
01997       return !mChanges.isEmpty();
01998     }
01999 
02000   private:
02001     void compareEvents( Event *newEvent, Event *oldEvent )
02002     {
02003       if ( !oldEvent || !newEvent )
02004         return;
02005       if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->doesFloat() != newEvent->doesFloat() )
02006         mChanges += i18n( "The invitation starting time has been changed from %1 to %2" )
02007                     .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) );
02008       if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->doesFloat() != newEvent->doesFloat() )
02009         mChanges += i18n( "The invitation ending time has been changed from %1 to %2" )
02010                     .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) );
02011     }
02012 
02013     void compareIncidences( Incidence *newInc, Incidence *oldInc )
02014     {
02015       if ( !oldInc || !newInc )
02016         return;
02017       if ( oldInc->summary() != newInc->summary() )
02018         mChanges += i18n( "The summary has been changed to: \"%1\"" ).arg( newInc->summary() );
02019       if ( oldInc->location() != newInc->location() )
02020         mChanges += i18n( "The location has been changed to: \"%1\"" ).arg( newInc->location() );
02021       if ( oldInc->description() != newInc->description() )
02022         mChanges += i18n( "The description has been changed to: \"%1\"" ).arg( newInc->description() );
02023       Attendee::List oldAttendees = oldInc->attendees();
02024       Attendee::List newAttendees = newInc->attendees();
02025       for ( Attendee::List::ConstIterator it = newAttendees.constBegin(); it != newAttendees.constEnd(); ++it ) {
02026         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
02027         if ( !oldAtt ) {
02028           mChanges += i18n( "Attendee %1 has been added" ).arg( (*it)->fullName() );
02029         } else {
02030           if ( oldAtt->status() != (*it)->status() )
02031             mChanges += i18n( "The status of attendee %1 has been changed to: %2" ).arg( (*it)->fullName() )
02032                         .arg( (*it)->statusStr() );
02033         }
02034       }
02035       for ( Attendee::List::ConstIterator it = oldAttendees.constBegin(); it != oldAttendees.constEnd(); ++it ) {
02036         Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
02037         if ( !newAtt )
02038           mChanges += i18n( "Attendee %1 has been removed" ).arg( (*it)->fullName() );
02039       }
02040     }
02041 
02042   private:
02043     Incidence* mExistingIncidence;
02044     QStringList mChanges;
02045 };
02046 
02047 
02048 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
02049 {
02050   if ( !id.startsWith( "ATTACH:" ) ) {
02051     QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
02052                   arg( generateLinkURL( id ), text );
02053     return res;
02054   } else {
02055     // draw the attachment links in non-bold face
02056     QString res = QString( "<a href=\"%1\">%2</a>" ).
02057                   arg( generateLinkURL( id ), text );
02058     return res;
02059   }
02060 }
02061 
02062 // Check if the given incidence is likely one that we own instead one from
02063 // a shared calendar (Kolab-specific)
02064 static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence )
02065 {
02066   CalendarResources *cal = dynamic_cast<CalendarResources*>( calendar );
02067   if ( !cal || !incidence ) {
02068     return true;
02069   }
02070   ResourceCalendar *res = cal->resource( incidence );
02071   if ( !res ) {
02072     return true;
02073   }
02074   const QString subRes = res->subresourceIdentifier( incidence );
02075   if ( !subRes.contains( "/.INBOX.directory/" ) ) {
02076     return false;
02077   }
02078   return true;
02079 }
02080 
02081 // The spacer for the invitation buttons
02082 static QString spacer = "<td> &nbsp; </td>";
02083 // The open & close table cell tags for the invitation buttons
02084 static QString tdOpen = "<td>";
02085 static QString tdClose = "</td>" + spacer;
02086 
02087 static QString responseButtons( Incidence *inc, bool rsvpReq, bool rsvpRec,
02088                                 InvitationFormatterHelper *helper )
02089 {
02090   QString html;
02091   if ( !helper ) {
02092     return html;
02093   }
02094 
02095   if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) {
02096     // Record only
02097     html += tdOpen;
02098     html += helper->makeLink( "record", i18n( "[Record]" ) );
02099     html += tdClose;
02100 
02101     // Move to trash
02102     html += tdOpen;
02103     html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) );
02104     html += tdClose;
02105 
02106   } else {
02107 
02108     // Accept
02109     html += tdOpen;
02110     html += helper->makeLink( "accept", i18n( "[Accept]" ) );
02111     html += tdClose;
02112 
02113     // Tentative
02114     html += tdOpen;
02115     html += helper->makeLink( "accept_conditionally",
02116                               i18n( "Accept conditionally", "[Accept cond.]" ) );
02117     html += tdClose;
02118 
02119     // Counter proposal
02120     html += tdOpen;
02121     html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
02122     html += tdClose;
02123 
02124     // Decline
02125     html += tdOpen;
02126     html += helper->makeLink( "decline", i18n( "[Decline]" ) );
02127     html += tdClose;
02128   }
02129 
02130   if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
02131     // Delegate
02132     html += tdOpen;
02133     html += helper->makeLink( "delegate", i18n( "[Delegate]" ) );
02134     html += tdClose;
02135 
02136     // Forward
02137     html += tdOpen;
02138     html += helper->makeLink( "forward", i18n( "[Forward]" ) );
02139     html += tdClose;
02140 
02141     // Check calendar
02142     if ( inc && inc->type() == "Event" ) {
02143       html += tdOpen;
02144       html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
02145       html += tdClose;
02146     }
02147   }
02148   return html;
02149 }
02150 
02151 static QString counterButtons( Incidence *incidence,
02152                                InvitationFormatterHelper *helper )
02153 {
02154   QString html;
02155   if ( !helper ) {
02156     return html;
02157   }
02158 
02159   // Accept proposal
02160   html += tdOpen;
02161   html += helper->makeLink( "accept_counter", i18n("[Accept]") );
02162   html += tdClose;
02163 
02164   // Decline proposal
02165   html += tdOpen;
02166   html += helper->makeLink( "decline_counter", i18n("[Decline]") );
02167   html += tdClose;
02168 
02169   // Check calendar
02170   if ( incidence && incidence->type() == "Event" ) {
02171     html += tdOpen;
02172     html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
02173     html += tdClose;
02174   }
02175   return html;
02176 }
02177 
02178 QString IncidenceFormatter::formatICalInvitationHelper( QString invitation,
02179                                                         Calendar *mCalendar,
02180                                                         InvitationFormatterHelper *helper,
02181                                                         bool noHtmlMode )
02182 {
02183   if ( invitation.isEmpty() ) {
02184     return QString::null;
02185   }
02186 
02187   ICalFormat format;
02188   // parseScheduleMessage takes the tz from the calendar, no need to set it manually here for the format!
02189   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
02190 
02191   if( !msg ) {
02192     kdDebug( 5850 ) << "Failed to parse the scheduling message" << endl;
02193     Q_ASSERT( format.exception() );
02194     kdDebug( 5850 ) << format.exception()->message() << endl;
02195     return QString::null;
02196   }
02197 
02198   IncidenceBase *incBase = msg->event();
02199 
02200   // Determine if this incidence is in my calendar (and owned by me)
02201   Incidence *existingIncidence = 0;
02202   if ( incBase && helper->calendar() ) {
02203     existingIncidence = helper->calendar()->incidence( incBase->uid() );
02204     if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
02205       existingIncidence = 0;
02206     }
02207     if ( !existingIncidence ) {
02208       const Incidence::List list = helper->calendar()->incidences();
02209       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
02210         if ( (*it)->schedulingID() == incBase->uid() &&
02211              incidenceOwnedByMe( helper->calendar(), *it ) ) {
02212           existingIncidence = *it;
02213           break;
02214         }
02215       }
02216     }
02217   }
02218 
02219   // First make the text of the message
02220   QString html;
02221 
02222   QString tableStyle = QString::fromLatin1(
02223     "style=\"border: solid 1px; margin: 0em;\"" );
02224   QString tableHead = QString::fromLatin1(
02225     "<div align=\"center\">"
02226     "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
02227     "<tr><td>").arg(tableStyle);
02228 
02229   html += tableHead;
02230   InvitationHeaderVisitor headerVisitor;
02231   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
02232   if ( !headerVisitor.act( incBase, msg ) )
02233     return QString::null;
02234   html += "<b>" + headerVisitor.result() + "</b>";
02235 
02236   InvitationBodyVisitor bodyVisitor( noHtmlMode );
02237   if ( !bodyVisitor.act( incBase, msg ) )
02238     return QString::null;
02239   html += bodyVisitor.result();
02240 
02241   if ( msg->method() == Scheduler::Request ) { // ### Scheduler::Publish/Refresh/Add as well?
02242     IncidenceCompareVisitor compareVisitor;
02243     if ( compareVisitor.act( incBase, existingIncidence ) ) {
02244       html += i18n("<p align=\"left\">The following changes have been made by the organizer:</p>");
02245       html += compareVisitor.result();
02246     }
02247   }
02248 
02249   Incidence *inc = dynamic_cast<Incidence*>( incBase );
02250 
02251   // determine if I am the organizer for this invitation
02252   bool myInc = iamOrganizer( inc );
02253 
02254   // determine if the invitation response has already been recorded
02255   bool rsvpRec = false;
02256   Attendee *ea = 0;
02257   if ( !myInc ) {
02258     Incidence *rsvpIncidence = existingIncidence;
02259     if ( !rsvpIncidence && inc && inc->revision() > 0 ) {
02260       rsvpIncidence = inc;
02261     }
02262     if ( rsvpIncidence ) {
02263       ea = findMyAttendee( rsvpIncidence );
02264     }
02265     if ( ea &&
02266          ( ea->status() == Attendee::Accepted ||
02267            ea->status() == Attendee::Declined ||
02268            ea->status() == Attendee::Tentative ) ) {
02269       rsvpRec = true;
02270     }
02271   }
02272 
02273   // determine invitation role
02274   QString role;
02275   bool isDelegated = false;
02276   Attendee *a = findMyAttendee( inc );
02277   if ( !a && inc ) {
02278     if ( !inc->attendees().isEmpty() ) {
02279       a = inc->attendees().first();
02280     }
02281   }
02282   if ( a ) {
02283     isDelegated = ( a->status() == Attendee::Delegated );
02284     role = Attendee::roleName( a->role() );
02285   }
02286 
02287   // Print if RSVP needed, not-needed, or response already recorded
02288   bool rsvpReq = rsvpRequested( inc );
02289   if ( !myInc && a ) {
02290     html += "<br/>";
02291     html += "<i><u>";
02292     if ( rsvpRec && inc ) {
02293       if ( inc->revision() == 0 ) {
02294         html += i18n( "Your <b>%1</b> response has already been recorded" ).
02295                 arg( ea->statusStr() );
02296       } else {
02297         html += i18n( "Your status for this invitation is <b>%1</b>" ).
02298                 arg( ea->statusStr() );
02299       }
02300       rsvpReq = false;
02301     } else if ( msg->method() == Scheduler::Cancel ) {
02302       html += i18n( "This invitation was declined" );
02303     } else if ( msg->method() == Scheduler::Add ) {
02304       html += i18n( "This invitation was accepted" );
02305     } else {
02306       if ( !isDelegated ) {
02307         html += rsvpRequestedStr( rsvpReq, role );
02308       } else {
02309         html += i18n( "Awaiting delegation response" );
02310       }
02311     }
02312     html += "</u></i>";
02313   }
02314 
02315   // Print if the organizer gave you a preset status
02316   if ( !myInc ) {
02317     if ( inc && inc->revision() == 0 ) {
02318       QString statStr = myStatusStr( inc );
02319       if ( !statStr.isEmpty() ) {
02320         html += "<br/>";
02321         html += "<i>";
02322         html += statStr;
02323         html += "</i>";
02324       }
02325     }
02326   }
02327 
02328   // Add groupware links
02329 
02330   html += "<br><table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td></tr>";
02331 
02332   switch ( msg->method() ) {
02333     case Scheduler::Publish:
02334     case Scheduler::Request:
02335     case Scheduler::Refresh:
02336     case Scheduler::Add:
02337     {
02338       if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
02339         html += "<tr>";
02340         if ( inc->type() == "Todo" ) {
02341           html += "<td colspan=\"9\">";
02342           html += helper->makeLink( "reply", i18n( "[Record invitation in my task list]" ) );
02343         } else {
02344           html += "<td colspan=\"13\">";
02345           html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) );
02346         }
02347         html += "</td></tr>";
02348       }
02349 
02350       if ( !myInc && a ) {
02351         html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
02352       }
02353       break;
02354     }
02355 
02356     case Scheduler::Cancel:
02357       // Remove invitation
02358       html += "<tr>";
02359       if ( inc->type() == "Todo" ) {
02360         html += "<td colspan=\"9\">";
02361         html += helper->makeLink( "cancel", i18n( "[Remove invitation from my task list]" ) );
02362       } else {
02363         html += "<td colspan=\"13\">";
02364         html += helper->makeLink( "cancel", i18n( "[Remove invitation from my calendar]" ) );
02365       }
02366       html += "</td></tr>";
02367       break;
02368 
02369     case Scheduler::Reply:
02370     {
02371       // Record invitation response
02372       Attendee *a = 0;
02373       Attendee *ea = 0;
02374       if ( inc ) {
02375         // First, determine if this reply is really a counter in disguise.
02376         if ( replyMeansCounter( inc ) ) {
02377           html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
02378           break;
02379         }
02380 
02381         // Next, maybe this is a declined reply that was delegated from me?
02382         // find first attendee who is delegated-from me
02383         // look a their PARTSTAT response, if the response is declined,
02384         // then we need to start over which means putting all the action
02385         // buttons and NOT putting on the [Record response..] button
02386         a = findDelegatedFromMyAttendee( inc );
02387         if ( a ) {
02388           if ( a->status() != Attendee::Accepted ||
02389                a->status() != Attendee::Tentative ) {
02390             html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
02391             break;
02392           }
02393         }
02394 
02395         // Finally, simply allow a Record of the reply
02396         if ( !inc->attendees().isEmpty() ) {
02397           a = inc->attendees().first();
02398         }
02399         if ( a ) {
02400           ea = findAttendee( existingIncidence, a->email() );
02401         }
02402       }
02403       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
02404         if ( inc && inc->revision() > 0 ) {
02405           html += "<br><u><i>";
02406           html += i18n( "The response has been recorded [%1]" ).arg( ea->statusStr() );
02407           html += "</i></u>";
02408         }
02409       } else {
02410         if ( inc ) {
02411           html += "<tr><td>";
02412           if ( inc->type() == "Todo" ) {
02413             html += helper->makeLink( "reply", i18n( "[Record response in my task list]" ) );
02414           } else {
02415             html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
02416           }
02417           html += "</td></tr>";
02418         }
02419       }
02420       break;
02421     }
02422 
02423     case Scheduler::Counter:
02424       // Counter proposal
02425       html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
02426       break;
02427 
02428     case Scheduler::Declinecounter:
02429     case Scheduler::NoMethod:
02430       break;
02431   }
02432 
02433   // close the groupware table
02434   html += "</td></tr></table>";
02435 
02436   // Add the attendee list if I am the organizer
02437   if ( myInc && helper->calendar() ) {
02438     html += invitationAttendees( helper->calendar()->incidence( inc->uid() ) );
02439   }
02440 
02441   // close the top-level table
02442   html += "</td></tr></table><br></div>";
02443 
02444   // Add the attachment list
02445   html += invitationAttachments( helper, inc );
02446 
02447   return html;
02448 }
02449 
02450 QString IncidenceFormatter::formatICalInvitation( QString invitation,
02451                                                   Calendar *mCalendar,
02452                                                   InvitationFormatterHelper *helper )
02453 {
02454   return formatICalInvitationHelper( invitation, mCalendar, helper, false );
02455 }
02456 
02457 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
02458                                                         Calendar *mCalendar,
02459                                                         InvitationFormatterHelper *helper )
02460 {
02461   return formatICalInvitationHelper( invitation, mCalendar, helper, true );
02462 }
02463 
02464 /*******************************************************************
02465  *  Helper functions for the msTNEF -> VPart converter
02466  *******************************************************************/
02467 
02468 
02469 //-----------------------------------------------------------------------------
02470 
02471 static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
02472                            const QString& fallback = QString::null)
02473 {
02474   return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
02475                             fallback );
02476 }
02477 
02478 static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
02479                            const QString& fallback = QString::null )
02480 {
02481   return tnefMsg->findNamedProp( name, fallback );
02482 }
02483 
02484 struct save_tz { char* old_tz; char* tz_env_str; };
02485 
02486 /* temporarily go to a different timezone */
02487 static struct save_tz set_tz( const char* _tc )
02488 {
02489   const char *tc = _tc?_tc:"UTC";
02490 
02491   struct save_tz rv;
02492 
02493   rv.old_tz = 0;
02494   rv.tz_env_str = 0;
02495 
02496   //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;
02497 
02498   char* tz_env = 0;
02499   if( getenv( "TZ" ) ) {
02500     tz_env = strdup( getenv( "TZ" ) );
02501     rv.old_tz = tz_env;
02502   }
02503   char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
02504   strcpy( tmp_env, "TZ=" );
02505   strcpy( tmp_env+3, tc );
02506   putenv( tmp_env );
02507 
02508   rv.tz_env_str = tmp_env;
02509 
02510   /* tmp_env is not free'ed -- it is part of the environment */
02511 
02512   tzset();
02513   //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;
02514 
02515   return rv;
02516 }
02517 
02518 /* restore previous timezone */
02519 static void unset_tz( struct save_tz old_tz )
02520 {
02521   if( old_tz.old_tz ) {
02522     char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
02523     strcpy( tmp_env, "TZ=" );
02524     strcpy( tmp_env+3, old_tz.old_tz );
02525     putenv( tmp_env );
02526     /* tmp_env is not free'ed -- it is part of the environment */
02527     free( old_tz.old_tz );
02528   } else {
02529     /* clear TZ from env */
02530     putenv( strdup("TZ") );
02531   }
02532   tzset();
02533 
02534   /* is this OK? */
02535   if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
02536 }
02537 
02538 static QDateTime utc2Local( const QDateTime& utcdt )
02539 {
02540   struct tm tmL;
02541 
02542   save_tz tmp_tz = set_tz("UTC");
02543   time_t utc = utcdt.toTime_t();
02544   unset_tz( tmp_tz );
02545 
02546   localtime_r( &utc, &tmL );
02547   return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
02548                     QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
02549 }
02550 
02551 
02552 static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
02553                                           bool bDateOnly = false )
02554 {
02555   QDate tmpDate;
02556   QTime tmpTime;
02557   int year, month, day, hour, minute, second;
02558 
02559   if( bDateOnly ) {
02560     year = dtStr.left( 4 ).toInt();
02561     month = dtStr.mid( 4, 2 ).toInt();
02562     day = dtStr.mid( 6, 2 ).toInt();
02563     hour = 0;
02564     minute = 0;
02565     second = 0;
02566   } else {
02567     year = dtStr.left( 4 ).toInt();
02568     month = dtStr.mid( 4, 2 ).toInt();
02569     day = dtStr.mid( 6, 2 ).toInt();
02570     hour = dtStr.mid( 9, 2 ).toInt();
02571     minute = dtStr.mid( 11, 2 ).toInt();
02572     second = dtStr.mid( 13, 2 ).toInt();
02573   }
02574   tmpDate.setYMD( year, month, day );
02575   tmpTime.setHMS( hour, minute, second );
02576 
02577   if( tmpDate.isValid() && tmpTime.isValid() ) {
02578     QDateTime dT = QDateTime( tmpDate, tmpTime );
02579 
02580     if( !bDateOnly ) {
02581       // correct for GMT ( == Zulu time == UTC )
02582       if (dtStr.at(dtStr.length()-1) == 'Z') {
02583         //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
02584         //localUTCOffset( dT ) );
02585         dT = utc2Local( dT );
02586       }
02587     }
02588     return dT;
02589   } else
02590     return QDateTime();
02591 }
02592 
02593 
02594 
02595 QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef )
02596 {
02597   bool bOk = false;
02598 
02599   KTNEFParser parser;
02600   QBuffer buf( tnef );
02601   CalendarLocal cal ( QString::fromLatin1( "UTC" ) );
02602   KABC::Addressee addressee;
02603   KABC::VCardConverter cardConv;
02604   ICalFormat calFormat;
02605   Event* event = new Event();
02606 
02607   if( parser.openDevice( &buf ) ) {
02608     KTNEFMessage* tnefMsg = parser.message();
02609     //QMap<int,KTNEFProperty*> props = parser.message()->properties();
02610 
02611     // Everything depends from property PR_MESSAGE_CLASS
02612     // (this is added by KTNEFParser):
02613     QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
02614       .upper();
02615     if( !msgClass.isEmpty() ) {
02616       // Match the old class names that might be used by Outlook for
02617       // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
02618       bool bCompatClassAppointment = false;
02619       bool bCompatMethodRequest = false;
02620       bool bCompatMethodCancled = false;
02621       bool bCompatMethodAccepted = false;
02622       bool bCompatMethodAcceptedCond = false;
02623       bool bCompatMethodDeclined = false;
02624       if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
02625         bCompatClassAppointment = true;
02626         if( msgClass.endsWith( ".MTGREQ" ) )
02627           bCompatMethodRequest = true;
02628         if( msgClass.endsWith( ".MTGCNCL" ) )
02629           bCompatMethodCancled = true;
02630         if( msgClass.endsWith( ".MTGRESPP" ) )
02631           bCompatMethodAccepted = true;
02632         if( msgClass.endsWith( ".MTGRESPA" ) )
02633           bCompatMethodAcceptedCond = true;
02634         if( msgClass.endsWith( ".MTGRESPN" ) )
02635           bCompatMethodDeclined = true;
02636       }
02637       bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );
02638 
02639       if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
02640         // Compose a vCal
02641         bool bIsReply = false;
02642         QString prodID = "-//Microsoft Corporation//Outlook ";
02643         prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
02644         prodID += "MIMEDIR/EN\n";
02645         prodID += "VERSION:2.0\n";
02646         calFormat.setApplication( "Outlook", prodID );
02647 
02648         Scheduler::Method method;
02649         if( bCompatMethodRequest )
02650           method = Scheduler::Request;
02651         else if( bCompatMethodCancled )
02652           method = Scheduler::Cancel;
02653         else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
02654                  bCompatMethodDeclined ) {
02655           method = Scheduler::Reply;
02656           bIsReply = true;
02657         } else {
02658           // pending(khz): verify whether "0x0c17" is the right tag ???
02659           //
02660           // at the moment we think there are REQUESTS and UPDATES
02661           //
02662           // but WHAT ABOUT REPLIES ???
02663           //
02664           //
02665 
02666           if( tnefMsg->findProp(0x0c17) == "1" )
02667             bIsReply = true;
02668           method = Scheduler::Request;
02669         }
02670 
02672         ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );
02673 
02674         QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );
02675 
02676         if( !sSenderSearchKeyEmail.isEmpty() ) {
02677           int colon = sSenderSearchKeyEmail.find( ':' );
02678           // May be e.g. "SMTP:KHZ@KDE.ORG"
02679           if( sSenderSearchKeyEmail.find( ':' ) == -1 )
02680             sSenderSearchKeyEmail.remove( 0, colon+1 );
02681         }
02682 
02683         QString s( tnefMsg->findProp( 0x0e04 ) );
02684         QStringList attendees = QStringList::split( ';', s );
02685         if( attendees.count() ) {
02686           for( QStringList::Iterator it = attendees.begin();
02687                it != attendees.end(); ++it ) {
02688             // Skip all entries that have no '@' since these are
02689             // no mail addresses
02690             if( (*it).find('@') == -1 ) {
02691               s = (*it).stripWhiteSpace();
02692 
02693               Attendee *attendee = new Attendee( s, s, true );
02694               if( bIsReply ) {
02695                 if( bCompatMethodAccepted )
02696                   attendee->setStatus( Attendee::Accepted );
02697                 if( bCompatMethodDeclined )
02698                   attendee->setStatus( Attendee::Declined );
02699                 if( bCompatMethodAcceptedCond )
02700                   attendee->setStatus(Attendee::Tentative);
02701               } else {
02702                 attendee->setStatus( Attendee::NeedsAction );
02703                 attendee->setRole( Attendee::ReqParticipant );
02704               }
02705               event->addAttendee(attendee);
02706             }
02707           }
02708         } else {
02709           // Oops, no attendees?
02710           // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
02711           s = sSenderSearchKeyEmail;
02712           if( !s.isEmpty() ) {
02713             Attendee *attendee = new Attendee( QString::null, QString::null,
02714                                                true );
02715             if( bIsReply ) {
02716               if( bCompatMethodAccepted )
02717                 attendee->setStatus( Attendee::Accepted );
02718               if( bCompatMethodAcceptedCond )
02719                 attendee->setStatus( Attendee::Declined );
02720               if( bCompatMethodDeclined )
02721                 attendee->setStatus( Attendee::Tentative );
02722             } else {
02723               attendee->setStatus(Attendee::NeedsAction);
02724               attendee->setRole(Attendee::ReqParticipant);
02725             }
02726             event->addAttendee(attendee);
02727           }
02728         }
02729         s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
02730         if( s.isEmpty() && !bIsReply )
02731           s = sSenderSearchKeyEmail;
02732         // TODO: Use the common name?
02733         if( !s.isEmpty() )
02734           event->setOrganizer( s );
02735 
02736         s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
02737           .replace( QChar( ':' ), QString::null );
02738         event->setDtStart( QDateTime::fromString( s ) ); // ## Format??
02739 
02740         s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
02741           .replace( QChar( ':' ), QString::null );
02742         event->setDtEnd( QDateTime::fromString( s ) );
02743 
02744         s = tnefMsg->findProp( 0x8208 );
02745         event->setLocation( s );
02746 
02747         // is it OK to set this to OPAQUE always ??
02748         //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
02749         //vPart += "SEQUENCE:0\n";
02750 
02751         // is "0x0023" OK  -  or should we look for "0x0003" ??
02752         s = tnefMsg->findProp( 0x0023 );
02753         event->setUid( s );
02754 
02755         // PENDING(khz): is this value in local timezone? Must it be
02756         // adjusted? Most likely this is a bug in the server or in
02757         // Outlook - we ignore it for now.
02758         s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
02759           .replace( QChar( ':' ), QString::null );
02760         // ### libkcal always uses currentDateTime()
02761         // event->setDtStamp(QDateTime::fromString(s));
02762 
02763         s = tnefMsg->findNamedProp( "Keywords" );
02764         event->setCategories( s );
02765 
02766         s = tnefMsg->findProp( 0x1000 );
02767         event->setDescription( s );
02768 
02769         s = tnefMsg->findProp( 0x0070 );
02770         event->setSummary( s );
02771 
02772         s = tnefMsg->findProp( 0x0026 );
02773         event->setPriority( s.toInt() );
02774 
02775         // is reminder flag set ?
02776         if(!tnefMsg->findProp(0x8503).isEmpty()) {
02777           Alarm *alarm = new Alarm(event);
02778           QDateTime highNoonTime =
02779             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
02780                                      .replace( QChar( '-' ), "" )
02781                                      .replace( QChar( ':' ), "" ) );
02782           QDateTime wakeMeUpTime =
02783             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
02784                                      .replace( QChar( '-' ), "" )
02785                                      .replace( QChar( ':' ), "" ) );
02786           alarm->setTime(wakeMeUpTime);
02787 
02788           if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
02789             alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
02790           else
02791             // default: wake them up 15 minutes before the appointment
02792             alarm->setStartOffset( Duration( 15*60 ) );
02793           alarm->setDisplayAlarm( i18n( "Reminder" ) );
02794 
02795           // Sorry: the different action types are not known (yet)
02796           //        so we always set 'DISPLAY' (no sounds, no images...)
02797           event->addAlarm( alarm );
02798         }
02799         cal.addEvent( event );
02800         bOk = true;
02801         // we finished composing a vCal
02802       } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
02803         addressee.setUid( stringProp( tnefMsg, attMSGID ) );
02804         addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) );
02805         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
02806         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
02807         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
02808         addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) );
02809         addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) );
02810         addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
02811         addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
02812         addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) );
02813         addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) );
02814         addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) );
02815 
02816         QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
02817           .replace( QChar( '-' ), QString::null )
02818           .replace( QChar( ':' ), QString::null );
02819         if( !s.isEmpty() )
02820           addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );
02821 
02822         addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );
02823 
02824         // collect parts of Name entry
02825         addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
02826         addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
02827         addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) );
02828         addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) );
02829         addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );
02830 
02831         addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
02832         addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
02833         addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
02834         /*
02835         the MAPI property ID of this (multiline) )field is unknown:
02836         vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
02837         */
02838 
02839         KABC::Address adr;
02840         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) );
02841         adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
02842         adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
02843         adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
02844         adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) );
02845         adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
02846         adr.setType(KABC::Address::Home);
02847         addressee.insertAddress(adr);
02848 
02849         adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
02850         adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) );
02851         adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) );
02852         adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) );
02853         adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
02854         adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) );
02855         adr.setType( KABC::Address::Work );
02856         addressee.insertAddress( adr );
02857 
02858         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) );
02859         adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
02860         adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
02861         adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
02862         adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) );
02863         adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
02864         adr.setType( KABC::Address::Dom );
02865         addressee.insertAddress(adr);
02866 
02867         // problem: the 'other' address was stored by KOrganizer in
02868         //          a line looking like the following one:
02869         // 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
02870 
02871         QString nr;
02872         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
02873         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) );
02874         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
02875         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) );
02876         nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
02877         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) );
02878         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
02879         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) );
02880         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
02881         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) );
02882 
02883         s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
02884           .replace( QChar( '-' ), QString::null )
02885           .replace( QChar( ':' ), QString::null );
02886         if( !s.isEmpty() )
02887           addressee.setBirthday( QDateTime::fromString( s ) );
02888 
02889         bOk = ( !addressee.isEmpty() );
02890       } else if( "IPM.NOTE" == msgClass ) {
02891 
02892       } // else if ... and so on ...
02893     }
02894   }
02895 
02896   // Compose return string
02897   QString iCal = calFormat.toString( &cal );
02898   if( !iCal.isEmpty() )
02899     // This was an iCal
02900     return iCal;
02901 
02902   // Not an iCal - try a vCard
02903   KABC::VCardConverter converter;
02904   return converter.createVCard( addressee );
02905 }
02906 
02907 
02908 QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef,
02909         Calendar *mCalendar, InvitationFormatterHelper *helper )
02910 {
02911   QString vPart = IncidenceFormatter::msTNEFToVPart( tnef );
02912   QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper );
02913   if( !iCal.isEmpty() )
02914     return iCal;
02915   return vPart;
02916 }
02917 
02918 
02919 
02920 
02921 /*******************************************************************
02922  *  Helper functions for the Incidence tooltips
02923  *******************************************************************/
02924 
02925 class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
02926 {
02927   public:
02928     ToolTipVisitor()
02929       : mCalendar( 0 ), mRichText( true ), mResult( "" ) {}
02930 
02931     bool act( Calendar *calendar, IncidenceBase *incidence,
02932               const QDate &date=QDate(), bool richText=true )
02933     {
02934       mCalendar = calendar;
02935       mDate = date;
02936       mRichText = richText;
02937       mResult = "";
02938       return incidence ? incidence->accept( *this ) : false;
02939     }
02940     QString result() const { return mResult; }
02941 
02942   protected:
02943     bool visit( Event *event );
02944     bool visit( Todo *todo );
02945     bool visit( Journal *journal );
02946     bool visit( FreeBusy *fb );
02947 
02948     QString dateRangeText( Event *event, const QDate &date );
02949     QString dateRangeText( Todo *todo, const QDate &date );
02950     QString dateRangeText( Journal *journal );
02951     QString dateRangeText( FreeBusy *fb );
02952 
02953     QString generateToolTip( Incidence* incidence, QString dtRangeText );
02954 
02955   protected:
02956     Calendar *mCalendar;
02957     QDate mDate;
02958     bool mRichText;
02959     QString mResult;
02960 };
02961 
02962 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const QDate &date )
02963 {
02964   QString ret;
02965   QString tmp;
02966 
02967   QDateTime startDt = event->dtStart();
02968   QDateTime endDt = event->dtEnd();
02969   if ( event->doesRecur() ) {
02970     if ( date.isValid() ) {
02971       QDateTime dt( date, QTime( 0, 0, 0 ) );
02972       int diffDays = startDt.daysTo( dt );
02973       dt = dt.addSecs( -1 );
02974       startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() );
02975       if ( event->hasEndDate() ) {
02976         endDt = endDt.addDays( diffDays );
02977         if ( startDt > endDt ) {
02978           startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() );
02979           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
02980         }
02981       }
02982     }
02983   }
02984   if ( event->isMultiDay() ) {
02985 
02986     tmp = "<br>" + i18n("Event start", "<i>From:</i>&nbsp;%1");
02987     if (event->doesFloat())
02988       ret += tmp.arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
02989     else
02990       ret += tmp.arg( IncidenceFormatter::dateToString( startDt ).replace(" ", "&nbsp;") );
02991 
02992     tmp = "<br>" + i18n("Event end","<i>To:</i>&nbsp;%1");
02993     if (event->doesFloat())
02994       ret += tmp.arg( IncidenceFormatter::dateToString( endDt, false ).replace(" ", "&nbsp;") );
02995     else
02996       ret += tmp.arg( IncidenceFormatter::dateToString( endDt ).replace(" ", "&nbsp;") );
02997 
02998   } else {
02999 
03000     ret += "<br>"+i18n("<i>Date:</i>&nbsp;%1").
03001            arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03002     if ( !event->doesFloat() ) {
03003       const QString dtStartTime =
03004         IncidenceFormatter::timeToString( startDt, true ).replace( " ", "&nbsp;" );
03005       const QString dtEndTime =
03006         IncidenceFormatter::timeToString( endDt, true ).replace( " ", "&nbsp;" );
03007       if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00'
03008         tmp = "<br>" + i18n("time for event, &nbsp; to prevent ugly line breaks",
03009         "<i>Time:</i>&nbsp;%1").
03010         arg( dtStartTime );
03011       } else {
03012         tmp = "<br>" + i18n("time range for event, &nbsp; to prevent ugly line breaks",
03013         "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2").
03014         arg( dtStartTime, dtEndTime );
03015       }
03016       ret += tmp;
03017     }
03018 
03019   }
03020   return ret;
03021 }
03022 
03023 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const QDate &date )
03024 {
03025   QString ret;
03026   bool floats( todo->doesFloat() );
03027 
03028   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03029     QDateTime startDt = todo->dtStart();
03030     if ( todo->doesRecur() ) {
03031       if ( date.isValid() ) {
03032         startDt.setDate( date );
03033       }
03034     }
03035     ret += "<br>" +
03036            i18n("<i>Start:</i>&nbsp;%1").
03037            arg( IncidenceFormatter::dateTimeToString( startDt, floats, false ).
03038                 replace( " ", "&nbsp;" ) );
03039   }
03040 
03041   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03042     QDateTime dueDt = todo->dtDue();
03043     if ( todo->doesRecur() ) {
03044       if ( date.isValid() ) {
03045         QDateTime dt( date, QTime( 0, 0, 0 ) );
03046         dt = dt.addSecs( -1 );
03047         dueDt.setDate( todo->recurrence()->getNextDateTime( dt ).date() );
03048       }
03049     }
03050     ret += "<br>" +
03051            i18n("<i>Due:</i>&nbsp;%1").
03052            arg( IncidenceFormatter::dateTimeToString( dueDt, floats, false ).
03053                 replace( " ", "&nbsp;" ) );
03054   }
03055 
03056   // Print priority and completed info here, for lack of a better place
03057 
03058   if ( todo->priority() > 0 ) {
03059     ret += "<br>";
03060     ret += "<i>" + i18n( "Priority:" ) + "</i>" + "&nbsp;";
03061     ret += QString::number( todo->priority() );
03062   }
03063 
03064   ret += "<br>";
03065   if ( todo->isCompleted() ) {
03066     ret += "<i>" + i18n( "Completed:" ) + "</i>" + "&nbsp;";
03067     ret += todo->completedStr().replace( " ", "&nbsp;" );
03068   } else {
03069     ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + "&nbsp;";
03070     ret += i18n( "%1%" ).arg( todo->percentComplete() );
03071   }
03072 
03073   return ret;
03074 }
03075 
03076 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal )
03077 {
03078   QString ret;
03079   if (journal->dtStart().isValid() ) {
03080     ret += "<br>" +
03081            i18n("<i>Date:</i>&nbsp;%1").
03082            arg( IncidenceFormatter::dateToString( journal->dtStart(), false ) );
03083   }
03084   return ret;
03085 }
03086 
03087 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
03088 {
03089   QString tmp( "<br>" + i18n("<i>Period start:</i>&nbsp;%1") );
03090   QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) );
03091   tmp = "<br>" + i18n("<i>Period start:</i>&nbsp;%1");
03092   ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) );
03093   return ret;
03094 }
03095 
03096 
03097 
03098 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
03099 {
03100   mResult = generateToolTip( event, dateRangeText( event, mDate ) );
03101   return !mResult.isEmpty();
03102 }
03103 
03104 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
03105 {
03106   mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
03107   return !mResult.isEmpty();
03108 }
03109 
03110 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
03111 {
03112   mResult = generateToolTip( journal, dateRangeText( journal ) );
03113   return !mResult.isEmpty();
03114 }
03115 
03116 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
03117 {
03118   mResult = "<qt><b>" + i18n("Free/Busy information for %1")
03119         .arg(fb->organizer().fullName()) + "</b>";
03120   mResult += dateRangeText( fb );
03121   mResult += "</qt>";
03122   return !mResult.isEmpty();
03123 }
03124 
03125 static QString tooltipPerson( const QString& email, QString name )
03126 {
03127   // Make the search, if there is an email address to search on,
03128   // and name is missing
03129   if ( name.isEmpty() && !email.isEmpty() ) {
03130     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
03131     KABC::Addressee::List addressList = add_book->findByEmail( email );
03132     if ( !addressList.isEmpty() ) {
03133       KABC::Addressee o = addressList.first();
03134       if ( !o.isEmpty() && addressList.size() < 2 ) {
03135         // use the name from the addressbook
03136         name = o.formattedName();
03137       }
03138     }
03139   }
03140 
03141   // Show the attendee
03142   QString tmpString = ( name.isEmpty() ? email : name );
03143 
03144   return tmpString;
03145 }
03146 
03147 static QString etc = i18n( "elipsis", "..." );
03148 static QString tooltipFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role )
03149 {
03150   int maxNumAtts = 8; // maximum number of people to print per attendee role
03151   QString sep = i18n( "separator for lists of people names", ", " );
03152   int sepLen = sep.length();
03153 
03154   int i = 0;
03155   QString tmpStr;
03156   Attendee::List::ConstIterator it;
03157   Attendee::List attendees = incidence->attendees();
03158 
03159   for( it = attendees.begin(); it != attendees.end(); ++it ) {
03160     Attendee *a = *it;
03161     if ( a->role() != role ) {
03162       // skip not this role
03163       continue;
03164     }
03165     if ( a->email() == incidence->organizer().email() ) {
03166       // skip attendee that is also the organizer
03167       continue;
03168     }
03169     if ( i == maxNumAtts ) {
03170       tmpStr += etc;
03171       break;
03172     }
03173     tmpStr += tooltipPerson( a->email(), a->name() );
03174     if ( !a->delegator().isEmpty() ) {
03175       tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
03176     }
03177     if ( !a->delegate().isEmpty() ) {
03178       tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
03179     }
03180     tmpStr += sep;
03181     i++;
03182   }
03183   if ( tmpStr.endsWith( sep ) ) {
03184     tmpStr.truncate( tmpStr.length() - sepLen );
03185   }
03186   return tmpStr;
03187 }
03188 
03189 static QString tooltipFormatAttendees( Incidence *incidence )
03190 {
03191   QString tmpStr, str;
03192 
03193   // Add organizer link
03194   tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "&nbsp;";
03195   tmpStr += tooltipPerson( incidence->organizer().email(),
03196                            incidence->organizer().name() );
03197   // Add "chair"
03198   str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair );
03199   if ( !str.isEmpty() ) {
03200     tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "&nbsp;";
03201     tmpStr += str;
03202   }
03203 
03204   // Add required participants
03205   str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant );
03206   if ( !str.isEmpty() ) {
03207     tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "&nbsp;";
03208     tmpStr += str;
03209   }
03210 
03211   // Add optional participants
03212   str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant );
03213   if ( !str.isEmpty() ) {
03214     tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "&nbsp;";
03215     tmpStr += str;
03216   }
03217 
03218   // Add observers
03219   str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant );
03220   if ( !str.isEmpty() ) {
03221     tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "&nbsp;";
03222     tmpStr += str;
03223   }
03224 
03225   return tmpStr;
03226 }
03227 
03228 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence* incidence, QString dtRangeText )
03229 {
03230   uint maxDescLen = 120; // maximum description chars to print (before elipsis)
03231 
03232   if ( !incidence ) {
03233     return QString::null;
03234   }
03235 
03236   QString tmp = "<qt>";
03237 
03238   // header
03239   tmp += "<b>" + incidence->summary().replace( "\n", "<br>" ) + "</b>";
03240   //NOTE: using <hr> seems to confuse Qt3 tooltips in some cases so use "-----"
03241   tmp += "<br>----------<br>";
03242 
03243   if ( mCalendar ) {
03244     QString calStr = IncidenceFormatter::resourceString( mCalendar, incidence );
03245     if ( !calStr.isEmpty() ) {
03246       tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + "&nbsp;";
03247       tmp += calStr;
03248     }
03249   }
03250 
03251   tmp += dtRangeText;
03252 
03253   if ( !incidence->location().isEmpty() ) {
03254     tmp += "<br>";
03255     tmp += "<i>" + i18n( "Location:" ) + "</i>" + "&nbsp;";
03256     tmp += incidence->location().replace( "\n", "<br>" );
03257   }
03258 
03259   QString durStr = IncidenceFormatter::durationString( incidence );
03260   if ( !durStr.isEmpty() ) {
03261     tmp += "<br>";
03262     tmp += "<i>" + i18n( "Duration:" ) + "</i>" + "&nbsp;";
03263     tmp += durStr;
03264   }
03265 
03266   if ( incidence->doesRecur() ) {
03267     tmp += "<br>";
03268     tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + "&nbsp;";
03269     tmp += IncidenceFormatter::recurrenceString( incidence );
03270   }
03271 
03272   if ( !incidence->description().isEmpty() ) {
03273     QString desc( incidence->description() );
03274     if ( desc.length() > maxDescLen ) {
03275       desc = desc.left( maxDescLen ) + etc;
03276     }
03277     tmp += "<br>----------<br>";
03278     tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
03279     tmp += desc.replace( "\n", "<br>" );
03280     tmp += "<br>";
03281   }
03282   if ( incidence->attendees().count() > 1 ) {
03283     tmp += "<br>----------<br>";
03284     tmp += tooltipFormatAttendees( incidence );
03285   }
03286 
03287   tmp += "</qt>";
03288   return tmp;
03289 }
03290 
03291 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
03292 {
03293   return toolTipStr( 0, incidence, QDate(), richText );
03294 }
03295 
03296 QString IncidenceFormatter::toolTipStr( Calendar *calendar,
03297                                         IncidenceBase *incidence,
03298                                         const QDate &date,
03299                                         bool richText )
03300 {
03301   ToolTipVisitor v;
03302   if ( v.act( calendar, incidence, date, richText ) ) {
03303     return v.result();
03304   } else {
03305     return QString::null;
03306   }
03307 }
03308 
03309 /*******************************************************************
03310  *  Helper functions for the Incidence tooltips
03311  *******************************************************************/
03312 
03313 class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
03314 {
03315   public:
03316     MailBodyVisitor() : mResult( "" ) {}
03317 
03318     bool act( IncidenceBase *incidence )
03319     {
03320       mResult = "";
03321       return incidence ? incidence->accept( *this ) : false;
03322     }
03323     QString result() const { return mResult; }
03324 
03325   protected:
03326     bool visit( Event *event );
03327     bool visit( Todo *todo );
03328     bool visit( Journal *journal );
03329     bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); }
03330   protected:
03331     QString mResult;
03332 };
03333 
03334 
03335 static QString mailBodyIncidence( Incidence *incidence )
03336 {
03337   QString body;
03338   if ( !incidence->summary().isEmpty() ) {
03339     body += i18n("Summary: %1\n").arg( incidence->summary() );
03340   }
03341   if ( !incidence->organizer().isEmpty() ) {
03342     body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() );
03343   }
03344   if ( !incidence->location().isEmpty() ) {
03345     body += i18n("Location: %1\n").arg( incidence->location() );
03346   }
03347   return body;
03348 }
03349 
03350 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
03351 {
03352   QString recurrence[]= {i18n("no recurrence", "None"),
03353     i18n("Minutely"), i18n("Hourly"), i18n("Daily"),
03354     i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"),
03355     i18n("Yearly"), i18n("Yearly"), i18n("Yearly")};
03356 
03357   mResult = mailBodyIncidence( event );
03358   mResult += i18n("Start Date: %1\n").
03359              arg( IncidenceFormatter::dateToString( event->dtStart(), true ) );
03360   if ( !event->doesFloat() ) {
03361     mResult += i18n("Start Time: %1\n").
03362                arg( IncidenceFormatter::timeToString( event->dtStart(), true ) );
03363   }
03364   if ( event->dtStart() != event->dtEnd() ) {
03365     mResult += i18n("End Date: %1\n").
03366                arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) );
03367   }
03368   if ( !event->doesFloat() ) {
03369     mResult += i18n("End Time: %1\n").
03370                arg( IncidenceFormatter::timeToString( event->dtEnd(), true ) );
03371   }
03372   if ( event->doesRecur() ) {
03373     Recurrence *recur = event->recurrence();
03374     // TODO: Merge these two to one of the form "Recurs every 3 days"
03375     mResult += i18n("Recurs: %1\n")
03376              .arg( recurrence[ recur->recurrenceType() ] );
03377     mResult += i18n("Frequency: %1\n")
03378              .arg( event->recurrence()->frequency() );
03379 
03380     if ( recur->duration() > 0 ) {
03381       mResult += i18n ("Repeats once", "Repeats %n times", recur->duration());
03382       mResult += '\n';
03383     } else {
03384       if ( recur->duration() != -1 ) {
03385 // TODO_Recurrence: What to do with floating
03386         QString endstr;
03387         if ( event->doesFloat() ) {
03388           endstr = KGlobal::locale()->formatDate( recur->endDate() );
03389         } else {
03390           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() );
03391         }
03392         mResult += i18n("Repeat until: %1\n").arg( endstr );
03393       } else {
03394         mResult += i18n("Repeats forever\n");
03395       }
03396     }
03397   }
03398   QString details = event->description();
03399   if ( !details.isEmpty() ) {
03400     mResult += i18n("Details:\n%1\n").arg( details );
03401   }
03402   return !mResult.isEmpty();
03403 }
03404 
03405 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
03406 {
03407   mResult = mailBodyIncidence( todo );
03408 
03409   if ( todo->hasStartDate() ) {
03410     mResult += i18n("Start Date: %1\n").
03411                arg( IncidenceFormatter::dateToString( todo->dtStart( false ), true ) );
03412     if ( !todo->doesFloat() ) {
03413       mResult += i18n("Start Time: %1\n").
03414                  arg( IncidenceFormatter::timeToString( todo->dtStart( false ),true ) );
03415     }
03416   }
03417   if ( todo->hasDueDate() ) {
03418     mResult += i18n("Due Date: %1\n").
03419                arg( IncidenceFormatter::dateToString( todo->dtDue(), true ) );
03420     if ( !todo->doesFloat() ) {
03421       mResult += i18n("Due Time: %1\n").
03422                  arg( IncidenceFormatter::timeToString( todo->dtDue(), true ) );
03423     }
03424   }
03425   QString details = todo->description();
03426   if ( !details.isEmpty() ) {
03427     mResult += i18n("Details:\n%1\n").arg( details );
03428   }
03429   return !mResult.isEmpty();
03430 }
03431 
03432 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
03433 {
03434   mResult = mailBodyIncidence( journal );
03435   mResult += i18n("Date: %1\n").
03436              arg( IncidenceFormatter::dateToString( journal->dtStart(), true ) );
03437   if ( !journal->doesFloat() ) {
03438     mResult += i18n("Time: %1\n").
03439                arg( IncidenceFormatter::timeToString( journal->dtStart(), true ) );
03440   }
03441   if ( !journal->description().isEmpty() )
03442     mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() );
03443   return !mResult.isEmpty();
03444 }
03445 
03446 
03447 
03448 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
03449 {
03450   if ( !incidence )
03451     return QString::null;
03452 
03453   MailBodyVisitor v;
03454   if ( v.act( incidence ) ) {
03455     return v.result();
03456   }
03457   return QString::null;
03458 }
03459 
03460 static QString recurEnd( Incidence *incidence )
03461 {
03462   QString endstr;
03463   if ( incidence->doesFloat() ) {
03464     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
03465   } else {
03466     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
03467   }
03468   return endstr;
03469 }
03470 
03471 /************************************
03472  *  More static formatting functions
03473  ************************************/
03474 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
03475 {
03476   if ( !incidence->doesRecur() ) {
03477     return i18n( "No recurrence" );
03478   }
03479   QStringList dayList;
03480   dayList.append( i18n( "31st Last" ) );
03481   dayList.append( i18n( "30th Last" ) );
03482   dayList.append( i18n( "29th Last" ) );
03483   dayList.append( i18n( "28th Last" ) );
03484   dayList.append( i18n( "27th Last" ) );
03485   dayList.append( i18n( "26th Last" ) );
03486   dayList.append( i18n( "25th Last" ) );
03487   dayList.append( i18n( "24th Last" ) );
03488   dayList.append( i18n( "23rd Last" ) );
03489   dayList.append( i18n( "22nd Last" ) );
03490   dayList.append( i18n( "21st Last" ) );
03491   dayList.append( i18n( "20th Last" ) );
03492   dayList.append( i18n( "19th Last" ) );
03493   dayList.append( i18n( "18th Last" ) );
03494   dayList.append( i18n( "17th Last" ) );
03495   dayList.append( i18n( "16th Last" ) );
03496   dayList.append( i18n( "15th Last" ) );
03497   dayList.append( i18n( "14th Last" ) );
03498   dayList.append( i18n( "13th Last" ) );
03499   dayList.append( i18n( "12th Last" ) );
03500   dayList.append( i18n( "11th Last" ) );
03501   dayList.append( i18n( "10th Last" ) );
03502   dayList.append( i18n( "9th Last" ) );
03503   dayList.append( i18n( "8th Last" ) );
03504   dayList.append( i18n( "7th Last" ) );
03505   dayList.append( i18n( "6th Last" ) );
03506   dayList.append( i18n( "5th Last" ) );
03507   dayList.append( i18n( "4th Last" ) );
03508   dayList.append( i18n( "3rd Last" ) );
03509   dayList.append( i18n( "2nd Last" ) );
03510   dayList.append( i18n( "last day of the month", "Last" ) );
03511   dayList.append( i18n( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
03512   dayList.append( i18n( "1st" ) );
03513   dayList.append( i18n( "2nd" ) );
03514   dayList.append( i18n( "3rd" ) );
03515   dayList.append( i18n( "4th" ) );
03516   dayList.append( i18n( "5th" ) );
03517   dayList.append( i18n( "6th" ) );
03518   dayList.append( i18n( "7th" ) );
03519   dayList.append( i18n( "8th" ) );
03520   dayList.append( i18n( "9th" ) );
03521   dayList.append( i18n( "10th" ) );
03522   dayList.append( i18n( "11th" ) );
03523   dayList.append( i18n( "12th" ) );
03524   dayList.append( i18n( "13th" ) );
03525   dayList.append( i18n( "14th" ) );
03526   dayList.append( i18n( "15th" ) );
03527   dayList.append( i18n( "16th" ) );
03528   dayList.append( i18n( "17th" ) );
03529   dayList.append( i18n( "18th" ) );
03530   dayList.append( i18n( "19th" ) );
03531   dayList.append( i18n( "20th" ) );
03532   dayList.append( i18n( "21st" ) );
03533   dayList.append( i18n( "22nd" ) );
03534   dayList.append( i18n( "23rd" ) );
03535   dayList.append( i18n( "24th" ) );
03536   dayList.append( i18n( "25th" ) );
03537   dayList.append( i18n( "26th" ) );
03538   dayList.append( i18n( "27th" ) );
03539   dayList.append( i18n( "28th" ) );
03540   dayList.append( i18n( "29th" ) );
03541   dayList.append( i18n( "30th" ) );
03542   dayList.append( i18n( "31st" ) );
03543   int weekStart = KGlobal::locale()->weekStartDay();
03544   QString dayNames;
03545   QString recurStr, txt;
03546   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
03547   Recurrence *recur = incidence->recurrence();
03548   switch ( recur->recurrenceType() ) {
03549   case Recurrence::rNone:
03550     return i18n( "No recurrence" );
03551 
03552   case Recurrence::rMinutely:
03553     recurStr = i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() );
03554     if ( recur->duration() != -1 ) {
03555       txt = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
03556       if ( recur->duration() >  0 ) {
03557         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03558       }
03559       return txt;
03560     }
03561     return recurStr;
03562 
03563   case Recurrence::rHourly:
03564     recurStr = i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() );
03565     if ( recur->duration() != -1 ) {
03566       txt = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
03567       if ( recur->duration() >  0 ) {
03568         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03569       }
03570       return txt;
03571     }
03572     return recurStr;
03573 
03574   case Recurrence::rDaily:
03575     recurStr = i18n( "Recurs daily", "Recurs every %n days", recur->frequency() );
03576     if ( recur->duration() != -1 ) {
03577 
03578       txt = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
03579       if ( recur->duration() >  0 ) {
03580         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03581       }
03582       return txt;
03583     }
03584     return recurStr;
03585 
03586   case Recurrence::rWeekly:
03587   {
03588     recurStr = i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() );
03589 
03590     bool addSpace = false;
03591     for ( int i = 0; i < 7; ++i ) {
03592       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
03593         if ( addSpace ) {
03594           dayNames.append( i18n( "separator for list of days", ", " ) );
03595         }
03596         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, true ) );
03597         addSpace = true;
03598       }
03599     }
03600     if ( dayNames.isEmpty() ) {
03601       dayNames = i18n( "Recurs weekly on no days", "no days" );
03602     }
03603     if ( recur->duration() != -1 ) {
03604       txt = i18n( "%1 on %2 until %3" ).
03605             arg( recurStr ).arg( dayNames ).arg( recurEnd( incidence ) );
03606       if ( recur->duration() >  0 ) {
03607         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03608       }
03609       return txt;
03610     }
03611     txt = i18n( "%1 on %2" ).arg( recurStr ).arg( dayNames );
03612     return txt;
03613   }
03614 
03615   case Recurrence::rMonthlyPos:
03616   {
03617     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
03618 
03619     KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
03620     if ( recur->duration() != -1 ) {
03621       txt = i18n( "%1 on the %2 %3 until %4" ).
03622             arg( recurStr ).
03623             arg( dayList[rule.pos() + 31] ).
03624             arg( calSys->weekDayName( rule.day(), false ) ).
03625             arg( recurEnd( incidence ) );
03626       if ( recur->duration() >  0 ) {
03627         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03628       }
03629       return txt;
03630     }
03631     txt = i18n( "%1 on the %2 %3" ).
03632           arg( recurStr ).
03633           arg( dayList[rule.pos() + 31] ).
03634           arg( calSys->weekDayName( rule.day(), false ) );
03635     return txt;
03636   }
03637 
03638   case Recurrence::rMonthlyDay:
03639   {
03640     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
03641 
03642     int days = recur->monthDays()[0];
03643     if ( recur->duration() != -1 ) {
03644       txt = i18n( "%1 on the %2 day until %3" ).
03645             arg( recurStr ).
03646             arg( dayList[days + 31] ).
03647             arg( recurEnd( incidence ) );
03648       if ( recur->duration() >  0 ) {
03649         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03650       }
03651       return txt;
03652     }
03653     txt = i18n( "%1 on the %2 day" ).arg( recurStr ).arg( dayList[days + 31] );
03654     return txt;
03655   }
03656 
03657   case Recurrence::rYearlyMonth:
03658   {
03659     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
03660 
03661     if ( recur->duration() != -1 ) {
03662       if ( !recur->yearDates().isEmpty() ) {
03663         txt = i18n( "%1 on %2 %3 until %4" ).
03664               arg( recurStr ).
03665               arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
03666               arg( dayList[ recur->yearDates()[0] + 31 ] ).
03667               arg( recurEnd( incidence ) );
03668         if ( recur->duration() >  0 ) {
03669           txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03670         }
03671         return txt;
03672       }
03673     }
03674     if ( !recur->yearDates().isEmpty() ) {
03675       txt = i18n( "%1 on %2 %3" ).
03676             arg( recurStr ).
03677             arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
03678             arg( dayList[ recur->yearDates()[0] + 31 ] );
03679       return txt;
03680     } else {
03681       if ( !recur->yearMonths().isEmpty() ) {
03682         txt = i18n( "Recurs yearly on %1 %2" ).
03683               arg( calSys->monthName( recur->yearMonths()[0],
03684                                       recur->startDate().year() ) ).
03685               arg( dayList[ recur->startDate().day() + 31 ] );
03686       } else {
03687         txt = i18n( "Recurs yearly on %1 %2" ).
03688               arg( calSys->monthName( recur->startDate().month(),
03689                                       recur->startDate().year() ) ).
03690               arg( dayList[ recur->startDate().day() + 31 ] );
03691       }
03692       return txt;
03693     }
03694   }
03695 
03696   case Recurrence::rYearlyDay:
03697   {
03698     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
03699 
03700     if ( recur->duration() != -1 ) {
03701       txt = i18n( "%1 on day %2 until %3" ).
03702             arg( recurStr ).
03703             arg( recur->yearDays()[0] ).
03704             arg( recurEnd( incidence ) );
03705       if ( recur->duration() >  0 ) {
03706         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03707       }
03708       return txt;
03709     }
03710     txt = i18n( "%1 on day %2" ).arg( recurStr ).arg( recur->yearDays()[0] );
03711     return txt;
03712   }
03713 
03714   case Recurrence::rYearlyPos:
03715   {
03716     recurStr = i18n( "Every year", "Every %n years", recur->frequency() );
03717 
03718     KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
03719     if ( recur->duration() != -1 ) {
03720       txt = i18n( "%1 on the %2 %3 of %4 until %5" ).
03721             arg( recurStr ).
03722             arg( dayList[rule.pos() + 31] ).
03723             arg( calSys->weekDayName( rule.day(), false ) ).
03724             arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
03725             arg( recurEnd( incidence ) );
03726       if ( recur->duration() >  0 ) {
03727         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03728       }
03729       return txt;
03730     }
03731     txt = i18n( "%1 on the %2 %3 of %4" ).
03732           arg( recurStr ).
03733           arg( dayList[rule.pos() + 31] ).
03734           arg( calSys->weekDayName( rule.day(), false ) ).
03735           arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
03736     return txt;
03737   }
03738 
03739   default:
03740     return i18n( "Incidence recurs" );
03741   }
03742 }
03743 
03744 QString IncidenceFormatter::timeToString( const QDateTime &date, bool shortfmt )
03745 {
03746   return KGlobal::locale()->formatTime( date.time(), !shortfmt );
03747 }
03748 
03749 QString IncidenceFormatter::dateToString( const QDateTime &date, bool shortfmt )
03750 {
03751   return
03752     KGlobal::locale()->formatDate( date.date(), shortfmt );
03753 }
03754 
03755 QString IncidenceFormatter::dateTimeToString( const QDateTime &date,
03756                                               bool allDay, bool shortfmt )
03757 {
03758   if ( allDay ) {
03759     return dateToString( date, shortfmt );
03760   }
03761 
03762   return  KGlobal::locale()->formatDateTime( date, shortfmt );
03763 }
03764 
03765 QString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence )
03766 {
03767   if ( !calendar || !incidence ) {
03768     return QString::null;
03769   }
03770 
03771   CalendarResources *calendarResource = dynamic_cast<CalendarResources*>( calendar );
03772   if ( !calendarResource ) {
03773     return QString::null;
03774   }
03775 
03776   ResourceCalendar *resourceCalendar = calendarResource->resource( incidence );
03777   if ( resourceCalendar ) {
03778     if ( !resourceCalendar->subresources().isEmpty() ) {
03779       QString subRes = resourceCalendar->subresourceIdentifier( incidence );
03780       if ( subRes.isEmpty() ) {
03781         return resourceCalendar->resourceName();
03782       } else {
03783         return resourceCalendar->labelForSubresource( subRes );
03784       }
03785     }
03786     return resourceCalendar->resourceName();
03787   }
03788 
03789   return QString::null;
03790 }
03791 
03792 static QString secs2Duration( int secs )
03793 {
03794   QString tmp;
03795   int days = secs / 86400;
03796   if ( days > 0 ) {
03797     tmp += i18n( "1 day", "%n days", days );
03798     tmp += ' ';
03799     secs -= ( days * 86400 );
03800   }
03801   int hours = secs / 3600;
03802   if ( hours > 0 ) {
03803     tmp += i18n( "1 hour", "%n hours", hours );
03804     tmp += ' ';
03805     secs -= ( hours * 3600 );
03806   }
03807   int mins = secs / 60;
03808   if ( mins > 0 ) {
03809     tmp += i18n( "1 minute", "%n minutes",  mins );
03810   }
03811   return tmp;
03812 }
03813 
03814 QString IncidenceFormatter::durationString( Incidence *incidence )
03815 {
03816   QString tmp;
03817   if ( incidence->type() == "Event" ) {
03818     Event *event = static_cast<Event *>( incidence );
03819     if ( event->hasEndDate() ) {
03820       if ( !event->doesFloat() ) {
03821         tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
03822       } else {
03823         tmp = i18n( "1 day", "%n days",
03824                     event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
03825       }
03826     } else {
03827       tmp = i18n( "forever" );
03828     }
03829   } else if ( incidence->type() == "Todo" ) {
03830     Todo *todo = static_cast<Todo *>( incidence );
03831     if ( todo->hasDueDate() ) {
03832       if ( todo->hasStartDate() ) {
03833         if ( !todo->doesFloat() ) {
03834           tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
03835         } else {
03836           tmp = i18n( "1 day", "%n days",
03837                       todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
03838         }
03839       }
03840     }
03841   }
03842   return tmp;
03843 }
03844 
KDE Home | KDE Accessibility Home | Description of Access Keys