libkcal

incidenceformatter.cpp

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