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