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