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