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