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, int method )
02068     {
02069       Incidence *inc = dynamic_cast<Incidence*>( incidence );
02070       if ( !inc || !existingIncidence || inc->revision() <= existingIncidence->revision() )
02071         return false;
02072       mExistingIncidence = existingIncidence;
02073       mMethod = method;
02074       return incidence->accept( *this );
02075     }
02076 
02077     QString result() const
02078     {
02079       if ( mChanges.isEmpty() ) {
02080         return QString::null;
02081       }
02082       QString html = "<div align=\"left\"><ul><li>";
02083       html += mChanges.join( "</li><li>" );
02084       html += "</li><ul></div>";
02085       return html;
02086     }
02087 
02088   protected:
02089     bool visit( Event *event )
02090     {
02091       compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
02092       compareIncidences( event, mExistingIncidence, mMethod );
02093       return !mChanges.isEmpty();
02094     }
02095     bool visit( Todo *todo )
02096     {
02097       compareTodos( todo, dynamic_cast<Todo*>( mExistingIncidence ) );
02098       compareIncidences( todo, mExistingIncidence, mMethod );
02099       return !mChanges.isEmpty();
02100     }
02101     bool visit( Journal *journal )
02102     {
02103       compareIncidences( journal, mExistingIncidence, mMethod );
02104       return !mChanges.isEmpty();
02105     }
02106     bool visit( FreeBusy *fb )
02107     {
02108       Q_UNUSED( fb );
02109       return !mChanges.isEmpty();
02110     }
02111 
02112   private:
02113     void compareEvents( Event *newEvent, Event *oldEvent )
02114     {
02115       if ( !oldEvent || !newEvent )
02116         return;
02117       if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->doesFloat() != newEvent->doesFloat() )
02118         mChanges += i18n( "The invitation starting time has been changed from %1 to %2" )
02119                     .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) );
02120       if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->doesFloat() != newEvent->doesFloat() )
02121         mChanges += i18n( "The invitation ending time has been changed from %1 to %2" )
02122                     .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) );
02123     }
02124 
02125     void compareTodos( Todo *newTodo, Todo *oldTodo )
02126     {
02127       if ( !oldTodo || !newTodo ) {
02128         return;
02129       }
02130 
02131       if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) {
02132         mChanges += i18n( "The task has been completed" );
02133       }
02134       if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) {
02135         mChanges += i18n( "The task is no longer completed" );
02136       }
02137       if ( oldTodo->percentComplete() != newTodo->percentComplete() ) {
02138         const QString oldPer = i18n( "%1%" ).arg( oldTodo->percentComplete() );
02139         const QString newPer = i18n( "%1%" ).arg( newTodo->percentComplete() );
02140         mChanges += i18n( "The task completed percentage has changed from %1 to %2" ).
02141                     arg( oldPer, newPer );
02142       }
02143 
02144       if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) {
02145         mChanges += i18n( "A task starting time has been added" );
02146       }
02147       if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) {
02148         mChanges += i18n( "The task starting time has been removed" );
02149       }
02150       if ( oldTodo->hasStartDate() && newTodo->hasStartDate() &&
02151            oldTodo->dtStart() != newTodo->dtStart() ) {
02152         mChanges += i18n( "The task starting time has been changed from %1 to %2" ).
02153                     arg( dateTimeToString( oldTodo->dtStart(), oldTodo->doesFloat(), false ),
02154                          dateTimeToString( newTodo->dtStart(), newTodo->doesFloat(), false ) );
02155       }
02156 
02157       if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) {
02158         mChanges += i18n( "A task due time has been added" );
02159       }
02160       if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) {
02161         mChanges += i18n( "The task due time has been removed" );
02162       }
02163       if ( oldTodo->hasDueDate() && newTodo->hasDueDate() &&
02164            oldTodo->dtDue() != newTodo->dtDue() ) {
02165         mChanges += i18n( "The task due time has been changed from %1 to %2" ).
02166                     arg( dateTimeToString( oldTodo->dtDue(), oldTodo->doesFloat(), false ),
02167                          dateTimeToString( newTodo->dtDue(), newTodo->doesFloat(), false ) );
02168       }
02169     }
02170 
02171     void compareIncidences( Incidence *newInc, Incidence *oldInc, int method )
02172     {
02173       if ( !oldInc || !newInc )
02174         return;
02175       if ( oldInc->summary() != newInc->summary() )
02176         mChanges += i18n( "The summary has been changed to: \"%1\"" ).arg( newInc->summary() );
02177       if ( oldInc->location() != newInc->location() )
02178         mChanges += i18n( "The location has been changed to: \"%1\"" ).arg( newInc->location() );
02179       if ( oldInc->description() != newInc->description() )
02180         mChanges += i18n( "The description has been changed to: \"%1\"" ).arg( newInc->description() );
02181       Attendee::List oldAttendees = oldInc->attendees();
02182       Attendee::List newAttendees = newInc->attendees();
02183       for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
02184             it != newAttendees.constEnd(); ++it ) {
02185         Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
02186         if ( !oldAtt ) {
02187           mChanges += i18n( "Attendee %1 has been added" ).arg( (*it)->fullName() );
02188         } else {
02189           if ( oldAtt->status() != (*it)->status() )
02190             mChanges += i18n( "The status of attendee %1 has been changed to: %2" ).
02191                         arg( (*it)->fullName() ).arg( (*it)->statusStr() );
02192         }
02193       }
02194       if ( method == Scheduler::Request ) {
02195         for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
02196               it != oldAttendees.constEnd(); ++it ) {
02197           if ( (*it)->email() != oldInc->organizer().email() ) {
02198             Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
02199             if ( !newAtt ) {
02200               mChanges += i18n( "Attendee %1 has been removed" ).arg( (*it)->fullName() );
02201             }
02202           }
02203         }
02204       }
02205     }
02206 
02207   private:
02208     Incidence *mExistingIncidence;
02209     int mMethod;
02210     QStringList mChanges;
02211 };
02212 
02213 
02214 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
02215 {
02216   if ( !id.startsWith( "ATTACH:" ) ) {
02217     QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
02218                   arg( generateLinkURL( id ), text );
02219     return res;
02220   } else {
02221     // draw the attachment links in non-bold face
02222     QString res = QString( "<a href=\"%1\">%2</a>" ).
02223                   arg( generateLinkURL( id ), text );
02224     return res;
02225   }
02226 }
02227 
02228 // Check if the given incidence is likely one that we own instead one from
02229 // a shared calendar (Kolab-specific)
02230 static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence )
02231 {
02232   CalendarResources *cal = dynamic_cast<CalendarResources*>( calendar );
02233   if ( !cal || !incidence ) {
02234     return true;
02235   }
02236   ResourceCalendar *res = cal->resource( incidence );
02237   if ( !res ) {
02238     return true;
02239   }
02240   const QString subRes = res->subresourceIdentifier( incidence );
02241   if ( !subRes.contains( "/.INBOX.directory/" ) ) {
02242     return false;
02243   }
02244   return true;
02245 }
02246 
02247 // The spacer for the invitation buttons
02248 static QString spacer = "<td> &nbsp; </td>";
02249 // The open & close table cell tags for the invitation buttons
02250 static QString tdOpen = "<td>";
02251 static QString tdClose = "</td>" + spacer;
02252 
02253 static QString responseButtons( Incidence *inc, bool rsvpReq, bool rsvpRec,
02254                                 InvitationFormatterHelper *helper )
02255 {
02256   QString html;
02257   if ( !helper ) {
02258     return html;
02259   }
02260 
02261   if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) {
02262     // Record only
02263     html += tdOpen;
02264     html += helper->makeLink( "record", i18n( "[Record]" ) );
02265     html += tdClose;
02266 
02267     // Move to trash
02268     html += tdOpen;
02269     html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) );
02270     html += tdClose;
02271 
02272   } else {
02273 
02274     // Accept
02275     html += tdOpen;
02276     html += helper->makeLink( "accept", i18n( "[Accept]" ) );
02277     html += tdClose;
02278 
02279     // Tentative
02280     html += tdOpen;
02281     html += helper->makeLink( "accept_conditionally",
02282                               i18n( "Accept conditionally", "[Accept cond.]" ) );
02283     html += tdClose;
02284 
02285     // Counter proposal
02286     html += tdOpen;
02287     html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
02288     html += tdClose;
02289 
02290     // Decline
02291     html += tdOpen;
02292     html += helper->makeLink( "decline", i18n( "[Decline]" ) );
02293     html += tdClose;
02294   }
02295 
02296   if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
02297     // Delegate
02298     html += tdOpen;
02299     html += helper->makeLink( "delegate", i18n( "[Delegate]" ) );
02300     html += tdClose;
02301 
02302     // Forward
02303     html += tdOpen;
02304     html += helper->makeLink( "forward", i18n( "[Forward]" ) );
02305     html += tdClose;
02306 
02307     // Check calendar
02308     if ( inc && inc->type() == "Event" ) {
02309       html += tdOpen;
02310       html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
02311       html += tdClose;
02312     }
02313   }
02314   return html;
02315 }
02316 
02317 static QString counterButtons( Incidence *incidence,
02318                                InvitationFormatterHelper *helper )
02319 {
02320   QString html;
02321   if ( !helper ) {
02322     return html;
02323   }
02324 
02325   // Accept proposal
02326   html += tdOpen;
02327   html += helper->makeLink( "accept_counter", i18n("[Accept]") );
02328   html += tdClose;
02329 
02330   // Decline proposal
02331   html += tdOpen;
02332   html += helper->makeLink( "decline_counter", i18n("[Decline]") );
02333   html += tdClose;
02334 
02335   // Check calendar
02336   if ( incidence && incidence->type() == "Event" ) {
02337     html += tdOpen;
02338     html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
02339     html += tdClose;
02340   }
02341   return html;
02342 }
02343 
02344 QString IncidenceFormatter::formatICalInvitationHelper( QString invitation,
02345                                                         Calendar *mCalendar,
02346                                                         InvitationFormatterHelper *helper,
02347                                                         bool noHtmlMode,
02348                                                         const QString &sender )
02349 {
02350   if ( invitation.isEmpty() ) {
02351     return QString::null;
02352   }
02353 
02354   ICalFormat format;
02355   // parseScheduleMessage takes the tz from the calendar, no need to set it manually here for the format!
02356   ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
02357 
02358   if( !msg ) {
02359     kdDebug( 5850 ) << "Failed to parse the scheduling message" << endl;
02360     Q_ASSERT( format.exception() );
02361     kdDebug( 5850 ) << format.exception()->message() << endl;
02362     return QString::null;
02363   }
02364 
02365   IncidenceBase *incBase = msg->event();
02366 
02367   // Determine if this incidence is in my calendar (and owned by me)
02368   Incidence *existingIncidence = 0;
02369   if ( incBase && helper->calendar() ) {
02370     existingIncidence = helper->calendar()->incidence( incBase->uid() );
02371     if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
02372       existingIncidence = 0;
02373     }
02374     if ( !existingIncidence ) {
02375       const Incidence::List list = helper->calendar()->incidences();
02376       for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
02377         if ( (*it)->schedulingID() == incBase->uid() &&
02378              incidenceOwnedByMe( helper->calendar(), *it ) ) {
02379           existingIncidence = *it;
02380           break;
02381         }
02382       }
02383     }
02384   }
02385 
02386   // First make the text of the message
02387   QString html;
02388 
02389   QString tableStyle = QString::fromLatin1(
02390     "style=\"border: solid 1px; margin: 0em;\"" );
02391   QString tableHead = QString::fromLatin1(
02392     "<div align=\"center\">"
02393     "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
02394     "<tr><td>").arg(tableStyle);
02395 
02396   html += tableHead;
02397   InvitationHeaderVisitor headerVisitor;
02398   // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
02399   if ( !headerVisitor.act( incBase, existingIncidence, msg, sender ) )
02400     return QString::null;
02401   html += "<b>" + headerVisitor.result() + "</b>";
02402 
02403   InvitationBodyVisitor bodyVisitor( noHtmlMode );
02404   if ( !bodyVisitor.act( incBase, existingIncidence, msg, sender ) )
02405     return QString::null;
02406   html += bodyVisitor.result();
02407 
02408   if ( msg->method() == Scheduler::Request ) {
02409     IncidenceCompareVisitor compareVisitor;
02410     if ( compareVisitor.act( incBase, existingIncidence, msg->method() ) ) {
02411       html += "<p align=\"left\">";
02412       html += i18n( "The following changes have been made by the organizer:" );
02413       html += "</p>";
02414       html += compareVisitor.result();
02415     }
02416   }
02417   if ( msg->method() == Scheduler::Reply ) {
02418     IncidenceCompareVisitor compareVisitor;
02419     if ( compareVisitor.act( incBase, existingIncidence, msg->method() ) ) {
02420       html += "<p align=\"left\">";
02421       if ( !sender.isEmpty() ) {
02422         html += i18n( "The following changes have been made by %1:" ).arg( sender );
02423       } else {
02424         html += i18n( "The following changes have been made by an attendee:" );
02425       }
02426       html += "</p>";
02427       html += compareVisitor.result();
02428     }
02429   }
02430 
02431   Incidence *inc = dynamic_cast<Incidence*>( incBase );
02432 
02433   // determine if I am the organizer for this invitation
02434   bool myInc = iamOrganizer( inc );
02435 
02436   // determine if the invitation response has already been recorded
02437   bool rsvpRec = false;
02438   Attendee *ea = 0;
02439   if ( !myInc ) {
02440     Incidence *rsvpIncidence = existingIncidence;
02441     if ( !rsvpIncidence && inc && inc->revision() > 0 ) {
02442       rsvpIncidence = inc;
02443     }
02444     if ( rsvpIncidence ) {
02445       ea = findMyAttendee( rsvpIncidence );
02446     }
02447     if ( ea &&
02448          ( ea->status() == Attendee::Accepted ||
02449            ea->status() == Attendee::Declined ||
02450            ea->status() == Attendee::Tentative ) ) {
02451       rsvpRec = true;
02452     }
02453   }
02454 
02455   // determine invitation role
02456   QString role;
02457   bool isDelegated = false;
02458   Attendee *a = findMyAttendee( inc );
02459   if ( !a && inc ) {
02460     if ( !inc->attendees().isEmpty() ) {
02461       a = inc->attendees().first();
02462     }
02463   }
02464   if ( a ) {
02465     isDelegated = ( a->status() == Attendee::Delegated );
02466     role = Attendee::roleName( a->role() );
02467   }
02468 
02469   // Print if RSVP needed, not-needed, or response already recorded
02470   bool rsvpReq = rsvpRequested( inc );
02471   if ( !myInc && a ) {
02472     html += "<br/>";
02473     html += "<i><u>";
02474     if ( rsvpRec && inc ) {
02475       if ( inc->revision() == 0 ) {
02476         html += i18n( "Your <b>%1</b> response has already been recorded" ).
02477                 arg( ea->statusStr() );
02478       } else {
02479         html += i18n( "Your status for this invitation is <b>%1</b>" ).
02480                 arg( ea->statusStr() );
02481       }
02482       rsvpReq = false;
02483     } else if ( msg->method() == Scheduler::Cancel ) {
02484       html += i18n( "This invitation was declined" );
02485     } else if ( msg->method() == Scheduler::Add ) {
02486       html += i18n( "This invitation was accepted" );
02487     } else {
02488       if ( !isDelegated ) {
02489         html += rsvpRequestedStr( rsvpReq, role );
02490       } else {
02491         html += i18n( "Awaiting delegation response" );
02492       }
02493     }
02494     html += "</u></i>";
02495   }
02496 
02497   // Print if the organizer gave you a preset status
02498   if ( !myInc ) {
02499     if ( inc && inc->revision() == 0 ) {
02500       QString statStr = myStatusStr( inc );
02501       if ( !statStr.isEmpty() ) {
02502         html += "<br/>";
02503         html += "<i>";
02504         html += statStr;
02505         html += "</i>";
02506       }
02507     }
02508   }
02509 
02510   // Add groupware links
02511 
02512   html += "<br><table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td></tr>";
02513 
02514   switch ( msg->method() ) {
02515     case Scheduler::Publish:
02516     case Scheduler::Request:
02517     case Scheduler::Refresh:
02518     case Scheduler::Add:
02519     {
02520       if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
02521         html += "<tr>";
02522         if ( inc->type() == "Todo" ) {
02523           html += "<td colspan=\"9\">";
02524           html += helper->makeLink( "reply", i18n( "[Record invitation in my task list]" ) );
02525         } else {
02526           html += "<td colspan=\"13\">";
02527           html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) );
02528         }
02529         html += "</td></tr>";
02530       }
02531 
02532       if ( !myInc && a ) {
02533         html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
02534       }
02535       break;
02536     }
02537 
02538     case Scheduler::Cancel:
02539       // Remove invitation
02540       if ( inc ) {
02541         html += "<tr>";
02542         if ( inc->type() == "Todo" ) {
02543           html += "<td colspan=\"9\">";
02544           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my task list]" ) );
02545         } else {
02546           html += "<td colspan=\"13\">";
02547           html += helper->makeLink( "cancel", i18n( "[Remove invitation from my calendar]" ) );
02548         }
02549         html += "</td></tr>";
02550       }
02551       break;
02552 
02553     case Scheduler::Reply:
02554     {
02555       // Record invitation response
02556       Attendee *a = 0;
02557       Attendee *ea = 0;
02558       if ( inc ) {
02559         // First, determine if this reply is really a counter in disguise.
02560         if ( replyMeansCounter( inc ) ) {
02561           html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
02562           break;
02563         }
02564 
02565         // Next, maybe this is a declined reply that was delegated from me?
02566         // find first attendee who is delegated-from me
02567         // look a their PARTSTAT response, if the response is declined,
02568         // then we need to start over which means putting all the action
02569         // buttons and NOT putting on the [Record response..] button
02570         a = findDelegatedFromMyAttendee( inc );
02571         if ( a ) {
02572           if ( a->status() != Attendee::Accepted ||
02573                a->status() != Attendee::Tentative ) {
02574             html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>";
02575             break;
02576           }
02577         }
02578 
02579         // Finally, simply allow a Record of the reply
02580         if ( !inc->attendees().isEmpty() ) {
02581           a = inc->attendees().first();
02582         }
02583         if ( a ) {
02584           ea = findAttendee( existingIncidence, a->email() );
02585         }
02586       }
02587       if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
02588         if ( inc && inc->revision() > 0 ) {
02589           html += "<br><u><i>";
02590           html += i18n( "The response has been recorded [%1]" ).arg( ea->statusStr() );
02591           html += "</i></u>";
02592         }
02593       } else {
02594         if ( inc ) {
02595           html += "<tr><td>";
02596           if ( inc->type() == "Todo" ) {
02597             html += helper->makeLink( "reply", i18n( "[Record response in my task list]" ) );
02598           } else {
02599             html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
02600           }
02601           html += "</td></tr>";
02602         }
02603       }
02604       break;
02605     }
02606 
02607     case Scheduler::Counter:
02608       // Counter proposal
02609       html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
02610       break;
02611 
02612     case Scheduler::Declinecounter:
02613     case Scheduler::NoMethod:
02614       break;
02615   }
02616 
02617   // close the groupware table
02618   html += "</td></tr></table>";
02619 
02620   // Add the attendee list if I am the organizer
02621   if ( myInc && helper->calendar() ) {
02622     html += invitationAttendees( helper->calendar()->incidence( inc->uid() ) );
02623   }
02624 
02625   // close the top-level table
02626   html += "</td></tr></table><br></div>";
02627 
02628   // Add the attachment list
02629   html += invitationAttachments( helper, inc );
02630 
02631   return html;
02632 }
02633 
02634 QString IncidenceFormatter::formatICalInvitation( QString invitation,
02635                                                   Calendar *mCalendar,
02636                                                   InvitationFormatterHelper *helper )
02637 {
02638   return formatICalInvitationHelper( invitation, mCalendar, helper, false, QString() );
02639 }
02640 
02641 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
02642                                                         Calendar *mCalendar,
02643                                                         InvitationFormatterHelper *helper )
02644 {
02645   return formatICalInvitationHelper( invitation, mCalendar, helper, true, QString() );
02646 }
02647 
02648 QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
02649                                                         Calendar *mCalendar,
02650                                                         InvitationFormatterHelper *helper,
02651                                                         const QString &sender )
02652 {
02653   return formatICalInvitationHelper( invitation, mCalendar, helper, true, sender );
02654 }
02655 
02656 
02657 /*******************************************************************
02658  *  Helper functions for the msTNEF -> VPart converter
02659  *******************************************************************/
02660 
02661 
02662 //-----------------------------------------------------------------------------
02663 
02664 static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
02665                            const QString& fallback = QString::null)
02666 {
02667   return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
02668                             fallback );
02669 }
02670 
02671 static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
02672                            const QString& fallback = QString::null )
02673 {
02674   return tnefMsg->findNamedProp( name, fallback );
02675 }
02676 
02677 struct save_tz { char* old_tz; char* tz_env_str; };
02678 
02679 /* temporarily go to a different timezone */
02680 static struct save_tz set_tz( const char* _tc )
02681 {
02682   const char *tc = _tc?_tc:"UTC";
02683 
02684   struct save_tz rv;
02685 
02686   rv.old_tz = 0;
02687   rv.tz_env_str = 0;
02688 
02689   //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;
02690 
02691   char* tz_env = 0;
02692   if( getenv( "TZ" ) ) {
02693     tz_env = strdup( getenv( "TZ" ) );
02694     rv.old_tz = tz_env;
02695   }
02696   char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
02697   strcpy( tmp_env, "TZ=" );
02698   strcpy( tmp_env+3, tc );
02699   putenv( tmp_env );
02700 
02701   rv.tz_env_str = tmp_env;
02702 
02703   /* tmp_env is not free'ed -- it is part of the environment */
02704 
02705   tzset();
02706   //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;
02707 
02708   return rv;
02709 }
02710 
02711 /* restore previous timezone */
02712 static void unset_tz( struct save_tz old_tz )
02713 {
02714   if( old_tz.old_tz ) {
02715     char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
02716     strcpy( tmp_env, "TZ=" );
02717     strcpy( tmp_env+3, old_tz.old_tz );
02718     putenv( tmp_env );
02719     /* tmp_env is not free'ed -- it is part of the environment */
02720     free( old_tz.old_tz );
02721   } else {
02722     /* clear TZ from env */
02723     putenv( strdup("TZ") );
02724   }
02725   tzset();
02726 
02727   /* is this OK? */
02728   if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
02729 }
02730 
02731 static QDateTime utc2Local( const QDateTime& utcdt )
02732 {
02733   struct tm tmL;
02734 
02735   save_tz tmp_tz = set_tz("UTC");
02736   time_t utc = utcdt.toTime_t();
02737   unset_tz( tmp_tz );
02738 
02739   localtime_r( &utc, &tmL );
02740   return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
02741                     QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
02742 }
02743 
02744 
02745 static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
02746                                           bool bDateOnly = false )
02747 {
02748   QDate tmpDate;
02749   QTime tmpTime;
02750   int year, month, day, hour, minute, second;
02751 
02752   if( bDateOnly ) {
02753     year = dtStr.left( 4 ).toInt();
02754     month = dtStr.mid( 4, 2 ).toInt();
02755     day = dtStr.mid( 6, 2 ).toInt();
02756     hour = 0;
02757     minute = 0;
02758     second = 0;
02759   } else {
02760     year = dtStr.left( 4 ).toInt();
02761     month = dtStr.mid( 4, 2 ).toInt();
02762     day = dtStr.mid( 6, 2 ).toInt();
02763     hour = dtStr.mid( 9, 2 ).toInt();
02764     minute = dtStr.mid( 11, 2 ).toInt();
02765     second = dtStr.mid( 13, 2 ).toInt();
02766   }
02767   tmpDate.setYMD( year, month, day );
02768   tmpTime.setHMS( hour, minute, second );
02769 
02770   if( tmpDate.isValid() && tmpTime.isValid() ) {
02771     QDateTime dT = QDateTime( tmpDate, tmpTime );
02772 
02773     if( !bDateOnly ) {
02774       // correct for GMT ( == Zulu time == UTC )
02775       if (dtStr.at(dtStr.length()-1) == 'Z') {
02776         //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
02777         //localUTCOffset( dT ) );
02778         dT = utc2Local( dT );
02779       }
02780     }
02781     return dT;
02782   } else
02783     return QDateTime();
02784 }
02785 
02786 
02787 
02788 QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef )
02789 {
02790   bool bOk = false;
02791 
02792   KTNEFParser parser;
02793   QBuffer buf( tnef );
02794   CalendarLocal cal ( QString::fromLatin1( "UTC" ) );
02795   KABC::Addressee addressee;
02796   KABC::VCardConverter cardConv;
02797   ICalFormat calFormat;
02798   Event* event = new Event();
02799 
02800   if( parser.openDevice( &buf ) ) {
02801     KTNEFMessage* tnefMsg = parser.message();
02802     //QMap<int,KTNEFProperty*> props = parser.message()->properties();
02803 
02804     // Everything depends from property PR_MESSAGE_CLASS
02805     // (this is added by KTNEFParser):
02806     QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
02807       .upper();
02808     if( !msgClass.isEmpty() ) {
02809       // Match the old class names that might be used by Outlook for
02810       // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
02811       bool bCompatClassAppointment = false;
02812       bool bCompatMethodRequest = false;
02813       bool bCompatMethodCancled = false;
02814       bool bCompatMethodAccepted = false;
02815       bool bCompatMethodAcceptedCond = false;
02816       bool bCompatMethodDeclined = false;
02817       if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
02818         bCompatClassAppointment = true;
02819         if( msgClass.endsWith( ".MTGREQ" ) )
02820           bCompatMethodRequest = true;
02821         if( msgClass.endsWith( ".MTGCNCL" ) )
02822           bCompatMethodCancled = true;
02823         if( msgClass.endsWith( ".MTGRESPP" ) )
02824           bCompatMethodAccepted = true;
02825         if( msgClass.endsWith( ".MTGRESPA" ) )
02826           bCompatMethodAcceptedCond = true;
02827         if( msgClass.endsWith( ".MTGRESPN" ) )
02828           bCompatMethodDeclined = true;
02829       }
02830       bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );
02831 
02832       if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
02833         // Compose a vCal
02834         bool bIsReply = false;
02835         QString prodID = "-//Microsoft Corporation//Outlook ";
02836         prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
02837         prodID += "MIMEDIR/EN\n";
02838         prodID += "VERSION:2.0\n";
02839         calFormat.setApplication( "Outlook", prodID );
02840 
02841         Scheduler::Method method;
02842         if( bCompatMethodRequest )
02843           method = Scheduler::Request;
02844         else if( bCompatMethodCancled )
02845           method = Scheduler::Cancel;
02846         else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
02847                  bCompatMethodDeclined ) {
02848           method = Scheduler::Reply;
02849           bIsReply = true;
02850         } else {
02851           // pending(khz): verify whether "0x0c17" is the right tag ???
02852           //
02853           // at the moment we think there are REQUESTS and UPDATES
02854           //
02855           // but WHAT ABOUT REPLIES ???
02856           //
02857           //
02858 
02859           if( tnefMsg->findProp(0x0c17) == "1" )
02860             bIsReply = true;
02861           method = Scheduler::Request;
02862         }
02863 
02865         ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );
02866 
02867         QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );
02868 
02869         if( !sSenderSearchKeyEmail.isEmpty() ) {
02870           int colon = sSenderSearchKeyEmail.find( ':' );
02871           // May be e.g. "SMTP:KHZ@KDE.ORG"
02872           if( sSenderSearchKeyEmail.find( ':' ) == -1 )
02873             sSenderSearchKeyEmail.remove( 0, colon+1 );
02874         }
02875 
02876         QString s( tnefMsg->findProp( 0x0e04 ) );
02877         QStringList attendees = QStringList::split( ';', s );
02878         if( attendees.count() ) {
02879           for( QStringList::Iterator it = attendees.begin();
02880                it != attendees.end(); ++it ) {
02881             // Skip all entries that have no '@' since these are
02882             // no mail addresses
02883             if( (*it).find('@') == -1 ) {
02884               s = (*it).stripWhiteSpace();
02885 
02886               Attendee *attendee = new Attendee( s, s, true );
02887               if( bIsReply ) {
02888                 if( bCompatMethodAccepted )
02889                   attendee->setStatus( Attendee::Accepted );
02890                 if( bCompatMethodDeclined )
02891                   attendee->setStatus( Attendee::Declined );
02892                 if( bCompatMethodAcceptedCond )
02893                   attendee->setStatus(Attendee::Tentative);
02894               } else {
02895                 attendee->setStatus( Attendee::NeedsAction );
02896                 attendee->setRole( Attendee::ReqParticipant );
02897               }
02898               event->addAttendee(attendee);
02899             }
02900           }
02901         } else {
02902           // Oops, no attendees?
02903           // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
02904           s = sSenderSearchKeyEmail;
02905           if( !s.isEmpty() ) {
02906             Attendee *attendee = new Attendee( QString::null, QString::null,
02907                                                true );
02908             if( bIsReply ) {
02909               if( bCompatMethodAccepted )
02910                 attendee->setStatus( Attendee::Accepted );
02911               if( bCompatMethodAcceptedCond )
02912                 attendee->setStatus( Attendee::Declined );
02913               if( bCompatMethodDeclined )
02914                 attendee->setStatus( Attendee::Tentative );
02915             } else {
02916               attendee->setStatus(Attendee::NeedsAction);
02917               attendee->setRole(Attendee::ReqParticipant);
02918             }
02919             event->addAttendee(attendee);
02920           }
02921         }
02922         s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
02923         if( s.isEmpty() && !bIsReply )
02924           s = sSenderSearchKeyEmail;
02925         // TODO: Use the common name?
02926         if( !s.isEmpty() )
02927           event->setOrganizer( s );
02928 
02929         s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
02930           .replace( QChar( ':' ), QString::null );
02931         event->setDtStart( QDateTime::fromString( s ) ); // ## Format??
02932 
02933         s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
02934           .replace( QChar( ':' ), QString::null );
02935         event->setDtEnd( QDateTime::fromString( s ) );
02936 
02937         s = tnefMsg->findProp( 0x8208 );
02938         event->setLocation( s );
02939 
02940         // is it OK to set this to OPAQUE always ??
02941         //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
02942         //vPart += "SEQUENCE:0\n";
02943 
02944         // is "0x0023" OK  -  or should we look for "0x0003" ??
02945         s = tnefMsg->findProp( 0x0023 );
02946         event->setUid( s );
02947 
02948         // PENDING(khz): is this value in local timezone? Must it be
02949         // adjusted? Most likely this is a bug in the server or in
02950         // Outlook - we ignore it for now.
02951         s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
02952           .replace( QChar( ':' ), QString::null );
02953         // ### libkcal always uses currentDateTime()
02954         // event->setDtStamp(QDateTime::fromString(s));
02955 
02956         s = tnefMsg->findNamedProp( "Keywords" );
02957         event->setCategories( s );
02958 
02959         s = tnefMsg->findProp( 0x1000 );
02960         event->setDescription( s );
02961 
02962         s = tnefMsg->findProp( 0x0070 );
02963         event->setSummary( s );
02964 
02965         s = tnefMsg->findProp( 0x0026 );
02966         event->setPriority( s.toInt() );
02967 
02968         // is reminder flag set ?
02969         if(!tnefMsg->findProp(0x8503).isEmpty()) {
02970           Alarm *alarm = new Alarm(event);
02971           QDateTime highNoonTime =
02972             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
02973                                      .replace( QChar( '-' ), "" )
02974                                      .replace( QChar( ':' ), "" ) );
02975           QDateTime wakeMeUpTime =
02976             pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
02977                                      .replace( QChar( '-' ), "" )
02978                                      .replace( QChar( ':' ), "" ) );
02979           alarm->setTime(wakeMeUpTime);
02980 
02981           if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
02982             alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
02983           else
02984             // default: wake them up 15 minutes before the appointment
02985             alarm->setStartOffset( Duration( 15*60 ) );
02986           alarm->setDisplayAlarm( i18n( "Reminder" ) );
02987 
02988           // Sorry: the different action types are not known (yet)
02989           //        so we always set 'DISPLAY' (no sounds, no images...)
02990           event->addAlarm( alarm );
02991         }
02992         cal.addEvent( event );
02993         bOk = true;
02994         // we finished composing a vCal
02995       } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
02996         addressee.setUid( stringProp( tnefMsg, attMSGID ) );
02997         addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) );
02998         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
02999         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
03000         addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
03001         addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) );
03002         addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) );
03003         addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
03004         addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
03005         addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) );
03006         addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) );
03007         addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) );
03008 
03009         QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
03010           .replace( QChar( '-' ), QString::null )
03011           .replace( QChar( ':' ), QString::null );
03012         if( !s.isEmpty() )
03013           addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );
03014 
03015         addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );
03016 
03017         // collect parts of Name entry
03018         addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
03019         addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
03020         addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) );
03021         addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) );
03022         addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );
03023 
03024         addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
03025         addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
03026         addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
03027         /*
03028         the MAPI property ID of this (multiline) )field is unknown:
03029         vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
03030         */
03031 
03032         KABC::Address adr;
03033         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) );
03034         adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
03035         adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
03036         adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
03037         adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) );
03038         adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
03039         adr.setType(KABC::Address::Home);
03040         addressee.insertAddress(adr);
03041 
03042         adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
03043         adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) );
03044         adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) );
03045         adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) );
03046         adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
03047         adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) );
03048         adr.setType( KABC::Address::Work );
03049         addressee.insertAddress( adr );
03050 
03051         adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) );
03052         adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
03053         adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
03054         adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
03055         adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) );
03056         adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
03057         adr.setType( KABC::Address::Dom );
03058         addressee.insertAddress(adr);
03059 
03060         // problem: the 'other' address was stored by KOrganizer in
03061         //          a line looking like the following one:
03062         // 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
03063 
03064         QString nr;
03065         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
03066         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) );
03067         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
03068         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) );
03069         nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
03070         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) );
03071         nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
03072         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) );
03073         nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
03074         addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) );
03075 
03076         s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
03077           .replace( QChar( '-' ), QString::null )
03078           .replace( QChar( ':' ), QString::null );
03079         if( !s.isEmpty() )
03080           addressee.setBirthday( QDateTime::fromString( s ) );
03081 
03082         bOk = ( !addressee.isEmpty() );
03083       } else if( "IPM.NOTE" == msgClass ) {
03084 
03085       } // else if ... and so on ...
03086     }
03087   }
03088 
03089   // Compose return string
03090   QString iCal = calFormat.toString( &cal );
03091   if( !iCal.isEmpty() )
03092     // This was an iCal
03093     return iCal;
03094 
03095   // Not an iCal - try a vCard
03096   KABC::VCardConverter converter;
03097   return converter.createVCard( addressee );
03098 }
03099 
03100 
03101 QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef,
03102         Calendar *mCalendar, InvitationFormatterHelper *helper )
03103 {
03104   QString vPart = IncidenceFormatter::msTNEFToVPart( tnef );
03105   QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper );
03106   if( !iCal.isEmpty() )
03107     return iCal;
03108   return vPart;
03109 }
03110 
03111 
03112 
03113 
03114 /*******************************************************************
03115  *  Helper functions for the Incidence tooltips
03116  *******************************************************************/
03117 
03118 class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
03119 {
03120   public:
03121     ToolTipVisitor()
03122       : mCalendar( 0 ), mRichText( true ), mResult( "" ) {}
03123 
03124     bool act( Calendar *calendar, IncidenceBase *incidence,
03125               const QDate &date=QDate(), bool richText=true )
03126     {
03127       mCalendar = calendar;
03128       mDate = date;
03129       mRichText = richText;
03130       mResult = "";
03131       return incidence ? incidence->accept( *this ) : false;
03132     }
03133     QString result() const { return mResult; }
03134 
03135   protected:
03136     bool visit( Event *event );
03137     bool visit( Todo *todo );
03138     bool visit( Journal *journal );
03139     bool visit( FreeBusy *fb );
03140 
03141     QString dateRangeText( Event *event, const QDate &date );
03142     QString dateRangeText( Todo *todo, const QDate &date );
03143     QString dateRangeText( Journal *journal );
03144     QString dateRangeText( FreeBusy *fb );
03145 
03146     QString generateToolTip( Incidence* incidence, QString dtRangeText );
03147 
03148   protected:
03149     Calendar *mCalendar;
03150     QDate mDate;
03151     bool mRichText;
03152     QString mResult;
03153 };
03154 
03155 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const QDate &date )
03156 {
03157   QString ret;
03158   QString tmp;
03159 
03160   QDateTime startDt = event->dtStart();
03161   QDateTime endDt = event->dtEnd();
03162   if ( event->doesRecur() ) {
03163     if ( date.isValid() ) {
03164       QDateTime dt( date, QTime( 0, 0, 0 ) );
03165       int diffDays = startDt.daysTo( dt );
03166       dt = dt.addSecs( -1 );
03167       startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() );
03168       if ( event->hasEndDate() ) {
03169         endDt = endDt.addDays( diffDays );
03170         if ( startDt > endDt ) {
03171           startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() );
03172           endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
03173         }
03174       }
03175     }
03176   }
03177   if ( event->isMultiDay() ) {
03178 
03179     tmp = "<br>" + i18n("Event start", "<i>From:</i>&nbsp;%1");
03180     if (event->doesFloat())
03181       ret += tmp.arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03182     else
03183       ret += tmp.arg( IncidenceFormatter::dateToString( startDt ).replace(" ", "&nbsp;") );
03184 
03185     tmp = "<br>" + i18n("Event end","<i>To:</i>&nbsp;%1");
03186     if (event->doesFloat())
03187       ret += tmp.arg( IncidenceFormatter::dateToString( endDt, false ).replace(" ", "&nbsp;") );
03188     else
03189       ret += tmp.arg( IncidenceFormatter::dateToString( endDt ).replace(" ", "&nbsp;") );
03190 
03191   } else {
03192 
03193     ret += "<br>"+i18n("<i>Date:</i>&nbsp;%1").
03194            arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", "&nbsp;") );
03195     if ( !event->doesFloat() ) {
03196       const QString dtStartTime =
03197         IncidenceFormatter::timeToString( startDt, true ).replace( " ", "&nbsp;" );
03198       const QString dtEndTime =
03199         IncidenceFormatter::timeToString( endDt, true ).replace( " ", "&nbsp;" );
03200       if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00'
03201         tmp = "<br>" + i18n("time for event, &nbsp; to prevent ugly line breaks",
03202         "<i>Time:</i>&nbsp;%1").
03203         arg( dtStartTime );
03204       } else {
03205         tmp = "<br>" + i18n("time range for event, &nbsp; to prevent ugly line breaks",
03206         "<i>Time:</i>&nbsp;%1&nbsp;-&nbsp;%2").
03207         arg( dtStartTime, dtEndTime );
03208       }
03209       ret += tmp;
03210     }
03211 
03212   }
03213   return ret;
03214 }
03215 
03216 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const QDate &date )
03217 {
03218   QString ret;
03219   bool floats( todo->doesFloat() );
03220 
03221   if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03222     QDateTime startDt = todo->dtStart();
03223     if ( todo->doesRecur() ) {
03224       if ( date.isValid() ) {
03225         startDt.setDate( date );
03226       }
03227     }
03228     ret += "<br>" +
03229            i18n("<i>Start:</i>&nbsp;%1").
03230            arg( IncidenceFormatter::dateTimeToString( startDt, floats, false ).
03231                 replace( " ", "&nbsp;" ) );
03232   }
03233 
03234   if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03235     QDateTime dueDt = todo->dtDue();
03236     if ( todo->doesRecur() ) {
03237       if ( date.isValid() ) {
03238         QDateTime dt( date, QTime( 0, 0, 0 ) );
03239         dt = dt.addSecs( -1 );
03240         dueDt.setDate( todo->recurrence()->getNextDateTime( dt ).date() );
03241       }
03242     }
03243     ret += "<br>" +
03244            i18n("<i>Due:</i>&nbsp;%1").
03245            arg( IncidenceFormatter::dateTimeToString( dueDt, floats, false ).
03246                 replace( " ", "&nbsp;" ) );
03247   }
03248 
03249   // Print priority and completed info here, for lack of a better place
03250 
03251   if ( todo->priority() > 0 ) {
03252     ret += "<br>";
03253     ret += "<i>" + i18n( "Priority:" ) + "</i>" + "&nbsp;";
03254     ret += QString::number( todo->priority() );
03255   }
03256 
03257   ret += "<br>";
03258   if ( todo->isCompleted() ) {
03259     ret += "<i>" + i18n( "Completed:" ) + "</i>" + "&nbsp;";
03260     ret += todo->completedStr().replace( " ", "&nbsp;" );
03261   } else {
03262     ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + "&nbsp;";
03263     ret += i18n( "%1%" ).arg( todo->percentComplete() );
03264   }
03265 
03266   return ret;
03267 }
03268 
03269 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal )
03270 {
03271   QString ret;
03272   if (journal->dtStart().isValid() ) {
03273     ret += "<br>" +
03274            i18n("<i>Date:</i>&nbsp;%1").
03275            arg( IncidenceFormatter::dateToString( journal->dtStart(), false ) );
03276   }
03277   return ret;
03278 }
03279 
03280 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
03281 {
03282   QString tmp( "<br>" + i18n("<i>Period start:</i>&nbsp;%1") );
03283   QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) );
03284   tmp = "<br>" + i18n("<i>Period start:</i>&nbsp;%1");
03285   ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) );
03286   return ret;
03287 }
03288 
03289 
03290 
03291 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
03292 {
03293   mResult = generateToolTip( event, dateRangeText( event, mDate ) );
03294   return !mResult.isEmpty();
03295 }
03296 
03297 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
03298 {
03299   mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
03300   return !mResult.isEmpty();
03301 }
03302 
03303 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
03304 {
03305   mResult = generateToolTip( journal, dateRangeText( journal ) );
03306   return !mResult.isEmpty();
03307 }
03308 
03309 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
03310 {
03311   mResult = "<qt><b>" + i18n("Free/Busy information for %1")
03312         .arg(fb->organizer().fullName()) + "</b>";
03313   mResult += dateRangeText( fb );
03314   mResult += "</qt>";
03315   return !mResult.isEmpty();
03316 }
03317 
03318 static QString tooltipPerson( const QString& email, QString name )
03319 {
03320   // Make the search, if there is an email address to search on,
03321   // and name is missing
03322   if ( name.isEmpty() && !email.isEmpty() ) {
03323     KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
03324     KABC::Addressee::List addressList = add_book->findByEmail( email );
03325     if ( !addressList.isEmpty() ) {
03326       KABC::Addressee o = addressList.first();
03327       if ( !o.isEmpty() && addressList.size() < 2 ) {
03328         // use the name from the addressbook
03329         name = o.formattedName();
03330       }
03331     }
03332   }
03333 
03334   // Show the attendee
03335   QString tmpString = ( name.isEmpty() ? email : name );
03336 
03337   return tmpString;
03338 }
03339 
03340 static QString etc = i18n( "elipsis", "..." );
03341 static QString tooltipFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role )
03342 {
03343   int maxNumAtts = 8; // maximum number of people to print per attendee role
03344   QString sep = i18n( "separator for lists of people names", ", " );
03345   int sepLen = sep.length();
03346 
03347   int i = 0;
03348   QString tmpStr;
03349   Attendee::List::ConstIterator it;
03350   Attendee::List attendees = incidence->attendees();
03351 
03352   for( it = attendees.begin(); it != attendees.end(); ++it ) {
03353     Attendee *a = *it;
03354     if ( a->role() != role ) {
03355       // skip not this role
03356       continue;
03357     }
03358     if ( a->email() == incidence->organizer().email() ) {
03359       // skip attendee that is also the organizer
03360       continue;
03361     }
03362     if ( i == maxNumAtts ) {
03363       tmpStr += etc;
03364       break;
03365     }
03366     tmpStr += tooltipPerson( a->email(), a->name() );
03367     if ( !a->delegator().isEmpty() ) {
03368       tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() );
03369     }
03370     if ( !a->delegate().isEmpty() ) {
03371       tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() );
03372     }
03373     tmpStr += sep;
03374     i++;
03375   }
03376   if ( tmpStr.endsWith( sep ) ) {
03377     tmpStr.truncate( tmpStr.length() - sepLen );
03378   }
03379   return tmpStr;
03380 }
03381 
03382 static QString tooltipFormatAttendees( Incidence *incidence )
03383 {
03384   QString tmpStr, str;
03385 
03386   // Add organizer link
03387   int attendeeCount = incidence->attendees().count();
03388   if ( attendeeCount > 1 ||
03389        ( attendeeCount == 1 &&
03390          incidence->organizer().email() != incidence->attendees().first()->email() ) ) {
03391     tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "&nbsp;";
03392     tmpStr += tooltipPerson( incidence->organizer().email(),
03393                              incidence->organizer().name() );
03394   }
03395 
03396   // Add "chair"
03397   str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair );
03398   if ( !str.isEmpty() ) {
03399     tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "&nbsp;";
03400     tmpStr += str;
03401   }
03402 
03403   // Add required participants
03404   str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant );
03405   if ( !str.isEmpty() ) {
03406     tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "&nbsp;";
03407     tmpStr += str;
03408   }
03409 
03410   // Add optional participants
03411   str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant );
03412   if ( !str.isEmpty() ) {
03413     tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "&nbsp;";
03414     tmpStr += str;
03415   }
03416 
03417   // Add observers
03418   str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant );
03419   if ( !str.isEmpty() ) {
03420     tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "&nbsp;";
03421     tmpStr += str;
03422   }
03423 
03424   return tmpStr;
03425 }
03426 
03427 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence* incidence, QString dtRangeText )
03428 {
03429   uint maxDescLen = 120; // maximum description chars to print (before elipsis)
03430 
03431   if ( !incidence ) {
03432     return QString::null;
03433   }
03434 
03435   QString tmp = "<qt>";
03436 
03437   // header
03438   tmp += "<b>" + incidence->summary().replace( "\n", "<br>" ) + "</b>";
03439   //NOTE: using <hr> seems to confuse Qt3 tooltips in some cases so use "-----"
03440   tmp += "<br>----------<br>";
03441 
03442   if ( mCalendar ) {
03443     QString calStr = IncidenceFormatter::resourceString( mCalendar, incidence );
03444     if ( !calStr.isEmpty() ) {
03445       tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + "&nbsp;";
03446       tmp += calStr;
03447     }
03448   }
03449 
03450   tmp += dtRangeText;
03451 
03452   if ( !incidence->location().isEmpty() ) {
03453     tmp += "<br>";
03454     tmp += "<i>" + i18n( "Location:" ) + "</i>" + "&nbsp;";
03455     tmp += incidence->location().replace( "\n", "<br>" );
03456   }
03457 
03458   QString durStr = IncidenceFormatter::durationString( incidence );
03459   if ( !durStr.isEmpty() ) {
03460     tmp += "<br>";
03461     tmp += "<i>" + i18n( "Duration:" ) + "</i>" + "&nbsp;";
03462     tmp += durStr;
03463   }
03464 
03465   if ( incidence->doesRecur() ) {
03466     tmp += "<br>";
03467     tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + "&nbsp;";
03468     tmp += IncidenceFormatter::recurrenceString( incidence );
03469   }
03470 
03471   if ( !incidence->description().isEmpty() ) {
03472     QString desc( incidence->description() );
03473     if ( desc.length() > maxDescLen ) {
03474       desc = desc.left( maxDescLen ) + etc;
03475     }
03476     tmp += "<br>----------<br>";
03477     tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
03478     tmp += desc.replace( "\n", "<br>" );
03479     tmp += "<br>----------";
03480   }
03481 
03482   int reminderCount = incidence->alarms().count();
03483   if ( reminderCount > 0 && incidence->isAlarmEnabled() ) {
03484     tmp += "<br>";
03485     tmp += "<i>" + i18n( "Reminder:", "%n Reminders:", reminderCount ) + "</i>" + "&nbsp;";
03486     tmp += IncidenceFormatter::reminderStringList( incidence ).join( ", " );
03487   }
03488 
03489   tmp += "<br>";
03490   tmp += tooltipFormatAttendees( incidence );
03491 
03492   int categoryCount = incidence->categories().count();
03493   if ( categoryCount > 0 ) {
03494     tmp += "<br>";
03495     tmp += "<i>" + i18n( "Category:", "%n Categories:", categoryCount ) + "</i>" + "&nbsp;";
03496     tmp += incidence->categories().join( ", " );
03497   }
03498 
03499   tmp += "</qt>";
03500   return tmp;
03501 }
03502 
03503 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
03504 {
03505   return toolTipStr( 0, incidence, QDate(), richText );
03506 }
03507 
03508 QString IncidenceFormatter::toolTipStr( Calendar *calendar,
03509                                         IncidenceBase *incidence,
03510                                         const QDate &date,
03511                                         bool richText )
03512 {
03513   ToolTipVisitor v;
03514   if ( v.act( calendar, incidence, date, richText ) ) {
03515     return v.result();
03516   } else {
03517     return QString::null;
03518   }
03519 }
03520 
03521 /*******************************************************************
03522  *  Helper functions for the Incidence tooltips
03523  *******************************************************************/
03524 
03525 class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
03526 {
03527   public:
03528     MailBodyVisitor() : mResult( "" ) {}
03529 
03530     bool act( IncidenceBase *incidence )
03531     {
03532       mResult = "";
03533       return incidence ? incidence->accept( *this ) : false;
03534     }
03535     QString result() const { return mResult; }
03536 
03537   protected:
03538     bool visit( Event *event );
03539     bool visit( Todo *todo );
03540     bool visit( Journal *journal );
03541     bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); }
03542   protected:
03543     QString mResult;
03544 };
03545 
03546 
03547 static QString mailBodyIncidence( Incidence *incidence )
03548 {
03549   QString body;
03550   if ( !incidence->summary().isEmpty() ) {
03551     body += i18n("Summary: %1\n").arg( incidence->summary() );
03552   }
03553   if ( !incidence->organizer().isEmpty() ) {
03554     body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() );
03555   }
03556   if ( !incidence->location().isEmpty() ) {
03557     body += i18n("Location: %1\n").arg( incidence->location() );
03558   }
03559   return body;
03560 }
03561 
03562 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
03563 {
03564   QString recurrence[]= {i18n("no recurrence", "None"),
03565     i18n("Minutely"), i18n("Hourly"), i18n("Daily"),
03566     i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"),
03567     i18n("Yearly"), i18n("Yearly"), i18n("Yearly")};
03568 
03569   mResult = mailBodyIncidence( event );
03570   mResult += i18n("Start Date: %1\n").
03571              arg( IncidenceFormatter::dateToString( event->dtStart(), true ) );
03572   if ( !event->doesFloat() ) {
03573     mResult += i18n("Start Time: %1\n").
03574                arg( IncidenceFormatter::timeToString( event->dtStart(), true ) );
03575   }
03576   if ( event->dtStart() != event->dtEnd() ) {
03577     mResult += i18n("End Date: %1\n").
03578                arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) );
03579   }
03580   if ( !event->doesFloat() ) {
03581     mResult += i18n("End Time: %1\n").
03582                arg( IncidenceFormatter::timeToString( event->dtEnd(), true ) );
03583   }
03584   if ( event->doesRecur() ) {
03585     Recurrence *recur = event->recurrence();
03586     // TODO: Merge these two to one of the form "Recurs every 3 days"
03587     mResult += i18n("Recurs: %1\n")
03588              .arg( recurrence[ recur->recurrenceType() ] );
03589     mResult += i18n("Frequency: %1\n")
03590              .arg( event->recurrence()->frequency() );
03591 
03592     if ( recur->duration() > 0 ) {
03593       mResult += i18n ("Repeats once", "Repeats %n times", recur->duration());
03594       mResult += '\n';
03595     } else {
03596       if ( recur->duration() != -1 ) {
03597 // TODO_Recurrence: What to do with floating
03598         QString endstr;
03599         if ( event->doesFloat() ) {
03600           endstr = KGlobal::locale()->formatDate( recur->endDate() );
03601         } else {
03602           endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() );
03603         }
03604         mResult += i18n("Repeat until: %1\n").arg( endstr );
03605       } else {
03606         mResult += i18n("Repeats forever\n");
03607       }
03608     }
03609   }
03610   QString details = event->description();
03611   if ( !details.isEmpty() ) {
03612     mResult += i18n("Details:\n%1\n").arg( details );
03613   }
03614   return !mResult.isEmpty();
03615 }
03616 
03617 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
03618 {
03619   mResult = mailBodyIncidence( todo );
03620 
03621   if ( todo->hasStartDate() ) {
03622     mResult += i18n("Start Date: %1\n").
03623                arg( IncidenceFormatter::dateToString( todo->dtStart( false ), true ) );
03624     if ( !todo->doesFloat() ) {
03625       mResult += i18n("Start Time: %1\n").
03626                  arg( IncidenceFormatter::timeToString( todo->dtStart( false ),true ) );
03627     }
03628   }
03629   if ( todo->hasDueDate() ) {
03630     mResult += i18n("Due Date: %1\n").
03631                arg( IncidenceFormatter::dateToString( todo->dtDue(), true ) );
03632     if ( !todo->doesFloat() ) {
03633       mResult += i18n("Due Time: %1\n").
03634                  arg( IncidenceFormatter::timeToString( todo->dtDue(), true ) );
03635     }
03636   }
03637   QString details = todo->description();
03638   if ( !details.isEmpty() ) {
03639     mResult += i18n("Details:\n%1\n").arg( details );
03640   }
03641   return !mResult.isEmpty();
03642 }
03643 
03644 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
03645 {
03646   mResult = mailBodyIncidence( journal );
03647   mResult += i18n("Date: %1\n").
03648              arg( IncidenceFormatter::dateToString( journal->dtStart(), true ) );
03649   if ( !journal->doesFloat() ) {
03650     mResult += i18n("Time: %1\n").
03651                arg( IncidenceFormatter::timeToString( journal->dtStart(), true ) );
03652   }
03653   if ( !journal->description().isEmpty() )
03654     mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() );
03655   return !mResult.isEmpty();
03656 }
03657 
03658 
03659 
03660 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
03661 {
03662   if ( !incidence )
03663     return QString::null;
03664 
03665   MailBodyVisitor v;
03666   if ( v.act( incidence ) ) {
03667     return v.result();
03668   }
03669   return QString::null;
03670 }
03671 
03672 static QString recurEnd( Incidence *incidence )
03673 {
03674   QString endstr;
03675   if ( incidence->doesFloat() ) {
03676     endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
03677   } else {
03678     endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
03679   }
03680   return endstr;
03681 }
03682 
03683 /************************************
03684  *  More static formatting functions
03685  ************************************/
03686 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
03687 {
03688   if ( !incidence->doesRecur() ) {
03689     return i18n( "No recurrence" );
03690   }
03691   QStringList dayList;
03692   dayList.append( i18n( "31st Last" ) );
03693   dayList.append( i18n( "30th Last" ) );
03694   dayList.append( i18n( "29th Last" ) );
03695   dayList.append( i18n( "28th Last" ) );
03696   dayList.append( i18n( "27th Last" ) );
03697   dayList.append( i18n( "26th Last" ) );
03698   dayList.append( i18n( "25th Last" ) );
03699   dayList.append( i18n( "24th Last" ) );
03700   dayList.append( i18n( "23rd Last" ) );
03701   dayList.append( i18n( "22nd Last" ) );
03702   dayList.append( i18n( "21st Last" ) );
03703   dayList.append( i18n( "20th Last" ) );
03704   dayList.append( i18n( "19th Last" ) );
03705   dayList.append( i18n( "18th Last" ) );
03706   dayList.append( i18n( "17th Last" ) );
03707   dayList.append( i18n( "16th Last" ) );
03708   dayList.append( i18n( "15th Last" ) );
03709   dayList.append( i18n( "14th Last" ) );
03710   dayList.append( i18n( "13th Last" ) );
03711   dayList.append( i18n( "12th Last" ) );
03712   dayList.append( i18n( "11th Last" ) );
03713   dayList.append( i18n( "10th Last" ) );
03714   dayList.append( i18n( "9th Last" ) );
03715   dayList.append( i18n( "8th Last" ) );
03716   dayList.append( i18n( "7th Last" ) );
03717   dayList.append( i18n( "6th Last" ) );
03718   dayList.append( i18n( "5th Last" ) );
03719   dayList.append( i18n( "4th Last" ) );
03720   dayList.append( i18n( "3rd Last" ) );
03721   dayList.append( i18n( "2nd Last" ) );
03722   dayList.append( i18n( "last day of the month", "Last" ) );
03723   dayList.append( i18n( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
03724   dayList.append( i18n( "1st" ) );
03725   dayList.append( i18n( "2nd" ) );
03726   dayList.append( i18n( "3rd" ) );
03727   dayList.append( i18n( "4th" ) );
03728   dayList.append( i18n( "5th" ) );
03729   dayList.append( i18n( "6th" ) );
03730   dayList.append( i18n( "7th" ) );
03731   dayList.append( i18n( "8th" ) );
03732   dayList.append( i18n( "9th" ) );
03733   dayList.append( i18n( "10th" ) );
03734   dayList.append( i18n( "11th" ) );
03735   dayList.append( i18n( "12th" ) );
03736   dayList.append( i18n( "13th" ) );
03737   dayList.append( i18n( "14th" ) );
03738   dayList.append( i18n( "15th" ) );
03739   dayList.append( i18n( "16th" ) );
03740   dayList.append( i18n( "17th" ) );
03741   dayList.append( i18n( "18th" ) );
03742   dayList.append( i18n( "19th" ) );
03743   dayList.append( i18n( "20th" ) );
03744   dayList.append( i18n( "21st" ) );
03745   dayList.append( i18n( "22nd" ) );
03746   dayList.append( i18n( "23rd" ) );
03747   dayList.append( i18n( "24th" ) );
03748   dayList.append( i18n( "25th" ) );
03749   dayList.append( i18n( "26th" ) );
03750   dayList.append( i18n( "27th" ) );
03751   dayList.append( i18n( "28th" ) );
03752   dayList.append( i18n( "29th" ) );
03753   dayList.append( i18n( "30th" ) );
03754   dayList.append( i18n( "31st" ) );
03755   int weekStart = KGlobal::locale()->weekStartDay();
03756   QString dayNames;
03757   QString recurStr, txt;
03758   const KCalendarSystem *calSys = KGlobal::locale()->calendar();
03759   Recurrence *recur = incidence->recurrence();
03760   switch ( recur->recurrenceType() ) {
03761   case Recurrence::rNone:
03762     return i18n( "No recurrence" );
03763 
03764   case Recurrence::rMinutely:
03765     recurStr = i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() );
03766     if ( recur->duration() != -1 ) {
03767       txt = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
03768       if ( recur->duration() >  0 ) {
03769         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03770       }
03771       return txt;
03772     }
03773     return recurStr;
03774 
03775   case Recurrence::rHourly:
03776     recurStr = i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() );
03777     if ( recur->duration() != -1 ) {
03778       txt = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
03779       if ( recur->duration() >  0 ) {
03780         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03781       }
03782       return txt;
03783     }
03784     return recurStr;
03785 
03786   case Recurrence::rDaily:
03787     recurStr = i18n( "Recurs daily", "Recurs every %n days", recur->frequency() );
03788     if ( recur->duration() != -1 ) {
03789 
03790       txt = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) );
03791       if ( recur->duration() >  0 ) {
03792         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03793       }
03794       return txt;
03795     }
03796     return recurStr;
03797 
03798   case Recurrence::rWeekly:
03799   {
03800     recurStr = i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() );
03801 
03802     bool addSpace = false;
03803     for ( int i = 0; i < 7; ++i ) {
03804       if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
03805         if ( addSpace ) {
03806           dayNames.append( i18n( "separator for list of days", ", " ) );
03807         }
03808         dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, true ) );
03809         addSpace = true;
03810       }
03811     }
03812     if ( dayNames.isEmpty() ) {
03813       dayNames = i18n( "Recurs weekly on no days", "no days" );
03814     }
03815     if ( recur->duration() != -1 ) {
03816       txt = i18n( "%1 on %2 until %3" ).
03817             arg( recurStr ).arg( dayNames ).arg( recurEnd( incidence ) );
03818       if ( recur->duration() >  0 ) {
03819         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03820       }
03821       return txt;
03822     }
03823     txt = i18n( "%1 on %2" ).arg( recurStr ).arg( dayNames );
03824     return txt;
03825   }
03826   case Recurrence::rMonthlyPos:
03827   {
03828     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
03829 
03830     if ( !recur->monthPositions().isEmpty() ) {
03831       KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
03832       if ( recur->duration() != -1 ) {
03833         txt = i18n( "%1 on the %2 %3 until %4" ).
03834               arg( recurStr ).
03835               arg( dayList[rule.pos() + 31] ).
03836               arg( calSys->weekDayName( rule.day(), false ) ).
03837               arg( recurEnd( incidence ) );
03838         if ( recur->duration() >  0 ) {
03839           txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03840         }
03841         return txt;
03842       }
03843       txt = i18n( "%1 on the %2 %3" ).
03844             arg( recurStr ).
03845             arg( dayList[rule.pos() + 31] ).
03846             arg( calSys->weekDayName( rule.day(), false ) );
03847       return txt;
03848     } else {
03849       return recurStr;
03850     }
03851     break;
03852   }
03853   case Recurrence::rMonthlyDay:
03854   {
03855     recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() );
03856 
03857     if ( !recur->monthDays().isEmpty() ) {
03858       int days = recur->monthDays()[0];
03859       if ( recur->duration() != -1 ) {
03860         txt = i18n( "%1 on the %2 day until %3" ).
03861               arg( recurStr ).
03862               arg( dayList[days + 31] ).
03863               arg( recurEnd( incidence ) );
03864         if ( recur->duration() >  0 ) {
03865           txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03866         }
03867         return txt;
03868       }
03869       txt = i18n( "%1 on the %2 day" ).arg( recurStr ).arg( dayList[days + 31] );
03870       return txt;
03871     } else {
03872       return recurStr;
03873     }
03874     break;
03875   }
03876   case Recurrence::rYearlyMonth:
03877   {
03878     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
03879 
03880     if ( recur->duration() != -1 ) {
03881       if ( !recur->yearDates().isEmpty() ) {
03882         txt = i18n( "%1 on %2 %3 until %4" ).
03883               arg( recurStr ).
03884               arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
03885               arg( dayList[ recur->yearDates()[0] + 31 ] ).
03886               arg( recurEnd( incidence ) );
03887         if ( recur->duration() >  0 ) {
03888           txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03889         }
03890         return txt;
03891       }
03892     }
03893     if ( !recur->yearDates().isEmpty() ) {
03894       txt = i18n( "%1 on %2 %3" ).
03895             arg( recurStr ).
03896             arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
03897             arg( dayList[ recur->yearDates()[0] + 31 ] );
03898       return txt;
03899     } else {
03900       if ( !recur->yearMonths().isEmpty() ) {
03901         txt = i18n( "Recurs yearly on %1 %2" ).
03902               arg( calSys->monthName( recur->yearMonths()[0],
03903                                       recur->startDate().year() ) ).
03904               arg( dayList[ recur->startDate().day() + 31 ] );
03905       } else {
03906         txt = i18n( "Recurs yearly on %1 %2" ).
03907               arg( calSys->monthName( recur->startDate().month(),
03908                                       recur->startDate().year() ) ).
03909               arg( dayList[ recur->startDate().day() + 31 ] );
03910       }
03911       return txt;
03912     }
03913     break;
03914   }
03915   case Recurrence::rYearlyDay:
03916   {
03917     recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() );
03918     if ( !recur->yearDays().isEmpty() ) {
03919       if ( recur->duration() != -1 ) {
03920         txt = i18n( "%1 on day %2 until %3" ).
03921               arg( recurStr ).
03922               arg( recur->yearDays()[0] ).
03923               arg( recurEnd( incidence ) );
03924         if ( recur->duration() >  0 ) {
03925           txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03926         }
03927         return txt;
03928       }
03929       txt = i18n( "%1 on day %2" ).arg( recurStr ).arg( recur->yearDays()[0] );
03930       return txt;
03931     } else {
03932       return recurStr;
03933     }
03934     break;
03935   }
03936   case Recurrence::rYearlyPos:
03937   {
03938     recurStr = i18n( "Every year", "Every %n years", recur->frequency() );
03939     if ( !recur->yearPositions().isEmpty() && !recur->yearMonths().isEmpty() ) {
03940       KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
03941       if ( recur->duration() != -1 ) {
03942         txt = i18n( "%1 on the %2 %3 of %4 until %5" ).
03943               arg( recurStr ).
03944               arg( dayList[rule.pos() + 31] ).
03945               arg( calSys->weekDayName( rule.day(), false ) ).
03946               arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ).
03947               arg( recurEnd( incidence ) );
03948       if ( recur->duration() >  0 ) {
03949         txt += i18n( " (%1 occurrences)" ).arg( recur->duration() );
03950       }
03951       return txt;
03952       }
03953       txt = i18n( "%1 on the %2 %3 of %4" ).
03954             arg( recurStr ).
03955             arg( dayList[rule.pos() + 31] ).
03956             arg( calSys->weekDayName( rule.day(), false ) ).
03957             arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
03958       return txt;
03959     } else {
03960       return recurStr;
03961     }
03962    break;
03963   }
03964   }
03965 
03966   return i18n( "Incidence recurs" );
03967 }
03968 
03969 QString IncidenceFormatter::timeToString( const QDateTime &date, bool shortfmt )
03970 {
03971   return KGlobal::locale()->formatTime( date.time(), !shortfmt );
03972 }
03973 
03974 QString IncidenceFormatter::dateToString( const QDateTime &date, bool shortfmt )
03975 {
03976   return
03977     KGlobal::locale()->formatDate( date.date(), shortfmt );
03978 }
03979 
03980 QString IncidenceFormatter::dateTimeToString( const QDateTime &date,
03981                                               bool allDay, bool shortfmt )
03982 {
03983   if ( allDay ) {
03984     return dateToString( date, shortfmt );
03985   }
03986 
03987   return  KGlobal::locale()->formatDateTime( date, shortfmt );
03988 }
03989 
03990 QString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence )
03991 {
03992   if ( !calendar || !incidence ) {
03993     return QString::null;
03994   }
03995 
03996   CalendarResources *calendarResource = dynamic_cast<CalendarResources*>( calendar );
03997   if ( !calendarResource ) {
03998     return QString::null;
03999   }
04000 
04001   ResourceCalendar *resourceCalendar = calendarResource->resource( incidence );
04002   if ( resourceCalendar ) {
04003     if ( !resourceCalendar->subresources().isEmpty() ) {
04004       QString subRes = resourceCalendar->subresourceIdentifier( incidence );
04005       if ( subRes.isEmpty() ) {
04006         return resourceCalendar->resourceName();
04007       } else {
04008         return resourceCalendar->labelForSubresource( subRes );
04009       }
04010     }
04011     return resourceCalendar->resourceName();
04012   }
04013 
04014   return QString::null;
04015 }
04016 
04017 static QString secs2Duration( int secs )
04018 {
04019   QString tmp;
04020   int days = secs / 86400;
04021   if ( days > 0 ) {
04022     tmp += i18n( "1 day", "%n days", days );
04023     tmp += ' ';
04024     secs -= ( days * 86400 );
04025   }
04026   int hours = secs / 3600;
04027   if ( hours > 0 ) {
04028     tmp += i18n( "1 hour", "%n hours", hours );
04029     tmp += ' ';
04030     secs -= ( hours * 3600 );
04031   }
04032   int mins = secs / 60;
04033   if ( mins > 0 ) {
04034     tmp += i18n( "1 minute", "%n minutes",  mins );
04035   }
04036   return tmp;
04037 }
04038 
04039 QString IncidenceFormatter::durationString( Incidence *incidence )
04040 {
04041   QString tmp;
04042   if ( incidence->type() == "Event" ) {
04043     Event *event = static_cast<Event *>( incidence );
04044     if ( event->hasEndDate() ) {
04045       if ( !event->doesFloat() ) {
04046         tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
04047       } else {
04048         tmp = i18n( "1 day", "%n days",
04049                     event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
04050       }
04051     } else {
04052       tmp = i18n( "forever" );
04053     }
04054   } else if ( incidence->type() == "Todo" ) {
04055     Todo *todo = static_cast<Todo *>( incidence );
04056     if ( todo->hasDueDate() ) {
04057       if ( todo->hasStartDate() ) {
04058         if ( !todo->doesFloat() ) {
04059           tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
04060         } else {
04061           tmp = i18n( "1 day", "%n days",
04062                       todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
04063         }
04064       }
04065     }
04066   }
04067   return tmp;
04068 }
04069 
04070 QStringList IncidenceFormatter::reminderStringList( Incidence *incidence, bool shortfmt )
04071 {
04072   //TODO: implement shortfmt=false
04073   Q_UNUSED( shortfmt );
04074 
04075   QStringList reminderStringList;
04076 
04077   if ( incidence ) {
04078     Alarm::List alarms = incidence->alarms();
04079     Alarm::List::ConstIterator it;
04080     for ( it = alarms.begin(); it != alarms.end(); ++it ) {
04081       Alarm *alarm = *it;
04082       int offset = 0;
04083       QString remStr, atStr, offsetStr;
04084       if ( alarm->hasTime() ) {
04085         offset = 0;
04086         if ( alarm->time().isValid() ) {
04087           atStr = KGlobal::locale()->formatDateTime( alarm->time() );
04088         }
04089       } else if ( alarm->hasStartOffset() ) {
04090         offset = alarm->startOffset().asSeconds();
04091         if ( offset < 0 ) {
04092           offset = -offset;
04093           offsetStr = i18n( "N days/hours/minutes before the start datetime",
04094                             "%1 before the start" );
04095         } else if ( offset > 0 ) {
04096           offsetStr = i18n( "N days/hours/minutes after the start datetime",
04097                             "%1 after the start" );
04098         } else { //offset is 0
04099           if ( incidence->dtStart().isValid() ) {
04100             atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() );
04101           }
04102         }
04103       } else if ( alarm->hasEndOffset() ) {
04104         offset = alarm->endOffset().asSeconds();
04105         if ( offset < 0 ) {
04106           offset = -offset;
04107           if ( incidence->type() == "Todo" ) {
04108             offsetStr = i18n( "N days/hours/minutes before the due datetime",
04109                               "%1 before the to-do is due" );
04110           } else {
04111             offsetStr = i18n( "N days/hours/minutes before the end datetime",
04112                               "%1 before the end" );
04113           }
04114         } else if ( offset > 0 ) {
04115           if ( incidence->type() == "Todo" ) {
04116             offsetStr = i18n( "N days/hours/minutes after the due datetime",
04117                               "%1 after the to-do is due" );
04118           } else {
04119             offsetStr = i18n( "N days/hours/minutes after the end datetime",
04120                               "%1 after the end" );
04121           }
04122         } else { //offset is 0
04123           if ( incidence->type() == "Todo" ) {
04124             Todo *t = static_cast<Todo *>( incidence );
04125             if ( t->dtDue().isValid() ) {
04126               atStr = KGlobal::locale()->formatDateTime( t->dtDue() );
04127             }
04128           } else {
04129             Event *e = static_cast<Event *>( incidence );
04130             if ( e->dtEnd().isValid() ) {
04131               atStr = KGlobal::locale()->formatDateTime( e->dtEnd() );
04132             }
04133           }
04134         }
04135       }
04136       if ( offset == 0 ) {
04137         if ( !atStr.isEmpty() ) {
04138           remStr = i18n( "reminder occurs at datetime", "at %1" ).arg( atStr );
04139         }
04140       } else {
04141         remStr = offsetStr.arg( secs2Duration( offset ) );
04142       }
04143 
04144       if ( alarm->repeatCount() > 0 ) {
04145         QString countStr = i18n( "repeats once", "repeats %n times", alarm->repeatCount() );
04146         QString intervalStr = i18n( "interval is N days/hours/minutes", "interval is %1" ).
04147                               arg( secs2Duration( alarm->snoozeTime().asSeconds() ) );
04148         QString repeatStr = i18n( "(repeat string, interval string)", "(%1, %2)" ).
04149                             arg( countStr, intervalStr );
04150         remStr = remStr + ' ' + repeatStr;
04151 
04152       }
04153       reminderStringList << remStr;
04154     }
04155   }
04156 
04157   return reminderStringList;
04158 }
KDE Home | KDE Accessibility Home | Description of Access Keys