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                              IncidenceFormatter::timeToString( todo->dtStart(), false ) );
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                              IncidenceFormatter::timeToString( todo->dtDue(), false ) );
01407     }
01408 
01409   } else {
01410     html += invitationRow( i18n( "Due Date:" ), i18n( "Due Date: None", "None" ) );
01411   }
01412 
01413   html += "</table></div>\n";
01414   html += invitationsDetailsIncidence( todo, noHtmlMode );
01415 
01416   return html;
01417 }
01418 
01419 static QString invitationDetailsJournal( Journal *journal, bool noHtmlMode )
01420 {
01421   if ( !journal ) {
01422     return QString::null;
01423   }
01424 
01425   QString sSummary = i18n( "Summary unspecified" );
01426   QString sDescr = i18n( "Description unspecified" );
01427   if ( ! journal->summary().isEmpty() ) {
01428     sSummary = journal->summary();
01429     if ( noHtmlMode ) {
01430       sSummary = cleanHtml( sSummary );
01431     }
01432   }
01433   if ( ! journal->description().isEmpty() ) {
01434     sDescr = journal->description();
01435     if ( noHtmlMode ) {
01436       sDescr = cleanHtml( sDescr );
01437     }
01438   }
01439   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
01440   html += invitationRow( i18n( "Summary:" ), sSummary );
01441   html += invitationRow( i18n( "Date:" ),
01442                          IncidenceFormatter::dateToString( journal->dtStart(), false ) );
01443   html += invitationRow( i18n( "Description:" ), sDescr );
01444   html += "</table>\n";
01445   html += invitationsDetailsIncidence( journal, noHtmlMode );
01446 
01447   return html;
01448 }
01449 
01450 static QString invitationDetailsFreeBusy( FreeBusy *fb, bool /*noHtmlMode*/ )
01451 {
01452   if ( !fb )
01453     return QString::null;
01454   QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
01455 
01456   html += invitationRow( i18n("Person:"), fb->organizer().fullName() );
01457   html += invitationRow( i18n("Start date:"),
01458                          IncidenceFormatter::dateToString( fb->dtStart(), true ) );
01459   html += invitationRow( i18n("End date:"),
01460                          KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) );
01461   html += "<tr><td colspan=2><hr></td></tr>\n";
01462   html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
01463 
01464   QValueList<Period> periods = fb->busyPeriods();
01465 
01466   QValueList<Period>::iterator it;
01467   for ( it = periods.begin(); it != periods.end(); ++it ) {
01468     Period per = *it;
01469     if ( per.hasDuration() ) {
01470       int dur = per.duration().asSeconds();
01471       QString cont;
01472       if ( dur >= 3600 ) {
01473         cont += i18n("1 hour ", "%n hours ", dur / 3600);
01474         dur %= 3600;
01475       }
01476       if ( dur >= 60 ) {
01477         cont += i18n("1 minute", "%n minutes ", dur / 60);
01478         dur %= 60;
01479       }
01480       if ( dur > 0 ) {
01481         cont += i18n("1 second", "%n seconds", dur);
01482       }
01483       html += invitationRow( QString::null, i18n("startDate for duration", "%1 for %2")
01484           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
01485           .arg(cont) );
01486     } else {
01487       QString cont;
01488       if ( per.start().date() == per.end().date() ) {
01489         cont = i18n("date, fromTime - toTime ", "%1, %2 - %3")
01490             .arg( KGlobal::locale()->formatDate( per.start().date() ) )
01491             .arg( KGlobal::locale()->formatTime( per.start().time() ) )
01492             .arg( KGlobal::locale()->formatTime( per.end().time() ) );
01493       } else {
01494         cont = i18n("fromDateTime - toDateTime", "%1 - %2")
01495           .arg( KGlobal::locale()->formatDateTime( per.start(), false ) )
01496           .arg( KGlobal::locale()->formatDateTime( per.end(), false ) );
01497       }
01498 
01499       html += invitationRow( QString::null, cont );
01500     }
01501   }
01502 
01503   html += "</table>\n";
01504   return html;
01505 }
01506 
01507 static bool replyMeansCounter( Incidence */*incidence*/ )
01508 {
01509   return false;
01524 }
01525 
01526 static QString invitationHeaderEvent( Event *event, Incidence *existingIncidence,
01527                                       ScheduleMessage *msg, const QString &sender )
01528 {
01529   if ( !msg || !event )
01530     return QString::null;
01531 
01532   switch ( msg->method() ) {
01533   case Scheduler::Publish:
01534     return i18n( "This invitation has been published" );
01535   case Scheduler::Request:
01536     if ( existingIncidence && event->revision() > 0 ) {
01537       return i18n( "This invitation has been updated by the organizer %1" ).
01538         arg( event->organizer().fullName() );
01539     }
01540     if ( iamOrganizer( event ) ) {
01541       return i18n( "I created this invitation" );
01542     } else {
01543       if ( senderIsOrganizer( event, sender ) ) {
01544         if ( !event->organizer().fullName().isEmpty() ) {
01545           return i18n( "You received an invitation from %1" ).
01546             arg( event->organizer().fullName() );
01547         } else {
01548           return i18n( "You received an invitation" );
01549         }
01550       } else {
01551         if ( !event->organizer().fullName().isEmpty() ) {
01552           return i18n( "You received an invitation from %1 as a representative of %2" ).
01553             arg( sender, event->organizer().fullName() );
01554         } else {
01555           return i18n( "You received an invitation from %1 as the organizer's representative" ).
01556             arg( sender );
01557         }
01558       }
01559     }
01560   case Scheduler::Refresh:
01561     return i18n( "This invitation was refreshed" );
01562   case Scheduler::Cancel:
01563     return i18n( "This invitation has been canceled" );
01564   case Scheduler::Add:
01565     return i18n( "Addition to the invitation" );
01566   case Scheduler::Reply:
01567   {
01568     if ( replyMeansCounter( event ) ) {
01569       return i18n( "%1 makes this counter proposal" ).
01570         arg( firstAttendeeName( event, i18n( "Sender" ) ) );
01571     }
01572 
01573     Attendee::List attendees = event->attendees();
01574     if( attendees.count() == 0 ) {
01575       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01576       return QString::null;
01577     }
01578     if( attendees.count() != 1 ) {
01579       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01580                     << "but is " << attendees.count() << endl;
01581     }
01582     QString attendeeName = firstAttendeeName( event, i18n( "Sender" ) );
01583 
01584     QString delegatorName, dummy;
01585     Attendee* attendee = *attendees.begin();
01586     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
01587     if ( delegatorName.isEmpty() ) {
01588       delegatorName = attendee->delegator();
01589     }
01590 
01591     switch( attendee->status() ) {
01592     case Attendee::NeedsAction:
01593       return i18n( "%1 indicates this invitation still needs some action" ).arg( attendeeName );
01594     case Attendee::Accepted:
01595       if ( event->revision() > 0 ) {
01596         if ( !sender.isEmpty() ) {
01597           return i18n( "This invitation has been updated by attendee %1" ).arg( sender );
01598         } else {
01599           return i18n( "This invitation has been updated by an attendee" );
01600         }
01601       } else {
01602         if ( delegatorName.isEmpty() ) {
01603           return i18n( "%1 accepts this invitation" ).arg( attendeeName );
01604         } else {
01605           return i18n( "%1 accepts this invitation on behalf of %2" ).
01606             arg( attendeeName ).arg( delegatorName );
01607         }
01608       }
01609     case Attendee::Tentative:
01610       if ( delegatorName.isEmpty() ) {
01611         return i18n( "%1 tentatively accepts this invitation" ).
01612           arg( attendeeName );
01613       } else {
01614         return i18n( "%1 tentatively accepts this invitation on behalf of %2" ).
01615           arg( attendeeName ).arg( delegatorName );
01616       }
01617     case Attendee::Declined:
01618       if ( delegatorName.isEmpty() ) {
01619         return i18n( "%1 declines this invitation" ).arg( attendeeName );
01620       } else {
01621         return i18n( "%1 declines this invitation on behalf of %2" ).
01622           arg( attendeeName ).arg( delegatorName );
01623       }
01624     case Attendee::Delegated: {
01625       QString delegate, dummy;
01626       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
01627       if ( delegate.isEmpty() ) {
01628         delegate = attendee->delegate();
01629       }
01630       if ( !delegate.isEmpty() ) {
01631         return i18n( "%1 has delegated this invitation to %2" ).
01632           arg( attendeeName ) .arg( delegate );
01633       } else {
01634         return i18n( "%1 has delegated this invitation" ).arg( attendeeName );
01635       }
01636     }
01637     case Attendee::Completed:
01638       return i18n( "This invitation is now completed" );
01639     case Attendee::InProcess:
01640       return i18n( "%1 is still processing the invitation" ).
01641         arg( attendeeName );
01642     default:
01643       return i18n( "Unknown response to this invitation" );
01644     }
01645     break;
01646   }
01647 
01648   case Scheduler::Counter:
01649     return i18n( "%1 makes this counter proposal" ).
01650       arg( firstAttendeeName( event, i18n( "Sender" ) ) );
01651 
01652   case Scheduler::Declinecounter:
01653     return i18n( "%1 declines the counter proposal" ).
01654       arg( firstAttendeeName( event, i18n( "Sender" ) ) );
01655 
01656   case Scheduler::NoMethod:
01657     return i18n("Error: iMIP message with unknown method: '%1'").
01658       arg( msg->method() );
01659   }
01660   return QString::null;
01661 }
01662 
01663 static QString invitationHeaderTodo( Todo *todo, Incidence *existingIncidence,
01664                                      ScheduleMessage *msg, const QString &sender )
01665 {
01666   if ( !msg || !todo ) {
01667     return QString::null;
01668   }
01669 
01670   switch ( msg->method() ) {
01671   case Scheduler::Publish:
01672     return i18n("This task has been published");
01673   case Scheduler::Request:
01674     if ( existingIncidence && todo->revision() > 0 ) {
01675       return i18n( "This task has been updated by the organizer %1" ).
01676         arg( todo->organizer().fullName() );
01677     } else {
01678       if ( iamOrganizer( todo ) ) {
01679         return i18n( "I created this task" );
01680       } else {
01681         if ( senderIsOrganizer( todo, sender ) ) {
01682           if ( !todo->organizer().fullName().isEmpty() ) {
01683             return i18n( "You have been assigned this task by %1" ).
01684               arg( todo->organizer().fullName() );
01685           } else {
01686             return i18n( "You have been assigned this task" );
01687           }
01688         } else {
01689           if ( !todo->organizer().fullName().isEmpty() ) {
01690             return i18n( "You have been assigned this task by %1 as a representative of %2" ).
01691               arg( sender, todo->organizer().fullName() );
01692           } else {
01693             return i18n( "You have been assigned this task by %1 as the organizer's representative" ).
01694               arg( sender );
01695           }
01696         }
01697       }
01698     }
01699   case Scheduler::Refresh:
01700     return i18n( "This task was refreshed" );
01701   case Scheduler::Cancel:
01702     return i18n( "This task was canceled" );
01703   case Scheduler::Add:
01704     return i18n( "Addition to the task" );
01705   case Scheduler::Reply:
01706   {
01707     if ( replyMeansCounter( todo ) ) {
01708       return i18n( "%1 makes this counter proposal" ).
01709         arg( firstAttendeeName( todo, i18n( "Sender" ) ) );
01710     }
01711 
01712     Attendee::List attendees = todo->attendees();
01713     if( attendees.count() == 0 ) {
01714       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01715       return QString::null;
01716     }
01717     if( attendees.count() != 1 ) {
01718       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01719                     << "but is " << attendees.count() << endl;
01720     }
01721     QString attendeeName = firstAttendeeName( todo, i18n( "Sender" ) );
01722 
01723     QString delegatorName, dummy;
01724     Attendee* attendee = *attendees.begin();
01725     KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy );
01726     if ( delegatorName.isEmpty() ) {
01727       delegatorName = attendee->delegator();
01728     }
01729 
01730     switch( attendee->status() ) {
01731     case Attendee::NeedsAction:
01732       return i18n( "%1 indicates this task assignment still needs some action" ).arg( attendeeName );
01733     case Attendee::Accepted:
01734       if ( todo->revision() > 0 ) {
01735         if ( !sender.isEmpty() ) {
01736           if ( todo->isCompleted() ) {
01737             return i18n( "This task has been completed by assignee %1" ).arg( sender );
01738           } else {
01739             return i18n( "This task has been updated by assignee %1" ).arg( sender );
01740           }
01741         } else {
01742           if ( todo->isCompleted() ) {
01743             return i18n( "This task has been completed by an assignee" );
01744           } else {
01745             return i18n( "This task has been updated by an assignee" );
01746           }
01747         }
01748       } else {
01749         if ( delegatorName.isEmpty() ) {
01750           return i18n( "%1 accepts this task" ).arg( attendeeName );
01751         } else {
01752           return i18n( "%1 accepts this task on behalf of %2" ).
01753             arg( attendeeName ).arg( delegatorName );
01754         }
01755       }
01756     case Attendee::Tentative:
01757       if ( delegatorName.isEmpty() ) {
01758         return i18n( "%1 tentatively accepts this task" ).
01759           arg( attendeeName );
01760       } else {
01761         return i18n( "%1 tentatively accepts this task on behalf of %2" ).
01762           arg( attendeeName ).arg( delegatorName );
01763       }
01764     case Attendee::Declined:
01765       if ( delegatorName.isEmpty() ) {
01766         return i18n( "%1 declines this task" ).arg( attendeeName );
01767       } else {
01768         return i18n( "%1 declines this task on behalf of %2" ).
01769           arg( attendeeName ).arg( delegatorName );
01770       }
01771     case Attendee::Delegated: {
01772       QString delegate, dummy;
01773       KPIM::getNameAndMail( attendee->delegate(), delegate, dummy );
01774       if ( delegate.isEmpty() ) {
01775         delegate = attendee->delegate();
01776       }
01777       if ( !delegate.isEmpty() ) {
01778         return i18n( "%1 has delegated this request for the task to %2" ).
01779           arg( attendeeName ).arg( delegate );
01780       } else {
01781         return i18n( "%1 has delegated this request for the task" ).
01782           arg( attendeeName );
01783       }
01784     }
01785     case Attendee::Completed:
01786       return i18n( "The request for this task is now completed" );
01787     case Attendee::InProcess:
01788       return i18n( "%1 is still processing the task" ).
01789         arg( attendeeName );
01790     default:
01791       return i18n( "Unknown response to this task" );
01792     }
01793     break;
01794   }
01795 
01796   case Scheduler::Counter:
01797     return i18n( "%1 makes this counter proposal" ).
01798       arg( firstAttendeeName( todo, i18n( "Sender" ) ) );
01799 
01800   case Scheduler::Declinecounter:
01801     return i18n( "%1 declines the counter proposal" ).
01802       arg( firstAttendeeName( todo, i18n( "Sender" ) ) );
01803 
01804   case Scheduler::NoMethod:
01805     return i18n( "Error: iMIP message with unknown method: '%1'" ).
01806       arg( msg->method() );
01807   }
01808   return QString::null;
01809 }
01810 
01811 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
01812 {
01813   if ( !msg || !journal ) {
01814     return QString::null;
01815   }
01816 
01817   switch ( msg->method() ) {
01818   case Scheduler::Publish:
01819     return i18n("This journal has been published");
01820   case Scheduler::Request:
01821     return i18n( "You have been assigned this journal" );
01822   case Scheduler::Refresh:
01823     return i18n( "This journal was refreshed" );
01824   case Scheduler::Cancel:
01825     return i18n( "This journal was canceled" );
01826   case Scheduler::Add:
01827     return i18n( "Addition to the journal" );
01828   case Scheduler::Reply:
01829   {
01830     if ( replyMeansCounter( journal ) ) {
01831       return i18n( "Sender makes this counter proposal" );
01832     }
01833 
01834     Attendee::List attendees = journal->attendees();
01835     if( attendees.count() == 0 ) {
01836       kdDebug(5850) << "No attendees in the iCal reply!" << endl;
01837       return QString::null;
01838     }
01839     if( attendees.count() != 1 ) {
01840       kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
01841                     << "but is " << attendees.count() << endl;
01842     }
01843     Attendee* attendee = *attendees.begin();
01844 
01845     switch( attendee->status() ) {
01846     case Attendee::NeedsAction:
01847       return i18n( "Sender indicates this journal assignment still needs some action" );
01848     case Attendee::Accepted:
01849       return i18n( "Sender accepts this journal" );
01850     case Attendee::Tentative:
01851       return i18n( "Sender tentatively accepts this journal" );
01852     case Attendee::Declined:
01853       return i18n( "Sender declines this journal" );
01854     case Attendee::Delegated:
01855       return i18n( "Sender has delegated this request for the journal" );
01856     case Attendee::Completed:
01857       return i18n( "The request for this journal is now completed" );
01858     case Attendee::InProcess:
01859       return i18n( "Sender is still processing the invitation" );
01860     default:
01861       return i18n( "Unknown response to this journal" );
01862     }
01863     break;
01864   }
01865   case Scheduler::Counter:
01866     return i18n( "Sender makes this counter proposal" );
01867   case Scheduler::Declinecounter:
01868     return i18n( "Sender declines the counter proposal" );
01869   case Scheduler::NoMethod:
01870     return i18n("Error: iMIP message with unknown method: '%1'").
01871       arg( msg->method() );
01872   }
01873   return QString::null;
01874 }
01875 
01876 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
01877 {
01878   if ( !msg || !fb ) {
01879     return QString::null;
01880   }
01881 
01882   switch ( msg->method() ) {
01883   case Scheduler::Publish:
01884     return i18n("This free/busy list has been published");
01885   case Scheduler::Request:
01886     return i18n( "The free/busy list has been requested" );
01887   case Scheduler::Refresh:
01888     return i18n( "This free/busy list was refreshed" );
01889   case Scheduler::Cancel:
01890     return i18n( "This free/busy list was canceled" );
01891   case Scheduler::Add:
01892     return i18n( "Addition to the free/busy list" );
01893   case Scheduler::NoMethod:
01894   default:
01895     return i18n("Error: Free/Busy iMIP message with unknown method: '%1'").
01896       arg( msg->method() );
01897   }
01898 }
01899 
01900 static QString invitationAttendees( Incidence *incidence )
01901 {
01902   QString tmpStr;
01903   if ( !incidence ) {
01904     return tmpStr;
01905   }
01906 
01907   if ( incidence->type() == "Todo" ) {
01908     tmpStr += htmlAddTag( "u", i18n( "Assignees" ) );
01909   } else {
01910     tmpStr += htmlAddTag( "u", i18n( "Attendees" ) );
01911   }
01912   tmpStr += "<br/>";
01913 
01914   int count=0;
01915   Attendee::List attendees = incidence->attendees();
01916   if ( !attendees.isEmpty() ) {
01917 
01918     Attendee::List::ConstIterator it;
01919     for( it = attendees.begin(); it != attendees.end(); ++it ) {
01920       Attendee *a = *it;
01921       if ( !iamAttendee( a ) ) {
01922         count++;
01923         if ( count == 1 ) {
01924           tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\" columns=\"2\">";
01925         }
01926         tmpStr += "<tr>";
01927         tmpStr += "<td>";
01928         tmpStr += invitationPerson( a->email(), a->name(), QString::null );
01929         if ( !a->delegator().isEmpty() ) {
01930           tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
01931         }
01932         if ( !a->delegate().isEmpty() ) {
01933           tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
01934         }
01935         tmpStr += "</td>";
01936         tmpStr += "<td>" + a->statusStr() + "</td>";
01937         tmpStr += "</tr>";
01938       }
01939     }
01940   }
01941   if ( count ) {
01942     tmpStr += "</table>";
01943   } else {
01944     tmpStr += "<i>" + i18n( "No attendee", "None" ) + "</i>";
01945   }
01946 
01947   return tmpStr;
01948 }
01949 
01950 static QString invitationAttachments( InvitationFormatterHelper *helper, Incidence *incidence )
01951 {
01952   QString tmpStr;
01953   if ( !incidence ) {
01954     return tmpStr;
01955   }
01956 
01957   Attachment::List attachments = incidence->attachments();
01958   if ( !attachments.isEmpty() ) {
01959     tmpStr += i18n( "Attached Documents:" ) + "<ol>";
01960 
01961     Attachment::List::ConstIterator it;
01962     for( it = attachments.begin(); it != attachments.end(); ++it ) {
01963       Attachment *a = *it;
01964       tmpStr += "<li>";
01965       // Attachment icon
01966       KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
01967       const QString iconStr = mimeType ? mimeType->icon( a->uri(), false ) : QString( "application-octet-stream" );
01968       const QString iconPath = KGlobal::iconLoader()->iconPath( iconStr, KIcon::Small );
01969       if ( !iconPath.isEmpty() ) {
01970         tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
01971       }
01972       tmpStr += helper->makeLink( "ATTACH:" + a->label(), a->label() );
01973       tmpStr += "</li>";
01974     }
01975     tmpStr += "</ol>";
01976   }
01977 
01978   return tmpStr;
01979 }
01980 
01981 class IncidenceFormatter::ScheduleMessageVisitor
01982   : public IncidenceBase::Visitor
01983 {
01984   public:
01985     ScheduleMessageVisitor() : mExistingIncidence( 0 ), mMessage( 0 ) { mResult = ""; }
01986     bool act( IncidenceBase *incidence, Incidence *existingIncidence, ScheduleMessage *msg,
01987               const QString &sender )
01988     {
01989       mExistingIncidence = existingIncidence;
01990       mMessage = msg;
01991       mSender = sender;
01992       return incidence->accept( *this );
01993     }
01994     QString result() const { return mResult; }
01995 
01996   protected:
01997     QString mResult;
01998     Incidence *mExistingIncidence;
01999     ScheduleMessage *mMessage;
02000     QString mSender;
02001 };
02002 
02003 class IncidenceFormatter::InvitationHeaderVisitor
02004   : public IncidenceFormatter::ScheduleMessageVisitor
02005 {
02006   protected:
02007     bool visit( Event *event )
02008     {
02009       mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender );
02010       return !mResult.isEmpty();
02011     }
02012     bool visit( Todo *todo )
02013     {
02014       mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender );
02015       return !mResult.isEmpty();
02016     }
02017     bool visit( Journal *journal )
02018     {
02019       mResult = invitationHeaderJournal( journal, mMessage );
02020       return !mResult.isEmpty();
02021     }
02022     bool visit( FreeBusy *fb )
02023     {
02024       mResult = invitationHeaderFreeBusy( fb, mMessage );
02025       return !mResult.isEmpty();
02026     }
02027 };
02028 
02029 class IncidenceFormatter::InvitationBodyVisitor
02030   : public IncidenceFormatter::ScheduleMessageVisitor
02031 {
02032   public:
02033     InvitationBodyVisitor( bool noHtmlMode )
02034       : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ) {}
02035 
02036   protected:
02037     bool visit( Event *event )
02038     {
02039       mResult = invitationDetailsEvent( event, mNoHtmlMode );
02040       return !mResult.isEmpty();
02041     }
02042     bool visit( Todo *todo )
02043     {
02044       mResult = invitationDetailsTodo( todo, mNoHtmlMode );
02045       return !mResult.isEmpty();
02046     }
02047     bool visit( Journal *journal )
02048     {
02049       mResult = invitationDetailsJournal( journal, mNoHtmlMode );
02050       return !mResult.isEmpty();
02051     }
02052     bool visit( FreeBusy *fb )
02053     {
02054       mResult = invitationDetailsFreeBusy( fb, mNoHtmlMode );
02055       return !mResult.isEmpty();
02056     }
02057 
02058   private:
02059     bool mNoHtmlMode;
02060 };
02061 
02062 class IncidenceFormatter::IncidenceCompareVisitor
02063   : public IncidenceBase::Visitor
02064 {
02065   public:
02066     IncidenceCompareVisitor() : mExistingIncidence(0) {}
02067     bool act( IncidenceBase *incidence, Incidence* existingIncidence )
02068     {
02069       Incidence *inc = dynamic_cast<Incidence*>( incidence );
02070       if ( !inc || !existingIncidence || inc->revision() <= existingIncidence->revision() )
02071         return false;
02072       mExistingIncidence = existingIncidence;
02073       return incidence->accept( *this );
02074     }
02075 
02076     QString result() const
02077     {
02078       if ( mChanges.isEmpty() ) {
02079         return QString::null;
02080       }
02081       QString html = "<div align=\"left\"><ul><li>";
02082       html += mChanges.join( "</li><li>" );
02083       html += "</li><ul></div>";
02084       return html;
02085     }
02086 
02087   protected:
02088     bool visit( Event *event )
02089     {
02090       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
02091       compareIncidences( event, mExistingIncidence );
02092       return !mChanges.isEmpty();
02093     }
02094     bool visit( Todo *todo )
02095     {
02096       compareTodos( todo, dynamic_cast<Todo*>( mExistingIncidence ) );
02097       compareIncidences( todo, mExistingIncidence );
02098       return !mChanges.isEmpty();
02099     }
02100     bool visit( Journal *journal )
02101     {
02102       compareIncidences( journal, mExistingIncidence );
02103       return !mChanges.isEmpty();
02104     }
02105     bool visit( FreeBusy *fb )
02106     {
02107       Q_UNUSED( fb );
02108       return !mChanges.isEmpty();
02109     }
02110 
02111   private:
02112     void compareEvents( Event *newEvent, Event *oldEvent )
02113     {
02114       if ( !oldEvent || !newEvent )
02115         return;
02116       if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->doesFloat() != newEvent->doesFloat() )
02117         mChanges += i18n( "The invitation starting time has been changed from %1 to %2" )
02118                     .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) );
02119       if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->doesFloat() != newEvent->doesFloat() )
02120         mChanges += i18n( "The invitation ending time has been changed from %1 to %2" )
02121                     .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) );
02122     }
02123 
02124     void compareTodos( Todo *newTodo, Todo *oldTodo )
02125     {
02126       if ( !oldTodo || !newTodo ) {
02127         return;
02128       }
02129 
02130       if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) {
02131         mChanges += i18n( "The task has been completed" );
02132       }
02133       if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) {
02134         mChanges += i18n( "The task is no longer completed" );
02135       }
02136       if ( oldTodo->percentComplete() != newTodo->percentComplete() ) {
02137         const QString oldPer = i18n( "%1%" ).arg( oldTodo->percentComplete() );
02138         const QString newPer = i18n( "%1%" ).arg( newTodo->percentComplete() );
02139         mChanges += i18n( "The task completed percentage has changed from %1 to %2" ).
02140                     arg( oldPer, newPer );
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       if ( inc ) {
02532         html += "<tr>";
02533         if ( inc->type() == "Todo" ) {
02534           html += "<td colspan=\"9\">";
02535           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my task list]" ) );
02536         } else {
02537           html += "<td colspan=\"13\">";
02538           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my calendar]" ) );
02539         }
02540         html += "</td></tr>";
02541       }
02542       break;
02543 
02544     case Scheduler::Reply:
02545     {
02546       // Record invitation response
02547       Attendee *a = 0;
02548       Attendee *ea = 0;
02549       if ( inc ) {
02550         // First, determine if this reply is really a counter in disguise.
02551         if ( replyMeansCounter( inc ) ) {
02552           html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
02553           break;
02554         }
02555 
02556         // Next, maybe this is a declined reply that was delegated from me?
02557         // find first attendee who is delegated-from me
02558         // look a their PARTSTAT response, if the response is declined,
02559         // then we need to start over which means putting all the action
02560         // buttons and NOT putting on the [Record response..] button
02561         a = findDelegatedFromMyAttendee( inc );
02562         if ( a ) {
02563           if ( a->status() != Attendee::Accepted ||
02564                a->status() != Attendee::Tentative ) {
02565             html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
02566             break;
02567           }
02568         }
02569 
02570         // Finally, simply allow a Record of the reply
02571         if ( !inc->attendees().isEmpty() ) {
02572           a = inc->attendees().first();
02573         }
02574         if ( a ) {
02575           ea = findAttendee( existingIncidence, a->email() );
02576         }
02577       }
02578       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
02579         if ( inc && inc->revision() > 0 ) {
02580           html += "<br><u><i>";
02581           html += i18n( "The response has been recorded [%1]" ).arg( ea->statusStr() );
02582           html += "</i></u>";
02583         }
02584       } else {
02585         if ( inc ) {
02586           html += "<tr><td>";
02587           if ( inc->type() == "Todo" ) {
02588             html += helper->makeLink( "reply", i18n( "[Record response in my task list]" ) );
02589           } else {
02590             html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
02591           }
02592           html += "</td></tr>";
02593         }
02594       }
02595       break;
02596     }
02597 
02598     case Scheduler::Counter:
02599       // Counter proposal
02600       html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
02601       break;
02602 
02603     case Scheduler::Declinecounter:
02604     case Scheduler::NoMethod:
02605       break;
02606   }
02607 
02608   // close the groupware table
02609   html += "</td></tr></table>";
02610 
02611   // Add the attendee list if I am the organizer
02612   if ( myInc && helper->calendar() ) {
02613     html += invitationAttendees( helper->calendar()->incidence( inc->uid() ) );
02614   }
02615 
02616   // close the top-level table
02617   html += "</td></tr></table><br></div>";
02618 
02619   // Add the attachment list
02620   html += invitationAttachments( helper, inc );
02621 
02622   return html;
02623 }
02624 
02625 QString IncidenceFormatter::formatICalInvitation( QString invitation,
02626                                                   Calendar *mCalendar,
02627                                                   InvitationFormatterHelper *helper )
02628 {
02629   return formatICalInvitationHelper( invitation, mCalendar, helper, false, QString() );
02630 }
02631 
02632 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
02633                                                         Calendar *mCalendar,
02634                                                         InvitationFormatterHelper *helper )
02635 {
02636   return formatICalInvitationHelper( invitation, mCalendar, helper, true, QString() );
02637 }
02638 
02639 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
02640                                                         Calendar *mCalendar,
02641                                                         InvitationFormatterHelper *helper,
02642                                                         const QString &sender )
02643 {
02644   return formatICalInvitationHelper( invitation, mCalendar, helper, true, sender );
02645 }
02646 
02647 
02648 /*******************************************************************
02649  *  Helper functions for the msTNEF -> VPart converter
02650  *******************************************************************/
02651 
02652 
02653 //-----------------------------------------------------------------------------
02654 
02655 static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
02656                            const QString& fallback = QString::null)
02657 {
02658   return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
02659                             fallback );
02660 }
02661 
02662 static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
02663                            const QString& fallback = QString::null )
02664 {
02665   return tnefMsg->findNamedProp( name, fallback );
02666 }
02667 
02668 struct save_tz { char* old_tz; char* tz_env_str; };
02669 
02670 /* temporarily go to a different timezone */
02671 static struct save_tz set_tz( const char* _tc )
02672 {
02673   const char *tc = _tc?_tc:"UTC";
02674 
02675   struct save_tz rv;
02676 
02677   rv.old_tz = 0;
02678   rv.tz_env_str = 0;
02679 
02680   //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;
02681 
02682   char* tz_env = 0;
02683   if( getenv( "TZ" ) ) {
02684     tz_env = strdup( getenv( "TZ" ) );
02685     rv.old_tz = tz_env;
02686   }
02687   char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
02688   strcpy( tmp_env, "TZ=" );
02689   strcpy( tmp_env+3, tc );
02690   putenv( tmp_env );
02691 
02692   rv.tz_env_str = tmp_env;
02693 
02694   /* tmp_env is not free'ed -- it is part of the environment */
02695 
02696   tzset();
02697   //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;
02698 
02699   return rv;
02700 }
02701 
02702 /* restore previous timezone */
02703 static void unset_tz( struct save_tz old_tz )
02704 {
02705   if( old_tz.old_tz ) {
02706     char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
02707     strcpy( tmp_env, "TZ=" );
02708     strcpy( tmp_env+3, old_tz.old_tz );
02709     putenv( tmp_env );
02710     /* tmp_env is not free'ed -- it is part of the environment */
02711     free( old_tz.old_tz );
02712   } else {
02713     /* clear TZ from env */
02714     putenv( strdup("TZ") );
02715   }
02716   tzset();
02717 
02718   /* is this OK? */
02719   if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
02720 }
02721 
02722 static QDateTime utc2Local( const QDateTime& utcdt )
02723 {
02724   struct tm tmL;
02725 
02726   save_tz tmp_tz = set_tz("UTC");
02727   time_t utc = utcdt.toTime_t();
02728   unset_tz( tmp_tz );
02729 
02730   localtime_r( &utc, &tmL );
02731   return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
02732                     QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
02733 }
02734 
02735 
02736 static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
02737                                           bool bDateOnly = false )
02738 {
02739   QDate tmpDate;
02740   QTime tmpTime;
02741   int year, month, day, hour, minute, second;
02742 
02743   if( bDateOnly ) {
02744     year = dtStr.left( 4 ).toInt();
02745     month = dtStr.mid( 4, 2 ).toInt();
02746     day = dtStr.mid( 6, 2 ).toInt();
02747     hour = 0;
02748     minute = 0;
02749     second = 0;
02750   } else {
02751     year = dtStr.left( 4 ).toInt();
02752     month = dtStr.mid( 4, 2 ).toInt();
02753     day = dtStr.mid( 6, 2 ).toInt();
02754     hour = dtStr.mid( 9, 2 ).toInt();
02755     minute = dtStr.mid( 11, 2 ).toInt();
02756     second = dtStr.mid( 13, 2 ).toInt();
02757   }
02758   tmpDate.setYMD( year, month, day );
02759   tmpTime.setHMS( hour, minute, second );
02760 
02761   if( tmpDate.isValid() && tmpTime.isValid() ) {
02762     QDateTime dT = QDateTime( tmpDate, tmpTime );
02763 
02764     if( !bDateOnly ) {
02765       // correct for GMT ( == Zulu time == UTC )
02766       if (dtStr.at(dtStr.length()-1) == 'Z') {
02767         //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
02768         //localUTCOffset( dT ) );
02769         dT = utc2Local( dT );
02770       }
02771     }
02772     return dT;
02773   } else
02774     return QDateTime();
02775 }
02776 
02777 
02778 
02779 QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef )
02780 {
02781   bool bOk = false;
02782 
02783   KTNEFParser parser;
02784   QBuffer buf( tnef );
02785   CalendarLocal cal ( QString::fromLatin1( "UTC" ) );
02786   KABC::Addressee addressee;
02787   KABC::VCardConverter cardConv;
02788   ICalFormat calFormat;
02789   Event* event = new Event();
02790 
02791   if( parser.openDevice( &buf ) ) {
02792     KTNEFMessage* tnefMsg = parser.message();
02793     //QMap<int,KTNEFProperty*> props = parser.message()->properties();
02794 
02795     // Everything depends from property PR_MESSAGE_CLASS
02796     // (this is added by KTNEFParser):
02797     QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
02798       .upper();
02799     if( !msgClass.isEmpty() ) {
02800       // Match the old class names that might be used by Outlook for
02801       // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
02802       bool bCompatClassAppointment = false;
02803       bool bCompatMethodRequest = false;
02804       bool bCompatMethodCancled = false;
02805       bool bCompatMethodAccepted = false;
02806       bool bCompatMethodAcceptedCond = false;
02807       bool bCompatMethodDeclined = false;
02808       if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
02809         bCompatClassAppointment = true;
02810         if( msgClass.endsWith( ".MTGREQ" ) )
02811           bCompatMethodRequest = true;
02812         if( msgClass.endsWith( ".MTGCNCL" ) )
02813           bCompatMethodCancled = true;
02814         if( msgClass.endsWith( ".MTGRESPP" ) )
02815           bCompatMethodAccepted = true;
02816         if( msgClass.endsWith( ".MTGRESPA" ) )
02817           bCompatMethodAcceptedCond = true;
02818         if( msgClass.endsWith( ".MTGRESPN" ) )
02819           bCompatMethodDeclined = true;
02820       }
02821       bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );
02822 
02823       if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
02824         // Compose a vCal
02825         bool bIsReply = false;
02826         QString prodID = "-//Microsoft Corporation//Outlook ";
02827         prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
02828         prodID += "MIMEDIR/EN\n";
02829         prodID += "VERSION:2.0\n";
02830         calFormat.setApplication( "Outlook", prodID );
02831 
02832         Scheduler::Method method;
02833         if( bCompatMethodRequest )
02834           method = Scheduler::Request;
02835         else if( bCompatMethodCancled )
02836           method = Scheduler::Cancel;
02837         else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
02838                  bCompatMethodDeclined ) {
02839           method = Scheduler::Reply;
02840           bIsReply = true;
02841         } else {
02842           // pending(khz): verify whether "0x0c17" is the right tag ???
02843           //
02844           // at the moment we think there are REQUESTS and UPDATES
02845           //
02846           // but WHAT ABOUT REPLIES ???
02847           //
02848           //
02849 
02850           if( tnefMsg->findProp(0x0c17) == "1" )
02851             bIsReply = true;
02852           method = Scheduler::Request;
02853         }
02854 
02856         ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );
02857 
02858         QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );
02859 
02860         if( !sSenderSearchKeyEmail.isEmpty() ) {
02861           int colon = sSenderSearchKeyEmail.find( ':' );
02862           // May be e.g. "SMTP:KHZ@KDE.ORG"
02863           if( sSenderSearchKeyEmail.find( ':' ) == -1 )
02864             sSenderSearchKeyEmail.remove( 0, colon+1 );
02865         }
02866 
02867         QString s( tnefMsg->findProp( 0x0e04 ) );
02868         QStringList attendees = QStringList::split( ';', s );
02869         if( attendees.count() ) {
02870           for( QStringList::Iterator it = attendees.begin();
02871                it != attendees.end(); ++it ) {
02872             // Skip all entries that have no '@' since these are
02873             // no mail addresses
02874             if( (*it).find('@') == -1 ) {
02875               s = (*it).stripWhiteSpace();
02876 
02877               Attendee *attendee = new Attendee( s, s, true );
02878               if( bIsReply ) {
02879                 if( bCompatMethodAccepted )
02880                   attendee->setStatus( Attendee::Accepted );
02881                 if( bCompatMethodDeclined )
02882                   attendee->setStatus( Attendee::Declined );
02883                 if( bCompatMethodAcceptedCond )
02884                   attendee->setStatus(Attendee::Tentative);
02885               } else {
02886                 attendee->setStatus( Attendee::NeedsAction );
02887                 attendee->setRole( Attendee::ReqParticipant );
02888               }
02889               event->addAttendee(attendee);
02890             }
02891           }
02892         } else {
02893           // Oops, no attendees?
02894           // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
02895           s = sSenderSearchKeyEmail;
02896           if( !s.isEmpty() ) {
02897             Attendee *attendee = new Attendee( QString::null, QString::null,
02898                                                true );
02899             if( bIsReply ) {
02900               if( bCompatMethodAccepted )
02901                 attendee->setStatus( Attendee::Accepted );
02902               if( bCompatMethodAcceptedCond )
02903                 attendee->setStatus( Attendee::Declined );
02904               if( bCompatMethodDeclined )
02905                 attendee->setStatus( Attendee::Tentative );
02906             } else {
02907               attendee->setStatus(Attendee::NeedsAction);
02908               attendee->setRole(Attendee::ReqParticipant);
02909             }
02910             event->addAttendee(attendee);
02911           }
02912         }
02913         s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
02914         if( s.isEmpty() && !bIsReply )
02915           s = sSenderSearchKeyEmail;
02916         // TODO: Use the common name?
02917         if( !s.isEmpty() )
02918           event->setOrganizer( s );
02919 
02920         s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
02921           .replace( QChar( ':' ), QString::null );
02922         event->setDtStart( QDateTime::fromString( s ) ); // ## Format??
02923 
02924         s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
02925           .replace( QChar( ':' ), QString::null );
02926         event->setDtEnd( QDateTime::fromString( s ) );
02927 
02928         s = tnefMsg->findProp( 0x8208 );
02929         event->setLocation( s );
02930 
02931         // is it OK to set this to OPAQUE always ??
02932         //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
02933         //vPart += "SEQUENCE:0\n";
02934 
02935         // is "0x0023" OK  -  or should we look for "0x0003" ??
02936         s = tnefMsg->findProp( 0x0023 );
02937         event->setUid( s );
02938 
02939         // PENDING(khz): is this value in local timezone? Must it be
02940         // adjusted? Most likely this is a bug in the server or in
02941         // Outlook - we ignore it for now.
02942         s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
02943           .replace( QChar( ':' ), QString::null );
02944         // ### libkcal always uses currentDateTime()
02945         // event->setDtStamp(QDateTime::fromString(s));
02946 
02947         s = tnefMsg->findNamedProp( "Keywords" );
02948         event->setCategories( s );
02949 
02950         s = tnefMsg->findProp( 0x1000 );
02951         event->setDescription( s );
02952 
02953         s = tnefMsg->findProp( 0x0070 );
02954         event->setSummary( s );
02955 
02956         s = tnefMsg->findProp( 0x0026 );
02957         event->setPriority( s.toInt() );
02958 
02959         // is reminder flag set ?
02960         if(!tnefMsg->findProp(0x8503).isEmpty()) {
02961           Alarm *alarm = new Alarm(event);
02962           QDateTime highNoonTime =
02963             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
02964                                      .replace( QChar( '-' ), "" )
02965                                      .replace( QChar( ':' ), "" ) );
02966           QDateTime wakeMeUpTime =
02967             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
02968                                      .replace( QChar( '-' ), "" )
02969                                      .replace( QChar( ':' ), "" ) );
02970           alarm->setTime(wakeMeUpTime);
02971 
02972           if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
02973             alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
02974           else
02975             // default: wake them up 15 minutes before the appointment
02976             alarm->setStartOffset( Duration( 15*60 ) );
02977           alarm->setDisplayAlarm( i18n( "Reminder" ) );
02978 
02979           // Sorry: the different action types are not known (yet)
02980           //        so we always set 'DISPLAY' (no sounds, no images...)
02981           event->addAlarm( alarm );
02982         }
02983         cal.addEvent( event );
02984         bOk = true;
02985         // we finished composing a vCal
02986       } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
02987         addressee.setUid( stringProp( tnefMsg, attMSGID ) );
02988         addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) );
02989         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
02990         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
02991         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
02992         addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) );
02993         addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) );
02994         addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
02995         addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
02996         addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) );
02997         addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) );
02998         addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) );
02999 
03000         QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
03001           .replace( QChar( '-' ), QString::null )
03002           .replace( QChar( ':' ), QString::null );
03003         if( !s.isEmpty() )
03004           addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );
03005 
03006         addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );
03007 
03008         // collect parts of Name entry
03009         addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
03010         addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
03011         addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) );
03012         addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) );
03013         addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );
03014 
03015         addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
03016         addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
03017         addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
03018         /*
03019         the MAPI property ID of this (multiline) )field is unknown:
03020         vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
03021         */
03022 
03023         KABC::Address adr;
03024         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) );
03025         adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
03026         adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
03027         adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
03028         adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) );
03029         adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
03030         adr.setType(KABC::Address::Home);
03031         addressee.insertAddress(adr);
03032 
03033         adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
03034         adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) );
03035         adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) );
03036         adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) );
03037         adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
03038         adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) );
03039         adr.setType( KABC::Address::Work );
03040         addressee.insertAddress( adr );
03041 
03042         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) );
03043         adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
03044         adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
03045         adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
03046         adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) );
03047         adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
03048         adr.setType( KABC::Address::Dom );
03049         addressee.insertAddress(adr);
03050 
03051         // problem: the 'other' address was stored by KOrganizer in
03052         //          a line looking like the following one:
03053         // 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
03054 
03055         QString nr;
03056         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
03057         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) );
03058         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
03059         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) );
03060         nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
03061         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) );
03062         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
03063         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) );
03064         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
03065         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) );
03066 
03067         s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
03068           .replace( QChar( '-' ), QString::null )
03069           .replace( QChar( ':' ), QString::null );
03070         if( !s.isEmpty() )
03071           addressee.setBirthday( QDateTime::fromString( s ) );
03072 
03073         bOk = ( !addressee.isEmpty() );
03074       } else if( "IPM.NOTE" == msgClass ) {
03075 
03076       } // else if ... and so on ...
03077     }
03078   }
03079 
03080   // Compose return string
03081   QString iCal = calFormat.toString( &cal );
03082   if( !iCal.isEmpty() )
03083     // This was an iCal
03084     return iCal;
03085 
03086   // Not an iCal - try a vCard
03087   KABC::VCardConverter converter;
03088   return converter.createVCard( addressee );
03089 }
03090 
03091 
03092 QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef,
03093         Calendar *mCalendar, InvitationFormatterHelper *helper )
03094 {
03095   QString vPart = IncidenceFormatter::msTNEFToVPart( tnef );
03096   QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper );
03097   if( !iCal.isEmpty() )
03098     return iCal;
03099   return vPart;
03100 }
03101 
03102 
03103 
03104 
03105 /*******************************************************************
03106  *  Helper functions for the Incidence tooltips
03107  *******************************************************************/
03108 
03109 class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
03110 {
03111   public:
03112     ToolTipVisitor()
03113       : mCalendar( 0 ), mRichText( true ), mResult( "" ) {}
03114 
03115     bool act( Calendar *calendar, IncidenceBase *incidence,
03116               const QDate &date=QDate(), bool richText=true )
03117     {
03118       mCalendar = calendar;
03119       mDate = date;
03120       mRichText = richText;
03121       mResult = "";
03122       return incidence ? incidence->accept( *this ) : false;
03123     }
03124     QString result() const { return mResult; }
03125 
03126   protected:
03127     bool visit( Event *event );
03128     bool visit( Todo *todo );
03129     bool visit( Journal *journal );
03130     bool visit( FreeBusy *fb );
03131 
03132     QString dateRangeText( Event *event, const QDate &date );
03133     QString dateRangeText( Todo *todo, const QDate &date );
03134     QString dateRangeText( Journal *journal );
03135     QString dateRangeText( FreeBusy *fb );
03136 
03137     QString generateToolTip( Incidence* incidence, QString dtRangeText );
03138 
03139   protected:
03140     Calendar *mCalendar;
03141     QDate mDate;
03142     bool mRichText;
03143     QString mResult;
03144 };
03145 
03146 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const QDate &date )
03147 {
03148   QString ret;
03149   QString tmp;
03150 
03151   QDateTime startDt = event->dtStart();
03152   QDateTime endDt = event->dtEnd();
03153   if ( event->doesRecur() ) {
03154     if ( date.isValid() ) {
03155       QDateTime dt( date, QTime( 0, 0, 0 ) );
03156       int diffDays = startDt.daysTo( dt );
03157       dt = dt.addSecs( -1 );
03158       startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() );
03159       if ( event->hasEndDate() ) {
03160         endDt = endDt.addDays( diffDays );
03161         if ( startDt > endDt ) {
03162           startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() );
03163           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
03164         }
03165       }
03166     }
03167   }
03168   if ( event->isMultiDay() ) {
03169 
03170     tmp = "<br>" + i18n("Event start", "<i>From:</i>&nbsp;%1");
03171     if (event->doesFloat())
03172       ret += tmp.arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03173     else
03174       ret += tmp.arg( IncidenceFormatter::dateToString( startDt ).replace(" ", "&nbsp;") );
03175 
03176     tmp = "<br>" + i18n("Event end","<i>To:</i>&nbsp;%1");
03177     if (event->doesFloat())
03178       ret += tmp.arg( IncidenceFormatter::dateToString( endDt, false ).replace(" ", "&nbsp;") );
03179     else
03180       ret += tmp.arg( IncidenceFormatter::dateToString( endDt ).replace(" ", "&nbsp;") );
03181 
03182   } else {
03183 
03184     ret += "<br>"+i18n("<i>Date:</i>&nbsp;%1").
03185            arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03186     if ( !event->doesFloat() ) {
03187       const QString dtStartTime =
03188         IncidenceFormatter::timeToString( startDt, true ).replace( " ", "&nbsp;" );
03189       const QString dtEndTime =
03190         IncidenceFormatter::timeToString( endDt, true ).replace( " ", "&nbsp;" );
03191       if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00'
03192         tmp = "<br>" + i18n("time for event, &nbsp; to prevent ugly line breaks",
03193         "<i>Time:</i>&nbsp;%1").
03194         arg( dtStartTime );
03195       } else {
03196         tmp = "<br>" + i18n("time range for event, &nbsp; to prevent ugly line breaks",
03197         "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2").
03198         arg( dtStartTime, dtEndTime );
03199       }
03200       ret += tmp;
03201     }
03202 
03203   }
03204   return ret;
03205 }
03206 
03207 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const QDate &date )
03208 {
03209   QString ret;
03210   bool floats( todo->doesFloat() );
03211 
03212   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03213     QDateTime startDt = todo->dtStart();
03214     if ( todo->doesRecur() ) {
03215       if ( date.isValid() ) {
03216         startDt.setDate( date );
03217       }
03218     }
03219     ret += "<br>" +
03220            i18n("<i>Start:</i>&nbsp;%1").
03221            arg( IncidenceFormatter::dateTimeToString( startDt, floats, false ).
03222                 replace( " ", "&nbsp;" ) );
03223   }
03224 
03225   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03226     QDateTime dueDt = todo->dtDue();
03227     if ( todo->doesRecur() ) {
03228       if ( date.isValid() ) {
03229         QDateTime dt( date, QTime( 0, 0, 0 ) );
03230         dt = dt.addSecs( -1 );
03231         dueDt.setDate( todo->recurrence()->getNextDateTime( dt ).date() );
03232       }
03233     }
03234     ret += "<br>" +
03235            i18n("<i>Due:</i>&nbsp;%1").
03236            arg( IncidenceFormatter::dateTimeToString( dueDt, floats, false ).
03237                 replace( " ", "&nbsp;" ) );
03238   }
03239 
03240   // Print priority and completed info here, for lack of a better place
03241 
03242   if ( todo->priority() > 0 ) {
03243     ret += "<br>";
03244     ret += "<i>" + i18n( "Priority:" ) + "</i>" + "&nbsp;";
03245     ret += QString::number( todo->priority() );
03246   }
03247 
03248   ret += "<br>";
03249   if ( todo->isCompleted() ) {
03250     ret += "<i>" + i18n( "Completed:" ) + "</i>" + "&nbsp;";
03251     ret += todo->completedStr().replace( " ", "&nbsp;" );
03252   } else {
03253     ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + "&nbsp;";
03254     ret += i18n( "%1%" ).arg( todo->percentComplete() );
03255   }
03256 
03257   return ret;
03258 }
03259 
03260 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal )
03261 {
03262   QString ret;
03263   if (journal->dtStart().isValid() ) {
03264     ret += "<br>" +
03265            i18n("<i>Date:</i>&nbsp;%1").
03266            arg( IncidenceFormatter::dateToString( journal->dtStart(), false ) );
03267   }
03268   return ret;
03269 }
03270 
03271 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
03272 {
03273   QString tmp( "<br>" + i18n("<i>Period start:</i>&nbsp;%1") );
03274   QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) );
03275   tmp = "<br>" + i18n("<i>Period start:</i>&nbsp;%1");
03276   ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) );
03277   return ret;
03278 }
03279 
03280 
03281 
03282 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
03283 {
03284   mResult = generateToolTip( event, dateRangeText( event, mDate ) );
03285   return !mResult.isEmpty();
03286 }
03287 
03288 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
03289 {
03290   mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
03291   return !mResult.isEmpty();
03292 }
03293 
03294 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
03295 {
03296   mResult = generateToolTip( journal, dateRangeText( journal ) );
03297   return !mResult.isEmpty();
03298 }
03299 
03300 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
03301 {
03302   mResult = "<qt><b>" + i18n("Free/Busy information for %1")
03303         .arg(fb->organizer().fullName()) + "</b>";
03304   mResult += dateRangeText( fb );
03305   mResult += "</qt>";
03306   return !mResult.isEmpty();
03307 }
03308 
03309 static QString tooltipPerson( const QString& email, QString name )
03310 {
03311   // Make the search, if there is an email address to search on,
03312   // and name is missing
03313   if ( name.isEmpty() && !email.isEmpty() ) {
03314     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
03315     KABC::Addressee::List addressList = add_book->findByEmail( email );
03316     if ( !addressList.isEmpty() ) {
03317       KABC::Addressee o = addressList.first();
03318       if ( !o.isEmpty() && addressList.size() < 2 ) {
03319         // use the name from the addressbook
03320         name = o.formattedName();
03321       }
03322     }
03323   }
03324 
03325   // Show the attendee
03326   QString tmpString = ( name.isEmpty() ? email : name );
03327 
03328   return tmpString;
03329 }
03330 
03331 static QString etc = i18n( "elipsis", "..." );
03332 static QString tooltipFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role )
03333 {
03334   int maxNumAtts = 8; // maximum number of people to print per attendee role
03335   QString sep = i18n( "separator for lists of people names", ", " );
03336   int sepLen = sep.length();
03337 
03338   int i = 0;
03339   QString tmpStr;
03340   Attendee::List::ConstIterator it;
03341   Attendee::List attendees = incidence->attendees();
03342 
03343   for( it = attendees.begin(); it != attendees.end(); ++it ) {
03344     Attendee *a = *it;
03345     if ( a->role() != role ) {
03346       // skip not this role
03347       continue;
03348     }
03349     if ( a->email() == incidence->organizer().email() ) {
03350       // skip attendee that is also the organizer
03351       continue;
03352     }
03353     if ( i == maxNumAtts ) {
03354       tmpStr += etc;
03355       break;
03356     }
03357     tmpStr += tooltipPerson( a->email(), a->name() );
03358     if ( !a->delegator().isEmpty() ) {
03359       tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
03360     }
03361     if ( !a->delegate().isEmpty() ) {
03362       tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
03363     }
03364     tmpStr += sep;
03365     i++;
03366   }
03367   if ( tmpStr.endsWith( sep ) ) {
03368     tmpStr.truncate( tmpStr.length() - sepLen );
03369   }
03370   return tmpStr;
03371 }
03372 
03373 static QString tooltipFormatAttendees( Incidence *incidence )
03374 {
03375   QString tmpStr, str;
03376 
03377   // Add organizer link
03378   int attendeeCount = incidence->attendees().count();
03379   if ( attendeeCount > 1 ||
03380        ( attendeeCount == 1 &&
03381          incidence->organizer().email() != incidence->attendees().first()->email() ) ) {
03382     tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "&nbsp;";
03383     tmpStr += tooltipPerson( incidence->organizer().email(),
03384                              incidence->organizer().name() );
03385   }
03386 
03387   // Add "chair"
03388   str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair );
03389   if ( !str.isEmpty() ) {
03390     tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "&nbsp;";
03391     tmpStr += str;
03392   }
03393 
03394   // Add required participants
03395   str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant );
03396   if ( !str.isEmpty() ) {
03397     tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "&nbsp;";
03398     tmpStr += str;
03399   }
03400 
03401   // Add optional participants
03402   str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant );
03403   if ( !str.isEmpty() ) {
03404     tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "&nbsp;";
03405     tmpStr += str;
03406   }
03407 
03408   // Add observers
03409   str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant );
03410   if ( !str.isEmpty() ) {
03411     tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "&nbsp;";
03412     tmpStr += str;
03413   }
03414 
03415   return tmpStr;
03416 }
03417 
03418 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence* incidence, QString dtRangeText )
03419 {
03420   uint maxDescLen = 120; // maximum description chars to print (before elipsis)
03421 
03422   if ( !incidence ) {
03423     return QString::null;
03424   }
03425 
03426   QString tmp = "<qt>";
03427 
03428   // header
03429   tmp += "<b>" + incidence->summary().replace( "\n", "<br>" ) + "</b>";
03430   //NOTE: using <hr> seems to confuse Qt3 tooltips in some cases so use "-----"
03431   tmp += "<br>----------<br>";
03432 
03433   if ( mCalendar ) {
03434     QString calStr = IncidenceFormatter::resourceString( mCalendar, incidence );
03435     if ( !calStr.isEmpty() ) {
03436       tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + "&nbsp;";
03437       tmp += calStr;
03438     }
03439   }
03440 
03441   tmp += dtRangeText;
03442 
03443   if ( !incidence->location().isEmpty() ) {
03444     tmp += "<br>";
03445     tmp += "<i>" + i18n( "Location:" ) + "</i>" + "&nbsp;";
03446     tmp += incidence->location().replace( "\n", "<br>" );
03447   }
03448 
03449   QString durStr = IncidenceFormatter::durationString( incidence );
03450   if ( !durStr.isEmpty() ) {
03451     tmp += "<br>";
03452     tmp += "<i>" + i18n( "Duration:" ) + "</i>" + "&nbsp;";
03453     tmp += durStr;
03454   }
03455 
03456   if ( incidence->doesRecur() ) {
03457     tmp += "<br>";
03458     tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + "&nbsp;";
03459     tmp += IncidenceFormatter::recurrenceString( incidence );
03460   }
03461 
03462   if ( !incidence->description().isEmpty() ) {
03463     QString desc( incidence->description() );
03464     if ( desc.length() > maxDescLen ) {
03465       desc = desc.left( maxDescLen ) + etc;
03466     }
03467     tmp += "<br>----------<br>";
03468     tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
03469     tmp += desc.replace( "\n", "<br>" );
03470     tmp += "<br>----------";
03471   }
03472 
03473   int reminderCount = incidence->alarms().count();
03474   if ( reminderCount > 0 && incidence->isAlarmEnabled() ) {
03475     tmp += "<br>";
03476     tmp += "<i>" + i18n( "Reminder:", "%n Reminders:", reminderCount ) + "</i>" + "&nbsp;";
03477     tmp += IncidenceFormatter::reminderStringList( incidence ).join( ", " );
03478   }
03479 
03480   tmp += "<br>";
03481   tmp += tooltipFormatAttendees( incidence );
03482 
03483   int categoryCount = incidence->categories().count();
03484   if ( categoryCount > 0 ) {
03485     tmp += "<br>";
03486     tmp += "<i>" + i18n( "Category:", "%n Categories:", categoryCount ) + "</i>" + "&nbsp;";
03487     tmp += incidence->categories().join( ", " );
03488   }
03489 
03490   tmp += "</qt>";
03491   return tmp;
03492 }
03493 
03494 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
03495 {
03496   return toolTipStr( 0, incidence, QDate(), richText );
03497 }
03498 
03499 QString IncidenceFormatter::toolTipStr( Calendar *calendar,
03500                                         IncidenceBase *incidence,
03501                                         const QDate &date,
03502                                         bool richText )
03503 {
03504   ToolTipVisitor v;
03505   if ( v.act( calendar, incidence, date, richText ) ) {
03506     return v.result();
03507   } else {
03508     return QString::null;
03509   }
03510 }
03511 
03512 /*******************************************************************
03513  *  Helper functions for the Incidence tooltips
03514  *******************************************************************/
03515 
03516 class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
03517 {
03518   public:
03519     MailBodyVisitor() : mResult( "" ) {}
03520 
03521     bool act( IncidenceBase *incidence )
03522     {
03523       mResult = "";
03524       return incidence ? incidence->accept( *this ) : false;
03525     }
03526     QString result() const { return mResult; }
03527 
03528   protected:
03529     bool visit( Event *event );
03530     bool visit( Todo *todo );
03531     bool visit( Journal *journal );
03532     bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); }
03533   protected:
03534     QString mResult;
03535 };
03536 
03537 
03538 static QString mailBodyIncidence( Incidence *incidence )
03539 {
03540   QString body;
03541   if ( !incidence->summary().isEmpty() ) {
03542     body += i18n("Summary: %1\n").arg( incidence->summary() );
03543   }
03544   if ( !incidence->organizer().isEmpty() ) {
03545     body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() );
03546   }
03547   if ( !incidence->location().isEmpty() ) {
03548     body += i18n("Location: %1\n").arg( incidence->location() );
03549   }
03550   return body;
03551 }
03552 
03553 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
03554 {
03555   QString recurrence[]= {i18n("no recurrence", "None"),
03556     i18n("Minutely"), i18n("Hourly"), i18n("Daily"),
03557     i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"),
03558     i18n("Yearly"), i18n("Yearly"), i18n("Yearly")};
03559 
03560   mResult = mailBodyIncidence( event );
03561   mResult += i18n("Start Date: %1\n").
03562              arg( IncidenceFormatter::dateToString( event->dtStart(), true ) );
03563   if ( !event->doesFloat() ) {
03564     mResult += i18n("Start Time: %1\n").
03565                arg( IncidenceFormatter::timeToString( event->dtStart(), true ) );
03566   }
03567   if ( event->dtStart() != event->dtEnd() ) {
03568     mResult += i18n("End Date: %1\n").
03569                arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) );
03570   }
03571   if ( !event->doesFloat() ) {
03572     mResult += i18n("End Time: %1\n").
03573                arg( IncidenceFormatter::timeToString( event->dtEnd(), true ) );
03574   }
03575   if ( event->doesRecur() ) {
03576     Recurrence *recur = event->recurrence();
03577     // TODO: Merge these two to one of the form "Recurs every 3 days"
03578     mResult += i18n("Recurs: %1\n")
03579              .arg( recurrence[ recur->recurrenceType() ] );
03580     mResult += i18n("Frequency: %1\n")
03581              .arg( event->recurrence()->frequency() );
03582 
03583     if ( recur->duration() > 0 ) {
03584       mResult += i18n ("Repeats once", "Repeats %n times", recur->duration());
03585       mResult += '\n';
03586     } else {
03587       if ( recur->duration() != -1 ) {
03588 // TODO_Recurrence: What to do with floating
03589         QString endstr;
03590         if ( event->doesFloat() ) {
03591           endstr = KGlobal::locale()->formatDate( recur->endDate() );
03592         } else {
03593           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() );
03594         }
03595         mResult += i18n("Repeat until: %1\n").arg( endstr );
03596       } else {
03597         mResult += i18n("Repeats forever\n");
03598       }
03599     }
03600   }
03601   QString details = event->description();
03602   if ( !details.isEmpty() ) {
03603     mResult += i18n("Details:\n%1\n").arg( details );
03604   }
03605   return !mResult.isEmpty();
03606 }
03607 
03608 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
03609 {
03610   mResult = mailBodyIncidence( todo );
03611 
03612   if ( todo->hasStartDate() ) {
03613     mResult += i18n("Start Date: %1\n").
03614                arg( IncidenceFormatter::dateToString( todo->dtStart( false ), true ) );
03615     if ( !todo->doesFloat() ) {
03616       mResult += i18n("Start Time: %1\n").
03617                  arg( IncidenceFormatter::timeToString( todo->dtStart( false ),true ) );
03618     }
03619   }
03620   if ( todo->hasDueDate() ) {
03621     mResult += i18n("Due Date: %1\n").
03622                arg( IncidenceFormatter::dateToString( todo->dtDue(), true ) );
03623     if ( !todo->doesFloat() ) {
03624       mResult += i18n("Due Time: %1\n").
03625                  arg( IncidenceFormatter::timeToString( todo->dtDue(), true ) );
03626     }
03627   }
03628   QString details = todo->description();
03629   if ( !details.isEmpty() ) {
03630     mResult += i18n("Details:\n%1\n").arg( details );
03631   }
03632   return !mResult.isEmpty();
03633 }
03634 
03635 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
03636 {
03637   mResult = mailBodyIncidence( journal );
03638   mResult += i18n("Date: %1\n").
03639              arg( IncidenceFormatter::dateToString( journal->dtStart(), true ) );
03640   if ( !journal->doesFloat() ) {
03641     mResult += i18n("Time: %1\n").
03642                arg( IncidenceFormatter::timeToString( journal->dtStart(), true ) );
03643   }
03644   if ( !journal->description().isEmpty() )
03645     mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() );
03646   return !mResult.isEmpty();
03647 }
03648 
03649 
03650 
03651 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
03652 {
03653   if ( !incidence )
03654     return QString::null;
03655 
03656   MailBodyVisitor v;
03657   if ( v.act( incidence ) ) {
03658     return v.result();
03659   }
03660   return QString::null;
03661 }
03662 
03663 static QString recurEnd( Incidence *incidence )
03664 {
03665   QString endstr;
03666   if ( incidence->doesFloat() ) {
03667     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
03668   } else {
03669     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
03670   }
03671   return endstr;
03672 }
03673 
03674 /************************************
03675  *  More static formatting functions
03676  ************************************/
03677 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
03678 {
03679   if ( !incidence->doesRecur() ) {
03680     return i18n( "No recurrence" );
03681   }
03682   QStringList dayList;
03683   dayList.append( i18n( "31st Last" ) );
03684   dayList.append( i18n( "30th Last" ) );
03685   dayList.append( i18n( "29th Last" ) );
03686   dayList.append( i18n( "28th Last" ) );
03687   dayList.append( i18n( "27th Last" ) );
03688   dayList.append( i18n( "26th Last" ) );
03689   dayList.append( i18n( "25th Last" ) );
03690   dayList.append( i18n( "24th Last" ) );
03691   dayList.append( i18n( "23rd Last" ) );
03692   dayList.append( i18n( "22nd Last" ) );
03693   dayList.append( i18n( "21st Last" ) );
03694   dayList.append( i18n( "20th Last" ) );
03695   dayList.append( i18n( "19th Last" ) );
03696   dayList.append( i18n( "18th Last" ) );
03697   dayList.append( i18n( "17th Last" ) );
03698   dayList.append( i18n( "16th Last" ) );
03699   dayList.append( i18n( "15th Last" ) );
03700   dayList.append( i18n( "14th Last" ) );
03701   dayList.append( i18n( "13th Last" ) );
03702   dayList.append( i18n( "12th Last" ) );
03703   dayList.append( i18n( "11th Last" ) );
03704   dayList.append( i18n( "10th Last" ) );
03705   dayList.append( i18n( "9th Last" ) );
03706   dayList.append( i18n( "8th Last" ) );
03707   dayList.append( i18n( "7th Last" ) );
03708   dayList.append( i18n( "6th Last" ) );
03709   dayList.append( i18n( "5th Last" ) );
03710   dayList.append( i18n( "4th Last" ) );
03711   dayList.append( i18n( "3rd Last" ) );
03712   dayList.append( i18n( "2nd Last" ) );
03713   dayList.append( i18n( "last day of the month", "Last" ) );
03714   dayList.append( i18n( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
03715   dayList.append( i18n( "1st" ) );
03716   dayList.append( i18n( "2nd" ) );
03717   dayList.append( i18n( "3rd" ) );
03718   dayList.append( i18n( "4th" ) );
03719   dayList.append( i18n( "5th" ) );
03720   dayList.append( i18n( "6th" ) );
03721   dayList.append( i18n( "7th" ) );
03722   dayList.append( i18n( "8th" ) );
03723   dayList.append( i18n( "9th" ) );
03724   dayList.append( i18n( "10th" ) );
03725   dayList.append( i18n( "11th" ) );
03726   dayList.append( i18n( "12th" ) );
03727   dayList.append( i18n( "13th" ) );
03728   dayList.append( i18n( "14th" ) );
03729   dayList.append( i18n( "15th" ) );
03730   dayList.append( i18n( "16th" ) );
03731   dayList.append( i18n( "17th" ) );
03732   dayList.append( i18n( "18th" ) );
03733   dayList.append( i18n( "19th" ) );
03734   dayList.append( i18n( "20th" ) );
03735   dayList.append( i18n( "21st" ) );
03736   dayList.append( i18n( "22nd" ) );
03737   dayList.append( i18n( "23rd" ) );
03738   dayList.append( i18n( "24th" ) );
03739   dayList.append( i18n( "25th" ) );
03740   dayList.append( i18n( "26th" ) );
03741   dayList.append( i18n( "27th" ) );
03742   dayList.append( i18n( "28th" ) );
03743   dayList.append( i18n( "29th" ) );
03744   dayList.append( i18n( "30th" ) );
03745   dayList.append( i18n( "31st" ) );
03746   int weekStart = KGlobal::locale()->weekStartDay();
03747   QString dayNames;
03748   QString recurStr, txt;
03749   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
03750   Recurrence *recur = incidence->recurrence();
03751   switch ( recur->recurrenceType() ) {
03752   case Recurrence::rNone:
03753     return i18n( "No recurrence" );
03754 
03755   case Recurrence::rMinutely:
03756     recurStr = i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() );
03757     if ( recur->duration() != -1 ) {
03758       txt = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
03759       if ( recur->duration() >  0 ) {
03760         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03761       }
03762       return txt;
03763     }
03764     return recurStr;
03765 
03766   case Recurrence::rHourly:
03767     recurStr = i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() );
03768     if ( recur->duration() != -1 ) {
03769       txt = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
03770       if ( recur->duration() >  0 ) {
03771         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03772       }
03773       return txt;
03774     }
03775     return recurStr;
03776 
03777   case Recurrence::rDaily:
03778     recurStr = i18n( "Recurs daily", "Recurs every %n days", recur->frequency() );
03779     if ( recur->duration() != -1 ) {
03780 
03781       txt = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
03782       if ( recur->duration() >  0 ) {
03783         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03784       }
03785       return txt;
03786     }
03787     return recurStr;
03788 
03789   case Recurrence::rWeekly:
03790   {
03791     recurStr = i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() );
03792 
03793     bool addSpace = false;
03794     for ( int i = 0; i < 7; ++i ) {
03795       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
03796         if ( addSpace ) {
03797           dayNames.append( i18n( "separator for list of days", ", " ) );
03798         }
03799         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, true ) );
03800         addSpace = true;
03801       }
03802     }
03803     if ( dayNames.isEmpty() ) {
03804       dayNames = i18n( "Recurs weekly on no days", "no days" );
03805     }
03806     if ( recur->duration() != -1 ) {
03807       txt = i18n( "%1 on %2 until %3" ).
03808             arg( recurStr ).arg( dayNames ).arg( recurEnd( incidence ) );
03809       if ( recur->duration() >  0 ) {
03810         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03811       }
03812       return txt;
03813     }
03814     txt = i18n( "%1 on %2" ).arg( recurStr ).arg( dayNames );
03815     return txt;
03816   }
03817 
03818   case Recurrence::rMonthlyPos:
03819   {
03820     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
03821 
03822     KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
03823     if ( recur->duration() != -1 ) {
03824       txt = i18n( "%1 on the %2 %3 until %4" ).
03825             arg( recurStr ).
03826             arg( dayList[rule.pos() + 31] ).
03827             arg( calSys->weekDayName( rule.day(), false ) ).
03828             arg( recurEnd( incidence ) );
03829       if ( recur->duration() >  0 ) {
03830         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03831       }
03832       return txt;
03833     }
03834     txt = i18n( "%1 on the %2 %3" ).
03835           arg( recurStr ).
03836           arg( dayList[rule.pos() + 31] ).
03837           arg( calSys->weekDayName( rule.day(), false ) );
03838     return txt;
03839   }
03840 
03841   case Recurrence::rMonthlyDay:
03842   {
03843     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
03844 
03845     int days = recur->monthDays()[0];
03846     if ( recur->duration() != -1 ) {
03847       txt = i18n( "%1 on the %2 day until %3" ).
03848             arg( recurStr ).
03849             arg( dayList[days + 31] ).
03850             arg( recurEnd( incidence ) );
03851       if ( recur->duration() >  0 ) {
03852         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03853       }
03854       return txt;
03855     }
03856     txt = i18n( "%1 on the %2 day" ).arg( recurStr ).arg( dayList[days + 31] );
03857     return txt;
03858   }
03859 
03860   case Recurrence::rYearlyMonth:
03861   {
03862     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
03863 
03864     if ( recur->duration() != -1 ) {
03865       if ( !recur->yearDates().isEmpty() ) {
03866         txt = i18n( "%1 on %2 %3 until %4" ).
03867               arg( recurStr ).
03868               arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
03869               arg( dayList[ recur->yearDates()[0] + 31 ] ).
03870               arg( recurEnd( incidence ) );
03871         if ( recur->duration() >  0 ) {
03872           txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03873         }
03874         return txt;
03875       }
03876     }
03877     if ( !recur->yearDates().isEmpty() ) {
03878       txt = i18n( "%1 on %2 %3" ).
03879             arg( recurStr ).
03880             arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
03881             arg( dayList[ recur->yearDates()[0] + 31 ] );
03882       return txt;
03883     } else {
03884       if ( !recur->yearMonths().isEmpty() ) {
03885         txt = i18n( "Recurs yearly on %1 %2" ).
03886               arg( calSys->monthName( recur->yearMonths()[0],
03887                                       recur->startDate().year() ) ).
03888               arg( dayList[ recur->startDate().day() + 31 ] );
03889       } else {
03890         txt = i18n( "Recurs yearly on %1 %2" ).
03891               arg( calSys->monthName( recur->startDate().month(),
03892                                       recur->startDate().year() ) ).
03893               arg( dayList[ recur->startDate().day() + 31 ] );
03894       }
03895       return txt;
03896     }
03897   }
03898 
03899   case Recurrence::rYearlyDay:
03900   {
03901     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
03902 
03903     if ( recur->duration() != -1 ) {
03904       txt = i18n( "%1 on day %2 until %3" ).
03905             arg( recurStr ).
03906             arg( recur->yearDays()[0] ).
03907             arg( recurEnd( incidence ) );
03908       if ( recur->duration() >  0 ) {
03909         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03910       }
03911       return txt;
03912     }
03913     txt = i18n( "%1 on day %2" ).arg( recurStr ).arg( recur->yearDays()[0] );
03914     return txt;
03915   }
03916 
03917   case Recurrence::rYearlyPos:
03918   {
03919     recurStr = i18n( "Every year", "Every %n years", recur->frequency() );
03920 
03921     KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
03922     if ( recur->duration() != -1 ) {
03923       txt = i18n( "%1 on the %2 %3 of %4 until %5" ).
03924             arg( recurStr ).
03925             arg( dayList[rule.pos() + 31] ).
03926             arg( calSys->weekDayName( rule.day(), false ) ).
03927             arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
03928             arg( recurEnd( incidence ) );
03929       if ( recur->duration() >  0 ) {
03930         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03931       }
03932       return txt;
03933     }
03934     txt = i18n( "%1 on the %2 %3 of %4" ).
03935           arg( recurStr ).
03936           arg( dayList[rule.pos() + 31] ).
03937           arg( calSys->weekDayName( rule.day(), false ) ).
03938           arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
03939     return txt;
03940   }
03941 
03942   default:
03943     return i18n( "Incidence recurs" );
03944   }
03945 }
03946 
03947 QString IncidenceFormatter::timeToString( const QDateTime &date, bool shortfmt )
03948 {
03949   return KGlobal::locale()->formatTime( date.time(), !shortfmt );
03950 }
03951 
03952 QString IncidenceFormatter::dateToString( const QDateTime &date, bool shortfmt )
03953 {
03954   return
03955     KGlobal::locale()->formatDate( date.date(), shortfmt );
03956 }
03957 
03958 QString IncidenceFormatter::dateTimeToString( const QDateTime &date,
03959                                               bool allDay, bool shortfmt )
03960 {
03961   if ( allDay ) {
03962     return dateToString( date, shortfmt );
03963   }
03964 
03965   return  KGlobal::locale()->formatDateTime( date, shortfmt );
03966 }
03967 
03968 QString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence )
03969 {
03970   if ( !calendar || !incidence ) {
03971     return QString::null;
03972   }
03973 
03974   CalendarResources *calendarResource = dynamic_cast<CalendarResources*>( calendar );
03975   if ( !calendarResource ) {
03976     return QString::null;
03977   }
03978 
03979   ResourceCalendar *resourceCalendar = calendarResource->resource( incidence );
03980   if ( resourceCalendar ) {
03981     if ( !resourceCalendar->subresources().isEmpty() ) {
03982       QString subRes = resourceCalendar->subresourceIdentifier( incidence );
03983       if ( subRes.isEmpty() ) {
03984         return resourceCalendar->resourceName();
03985       } else {
03986         return resourceCalendar->labelForSubresource( subRes );
03987       }
03988     }
03989     return resourceCalendar->resourceName();
03990   }
03991 
03992   return QString::null;
03993 }
03994 
03995 static QString secs2Duration( int secs )
03996 {
03997   QString tmp;
03998   int days = secs / 86400;
03999   if ( days > 0 ) {
04000     tmp += i18n( "1 day", "%n days", days );
04001     tmp += ' ';
04002     secs -= ( days * 86400 );
04003   }
04004   int hours = secs / 3600;
04005   if ( hours > 0 ) {
04006     tmp += i18n( "1 hour", "%n hours", hours );
04007     tmp += ' ';
04008     secs -= ( hours * 3600 );
04009   }
04010   int mins = secs / 60;
04011   if ( mins > 0 ) {
04012     tmp += i18n( "1 minute", "%n minutes",  mins );
04013   }
04014   return tmp;
04015 }
04016 
04017 QString IncidenceFormatter::durationString( Incidence *incidence )
04018 {
04019   QString tmp;
04020   if ( incidence->type() == "Event" ) {
04021     Event *event = static_cast<Event *>( incidence );
04022     if ( event->hasEndDate() ) {
04023       if ( !event->doesFloat() ) {
04024         tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
04025       } else {
04026         tmp = i18n( "1 day", "%n days",
04027                     event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
04028       }
04029     } else {
04030       tmp = i18n( "forever" );
04031     }
04032   } else if ( incidence->type() == "Todo" ) {
04033     Todo *todo = static_cast<Todo *>( incidence );
04034     if ( todo->hasDueDate() ) {
04035       if ( todo->hasStartDate() ) {
04036         if ( !todo->doesFloat() ) {
04037           tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
04038         } else {
04039           tmp = i18n( "1 day", "%n days",
04040                       todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
04041         }
04042       }
04043     }
04044   }
04045   return tmp;
04046 }
04047 
04048 QStringList IncidenceFormatter::reminderStringList( Incidence *incidence, bool shortfmt )
04049 {
04050   //TODO: implement shortfmt=false
04051   Q_UNUSED( shortfmt );
04052 
04053   QStringList reminderStringList;
04054 
04055   if ( incidence ) {
04056     Alarm::List alarms = incidence->alarms();
04057     Alarm::List::ConstIterator it;
04058     for ( it = alarms.begin(); it != alarms.end(); ++it ) {
04059       Alarm *alarm = *it;
04060       int offset = 0;
04061       QString remStr, atStr, offsetStr;
04062       if ( alarm->hasTime() ) {
04063         offset = 0;
04064         if ( alarm->time().isValid() ) {
04065           atStr = KGlobal::locale()->formatDateTime( alarm->time() );
04066         }
04067       } else if ( alarm->hasStartOffset() ) {
04068         offset = alarm->startOffset().asSeconds();
04069         if ( offset < 0 ) {
04070           offset = -offset;
04071           offsetStr = i18n( "N days/hours/minutes before the start datetime",
04072                             "%1 before the start" );
04073         } else if ( offset > 0 ) {
04074           offsetStr = i18n( "N days/hours/minutes after the start datetime",
04075                             "%1 after the start" );
04076         } else { //offset is 0
04077           if ( incidence->dtStart().isValid() ) {
04078             atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() );
04079           }
04080         }
04081       } else if ( alarm->hasEndOffset() ) {
04082         offset = alarm->endOffset().asSeconds();
04083         if ( offset < 0 ) {
04084           offset = -offset;
04085           if ( incidence->type() == "Todo" ) {
04086             offsetStr = i18n( "N days/hours/minutes before the due datetime",
04087                               "%1 before the to-do is due" );
04088           } else {
04089             offsetStr = i18n( "N days/hours/minutes before the end datetime",
04090                               "%1 before the end" );
04091           }
04092         } else if ( offset > 0 ) {
04093           if ( incidence->type() == "Todo" ) {
04094             offsetStr = i18n( "N days/hours/minutes after the due datetime",
04095                               "%1 after the to-do is due" );
04096           } else {
04097             offsetStr = i18n( "N days/hours/minutes after the end datetime",
04098                               "%1 after the end" );
04099           }
04100         } else { //offset is 0
04101           if ( incidence->type() == "Todo" ) {
04102             Todo *t = static_cast<Todo *>( incidence );
04103             if ( t->dtDue().isValid() ) {
04104               atStr = KGlobal::locale()->formatDateTime( t->dtDue() );
04105             }
04106           } else {
04107             Event *e = static_cast<Event *>( incidence );
04108             if ( e->dtEnd().isValid() ) {
04109               atStr = KGlobal::locale()->formatDateTime( e->dtEnd() );
04110             }
04111           }
04112         }
04113       }
04114       if ( offset == 0 ) {
04115         if ( !atStr.isEmpty() ) {
04116           remStr = i18n( "reminder occurs at datetime", "at %1" ).arg( atStr );
04117         }
04118       } else {
04119         remStr = offsetStr.arg( secs2Duration( offset ) );
04120       }
04121 
04122       if ( alarm->repeatCount() > 0 ) {
04123         QString countStr = i18n( "repeats once", "repeats %n times", alarm->repeatCount() );
04124         QString intervalStr = i18n( "interval is N days/hours/minutes", "interval is %1" ).
04125                               arg( secs2Duration( alarm->snoozeTime().asSeconds() ) );
04126         QString repeatStr = i18n( "(repeat string, interval string)", "(%1, %2)" ).
04127                             arg( countStr, intervalStr );
04128         remStr = remStr + ' ' + repeatStr;
04129 
04130       }
04131       reminderStringList << remStr;
04132     }
04133   }
04134 
04135   return reminderStringList;
04136 }
KDE Home | KDE Accessibility Home | Description of Access Keys