libkcal

incidenceformatter.cpp

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