libkcal

incidenceformatter.cpp

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