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