diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 460c52653ab0dcca6f19a4f492ed2c5e4e963ab0 (patch) | |
tree | 67208f7c145782a7e90b123b982ca78d88cc2c87 /libkcal/incidenceformatter.cpp | |
download | tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.tar.gz tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'libkcal/incidenceformatter.cpp')
-rw-r--r-- | libkcal/incidenceformatter.cpp | 2215 |
1 files changed, 2215 insertions, 0 deletions
diff --git a/libkcal/incidenceformatter.cpp b/libkcal/incidenceformatter.cpp new file mode 100644 index 000000000..766906fd8 --- /dev/null +++ b/libkcal/incidenceformatter.cpp @@ -0,0 +1,2215 @@ +/* + This file is part of libkcal. + + Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "incidenceformatter.h" + +#include <libkcal/attachment.h> +#include <libkcal/event.h> +#include <libkcal/todo.h> +#include <libkcal/journal.h> +#include <libkcal/calendar.h> +#include <libkcal/calendarlocal.h> +#include <libkcal/icalformat.h> +#include <libkcal/freebusy.h> +#include <libkcal/calendarresources.h> + +#include <libemailfunctions/email.h> + +#include <ktnef/ktnefparser.h> +#include <ktnef/ktnefmessage.h> +#include <ktnef/ktnefdefs.h> +#include <kabc/phonenumber.h> +#include <kabc/vcardconverter.h> +#include <kabc/stdaddressbook.h> + +#include <kapplication.h> +// #include <kdebug.h> + +#include <klocale.h> +#include <kglobal.h> +#include <kiconloader.h> + +#include <qbuffer.h> +#include <qstylesheet.h> +#include <qdatetime.h> + +#include <time.h> + + +using namespace KCal; + + +/******************************************************************* + * Helper functions for the extensive display (event viewer) + *******************************************************************/ + +static QString eventViewerAddLink( const QString &ref, const QString &text, + bool newline = true ) +{ + QString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" ); + if ( newline ) tmpStr += "\n"; + return tmpStr; +} + +static QString eventViewerAddTag( const QString & tag, const QString & text ) +{ + int numLineBreaks = text.contains( "\n" ); + QString str = "<" + tag + ">"; + QString tmpText = text; + QString tmpStr = str; + if( numLineBreaks >= 0 ) { + if ( numLineBreaks > 0) { + int pos = 0; + QString tmp; + for( int i = 0; i <= numLineBreaks; i++ ) { + pos = tmpText.find( "\n" ); + tmp = tmpText.left( pos ); + tmpText = tmpText.right( tmpText.length() - pos - 1 ); + tmpStr += tmp + "<br>"; + } + } else { + tmpStr += tmpText; + } + } + tmpStr += "</" + tag + ">"; + return tmpStr; +} + +static QString linkPerson( const QString& email, QString name, QString uid ) +{ + // Make the search, if there is an email address to search on, + // and either name or uid is missing + if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) { + KABC::AddressBook *add_book = KABC::StdAddressBook::self( true ); + KABC::Addressee::List addressList = add_book->findByEmail( email ); + KABC::Addressee o = addressList.first(); + if ( !o.isEmpty() && addressList.size() < 2 ) { + if ( name.isEmpty() ) + // No name set, so use the one from the addressbook + name = o.formattedName(); + uid = o.uid(); + } else + // Email not found in the addressbook. Don't make a link + uid = QString::null; + } + kdDebug(5850) << "formatAttendees: uid = " << uid << endl; + + // Show the attendee + QString tmpString = "<li>"; + if ( !uid.isEmpty() ) { + // There is a UID, so make a link to the addressbook + if ( name.isEmpty() ) + // Use the email address for text + tmpString += eventViewerAddLink( "uid:" + uid, email ); + else + tmpString += eventViewerAddLink( "uid:" + uid, name ); + } else { + // No UID, just show some text + tmpString += ( name.isEmpty() ? email : name ); + } + tmpString += '\n'; + + // Make the mailto link + if ( !email.isEmpty() ) { + KCal::Person person( name, email ); + KURL mailto; + mailto.setProtocol( "mailto" ); + mailto.setPath( person.fullName() ); + tmpString += eventViewerAddLink( mailto.url(), QString::null ); + } + tmpString += "</li>\n"; + + return tmpString; +} + +static QString eventViewerFormatAttendees( Incidence *event ) +{ + QString tmpStr; + Attendee::List attendees = event->attendees(); + if ( attendees.count() ) { + + // Add organizer link + tmpStr += eventViewerAddTag( "i", i18n("Organizer") ); + tmpStr += "<ul>"; + tmpStr += linkPerson( event->organizer().email(), + event->organizer().name(), QString::null ); + tmpStr += "</ul>"; + + // Add attendees links + tmpStr += eventViewerAddTag( "i", i18n("Attendees") ); + tmpStr += "<ul>"; + Attendee::List::ConstIterator it; + for( it = attendees.begin(); it != attendees.end(); ++it ) { + Attendee *a = *it; + tmpStr += linkPerson( a->email(), a->name(), a->uid() ); + if ( !a->delegator().isEmpty() ) { + tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() ); + } + if ( !a->delegate().isEmpty() ) { + tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() ); + } + } + tmpStr += "</ul>"; + } + return tmpStr; +} + +static QString eventViewerFormatAttachments( Incidence *i ) +{ + QString tmpStr; + Attachment::List as = i->attachments(); + if ( as.count() > 0 ) { + Attachment::List::ConstIterator it; + for( it = as.begin(); it != as.end(); ++it ) { + if ( (*it)->isUri() ) { + QString name; + if ( (*it)->uri().startsWith( "kmail:" ) ) + name = i18n( "Show mail" ); + else + name = (*it)->uri(); + tmpStr += eventViewerAddLink( (*it)->uri(), name ); + tmpStr += "<br>"; + } + } + } + return tmpStr; +} + +/* + FIXME:This function depends of kaddressbook. Is necessary a new + type of event? +*/ +static QString eventViewerFormatBirthday( Event *event ) +{ + if ( !event) return QString::null; + if ( event->customProperty("KABC","BIRTHDAY") != "YES" ) return QString::null; + + QString uid = event->customProperty("KABC","UID-1"); + QString name = event->customProperty("KABC","NAME-1"); + QString email= event->customProperty("KABC","EMAIL-1"); + + QString tmpString = "<ul>"; + tmpString += linkPerson( email, name, uid ); + + if ( event->customProperty( "KABC", "ANNIVERSARY") == "YES" ) { + uid = event->customProperty("KABC","UID-2"); + name = event->customProperty("KABC","NAME-2"); + email= event->customProperty("KABC","EMAIL-2"); + tmpString += linkPerson( email, name, uid ); + } + + tmpString += "</ul>"; + return tmpString; +} + +static QString eventViewerFormatHeader( Incidence *incidence ) +{ + QString tmpStr = "<table><tr>"; + + // show icons + { + tmpStr += "<td>"; + + if ( incidence->type() == "Event" ) { + tmpStr += "<img src=\"" + + KGlobal::iconLoader()->iconPath( "appointment", KIcon::Small ) + + "\">"; + } + if ( incidence->type() == "Todo" ) { + tmpStr += "<img src=\"" + + KGlobal::iconLoader()->iconPath( "todo", KIcon::Small ) + + "\">"; + } + if ( incidence->type() == "Journal" ) { + tmpStr += "<img src=\"" + + KGlobal::iconLoader()->iconPath( "journal", KIcon::Small ) + + "\">"; + } + if ( incidence->isAlarmEnabled() ) { + tmpStr += "<img src=\"" + + KGlobal::iconLoader()->iconPath( "bell", KIcon::Small ) + + "\">"; + } + if ( incidence->doesRecur() ) { + tmpStr += "<img src=\"" + + KGlobal::iconLoader()->iconPath( "recur", KIcon::Small ) + + "\">"; + } + if ( incidence->isReadOnly() ) { + tmpStr += "<img src=\"" + + KGlobal::iconLoader()->iconPath( "readonlyevent", KIcon::Small ) + + "\">"; + } + + tmpStr += "</td>"; + } + + tmpStr += "<td>" + + eventViewerAddTag( "u", + eventViewerAddTag( "b", incidence->summary() ) ) + + "</td>"; + tmpStr += "</tr></table><br>"; + + return tmpStr; +} + +static QString eventViewerFormatEvent( Event *event ) +{ + if ( !event ) return QString::null; + QString tmpStr = eventViewerFormatHeader( event ); + + tmpStr += "<table>"; + + tmpStr += "<tr>"; + if ( event->doesFloat() ) { + if ( event->isMultiDay() ) { + tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>"; + tmpStr += "<td>" + i18n("<beginTime> - <endTime>","%1 - %2") + .arg( event->dtStartDateStr() ) + .arg( event->dtEndDateStr() ) + "</td>"; + } else { + tmpStr += "<td align=\"right\"><b>" + i18n( "Date" ) + "</b></td>"; + tmpStr += "<td>" + i18n("date as string","%1").arg( event->dtStartDateStr() ) + "</td>"; + } + } else { + if ( event->isMultiDay() ) { + tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>"; + tmpStr += "<td>" + i18n("<beginTime> - <endTime>","%1 - %2") + .arg( event->dtStartStr() ) + .arg( event->dtEndStr() ) + "</td>"; + } else { + tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>"; + if ( event->hasEndDate() && event->dtStart() != event->dtEnd()) { + tmpStr += "<td>" + i18n("<beginTime> - <endTime>","%1 - %2") + .arg( event->dtStartTimeStr() ) + .arg( event->dtEndTimeStr() ) + "</td>"; + } else { + tmpStr += "<td>" + event->dtStartTimeStr() + "</td>"; + } + tmpStr += "</tr><tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "Date" ) + "</b></td>"; + tmpStr += "<td>" + i18n("date as string","%1") + .arg( event->dtStartDateStr() ) + "</td>"; + } + } + tmpStr += "</tr>"; + + if ( event->customProperty("KABC","BIRTHDAY")== "YES" ) { + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "Birthday" ) + "</b></td>"; + tmpStr += "<td>" + eventViewerFormatBirthday( event ) + "</td>"; + tmpStr += "</tr>"; + tmpStr += "</table>"; + return tmpStr; + } + + if ( !event->description().isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "Description" ) + "</b></td>"; + tmpStr += "<td>" + eventViewerAddTag( "p", event->description() ) + "</td>"; + tmpStr += "</tr>"; + } + + if ( !event->location().isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "Location" ) + "</b></td>"; + tmpStr += "<td>" + event->location() + "</td>"; + tmpStr += "</tr>"; + } + + if ( event->categories().count() > 0 ) { + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "1 Category", "%n Categories", event->categories().count() )+ "</b></td>"; + tmpStr += "<td>" + event->categoriesStr() + "</td>"; + tmpStr += "</tr>"; + } + + if ( event->doesRecur() ) { + QDateTime dt = + event->recurrence()->getNextDateTime( QDateTime::currentDateTime() ); + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "Next on" ) + "</b></td>"; + if ( !event->doesFloat() ) { + tmpStr += "<td>" + + KGlobal::locale()->formatDateTime( dt, true ) + "</td>"; + } else { + tmpStr += "<td>" + + KGlobal::locale()->formatDate( dt.date(), true ) + "</td>"; + } + tmpStr += "</tr>"; + } + + int attendeeCount = event->attendees().count(); + if ( attendeeCount > 0 ) { + tmpStr += "<tr><td colspan=\"2\">"; + tmpStr += eventViewerFormatAttendees( event ); + tmpStr += "</td></tr>"; + } + + int attachmentCount = event->attachments().count(); + if ( attachmentCount > 0 ) { + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "1 attachment", "%n attachments", attachmentCount )+ "</b></td>"; + tmpStr += "<td>" + eventViewerFormatAttachments( event ) + "</td>"; + tmpStr += "</tr>"; + } + + tmpStr += "</table>"; + tmpStr += "<em>" + i18n( "Creation date: %1.").arg( + KGlobal::locale()->formatDateTime( event->created() , true ) ) + "</em>"; + return tmpStr; +} + +static QString eventViewerFormatTodo( Todo *todo ) +{ + if ( !todo ) return QString::null; + QString tmpStr = eventViewerFormatHeader( todo ); + + tmpStr += "<table>"; + + if ( todo->hasDueDate() ) { + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "Due on" ) + "</b></td>"; + if ( !todo->doesFloat() ) { + tmpStr += "<td>" + + KGlobal::locale()->formatDateTime( todo->dtDue(), true ) + + "</td>"; + } else { + tmpStr += "<td>" + + KGlobal::locale()->formatDate( todo->dtDue().date(), true ) + + "</td>"; + } + tmpStr += "</tr>"; + } + + if ( !todo->description().isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "Description" ) + "</b></td>"; + tmpStr += "<td>" + todo->description() + "</td>"; + tmpStr += "</tr>"; + } + + if ( !todo->location().isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "Location" ) + "</b></td>"; + tmpStr += "<td>" + todo->location() + "</td>"; + tmpStr += "</tr>"; + } + + if ( todo->categories().count() > 0 ) { + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "1 Category", "%n Categories", todo->categories().count() )+ "</b></td>"; + tmpStr += "<td>" + todo->categoriesStr() + "</td>"; + tmpStr += "</tr>"; + } + + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "Priority" ) + "</b></td>"; + if ( todo->priority() > 0 ) { + tmpStr += "<td>" + QString::number( todo->priority() ) + "</td>"; + } else { + tmpStr += "<td>" + i18n( "Unspecified" ) + "</td>"; + } + tmpStr += "</tr>"; + + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "Completed" ) + "</b></td>"; + tmpStr += "<td>" + i18n( "%1 %" ).arg( todo->percentComplete() ) + "</td>"; + tmpStr += "</tr>"; + + if ( todo->doesRecur() ) { + QDateTime dt = + todo->recurrence()->getNextDateTime( QDateTime::currentDateTime() ); + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "Next on" ) + "</b></td>"; + if ( !todo->doesFloat() ) { + tmpStr += "<td>" + + KGlobal::locale()->formatDateTime( dt, true ) + "</td>"; + } else { + tmpStr += "<td>" + + KGlobal::locale()->formatDate( dt.date(), true ) + "</td>"; + } + tmpStr += "</tr>"; + } + + int attendeeCount = todo->attendees().count(); + if ( attendeeCount > 0 ) { + tmpStr += "<tr><td colspan=\"2\">"; + tmpStr += eventViewerFormatAttendees( todo ); + tmpStr += "</td></tr>"; + } + + int attachmentCount = todo->attachments().count(); + if ( attachmentCount > 0 ) { + tmpStr += "<tr>"; + tmpStr += "<td align=\"right\"><b>" + i18n( "1 attachment", "%n attachments", attachmentCount )+ "</b></td>"; + tmpStr += "<td>" + eventViewerFormatAttachments( todo ) + "</td>"; + tmpStr += "</tr>"; + } + + tmpStr += "</table>"; + tmpStr += "<em>" + i18n( "Creation date: %1.").arg( + KGlobal::locale()->formatDateTime( todo->created(), true ) ) + "</em>"; + return tmpStr; +} + +static QString eventViewerFormatJournal( Journal *journal ) +{ + if ( !journal ) return QString::null; + + QString tmpStr; + if ( !journal->summary().isEmpty() ) { + tmpStr += eventViewerAddTag( "u", + eventViewerAddTag( "b", journal->summary() ) ); + } + tmpStr += eventViewerAddTag( "b", i18n("Journal for %1").arg( journal->dtStartDateStr( false ) ) ); + if ( !journal->description().isEmpty() ) + tmpStr += eventViewerAddTag( "p", journal->description() ); + return tmpStr; +} + +static QString eventViewerFormatFreeBusy( FreeBusy *fb ) +{ + if ( !fb ) return QString::null; + + QString tmpStr = + eventViewerAddTag( "u", + eventViewerAddTag( "b", i18n("Free/Busy information for %1") + .arg( fb->organizer().fullName() ) ) ); + tmpStr += eventViewerAddTag( "i", i18n("Busy times in date range %1 - %2:") + .arg( KGlobal::locale()->formatDate( fb->dtStart().date(), true ) ) + .arg( KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) ) ); + + QValueList<Period> periods = fb->busyPeriods(); + + QString text = eventViewerAddTag( "em", eventViewerAddTag( "b", i18n("Busy:") ) ); + QValueList<Period>::iterator it; + for ( it = periods.begin(); it != periods.end(); ++it ) { + Period per = *it; + if ( per.hasDuration() ) { + int dur = per.duration().asSeconds(); + QString cont; + if ( dur >= 3600 ) { + cont += i18n("1 hour ", "%n hours ", dur / 3600 ); + dur %= 3600; + } + if ( dur >= 60 ) { + cont += i18n("1 minute ", "%n minutes ", dur / 60); + dur %= 60; + } + if ( dur > 0 ) { + cont += i18n("1 second", "%n seconds", dur); + } + text += i18n("startDate for duration", "%1 for %2") + .arg( KGlobal::locale()->formatDateTime( per.start(), false ) ) + .arg( cont ); + text += "<br>"; + } else { + if ( per.start().date() == per.end().date() ) { + text += i18n("date, fromTime - toTime ", "%1, %2 - %3") + .arg( KGlobal::locale()->formatDate( per.start().date() ) ) + .arg( KGlobal::locale()->formatTime( per.start().time() ) ) + .arg( KGlobal::locale()->formatTime( per.end().time() ) ); + } else { + text += i18n("fromDateTime - toDateTime", "%1 - %2") + .arg( KGlobal::locale()->formatDateTime( per.start(), false ) ) + .arg( KGlobal::locale()->formatDateTime( per.end(), false ) ); + } + text += "<br>"; + } + } + tmpStr += eventViewerAddTag( "p", text ); + return tmpStr; +} + +class IncidenceFormatter::EventViewerVisitor : public IncidenceBase::Visitor +{ + public: + EventViewerVisitor() { mResult = ""; } + bool act( IncidenceBase *incidence ) { return incidence->accept( *this ); } + QString result() const { return mResult; } + protected: + bool visit( Event *event ) + { + mResult = eventViewerFormatEvent( event ); + return !mResult.isEmpty(); + } + bool visit( Todo *todo ) + { + mResult = eventViewerFormatTodo( todo ); + return !mResult.isEmpty(); + } + bool visit( Journal *journal ) + { + mResult = eventViewerFormatJournal( journal ); + return !mResult.isEmpty(); + } + bool visit( FreeBusy *fb ) + { + mResult = eventViewerFormatFreeBusy( fb ); + return !mResult.isEmpty(); + } + + protected: + QString mResult; +}; + +QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence ) +{ + if ( !incidence ) return QString::null; + EventViewerVisitor v; + if ( v.act( incidence ) ) { + return v.result(); + } else + return QString::null; +} + + + + +/******************************************************************* + * Helper functions for the body part formatter of kmail + *******************************************************************/ + +static QString string2HTML( const QString& str ) +{ + return QStyleSheet::convertFromPlainText(str, QStyleSheetItem::WhiteSpaceNormal); +} + +static QString eventStartTimeStr( Event *event ) +{ + QString tmp; + if ( ! event->doesFloat() ) { + tmp = i18n("%1: Start Date, %2: Start Time", "%1 %2") + .arg( event->dtStartDateStr(), event->dtStartTimeStr() ); + } else { + tmp = i18n("%1: Start Date", "%1 (time unspecified)") + .arg( event->dtStartDateStr() ); + } + return tmp; +} + +static QString eventEndTimeStr( Event *event ) +{ + QString tmp; + if ( event->hasEndDate() ) { + if ( ! event->doesFloat() ) { + tmp = i18n("%1: End Date, %2: End Time", "%1 %2") + .arg( event->dtEndDateStr(), event->dtEndTimeStr() ); + } else { + tmp = i18n("%1: End Date", "%1 (time unspecified)") + .arg( event->dtEndDateStr() ); + } + } else { + tmp = i18n( "Unspecified" ); + } + return tmp; +} + +static QString invitationRow( const QString &cell1, const QString &cell2 ) +{ + return "<tr><td>" + cell1 + "</td><td>" + cell2 + "</td></tr>\n"; +} + +static QString invitationsDetailsIncidence( Incidence *incidence ) +{ + QString html; + QString descr = incidence->description(); + if( !descr.isEmpty() ) { + html += "<br/><u>" + i18n("Description:") + + "</u><table border=\"0\"><tr><td> </td><td>"; + html += string2HTML(descr) + "</td></tr></table>"; + } + QStringList comments = incidence->comments(); + if ( !comments.isEmpty() ) { + html += "<br><u>" + i18n("Comments:") + + "</u><table border=\"0\"><tr><td> </td><td><ul>"; + for ( uint i = 0; i < comments.count(); ++i ) + html += "<li>" + string2HTML( comments[i] ) + "</li>"; + html += "</ul></td></tr></table>"; + } + return html; +} + +static QString invitationDetailsEvent( Event* event ) +{ + // Meeting details are formatted into an HTML table + if ( !event ) + return QString::null; + + QString html; + QString tmp; + + QString sSummary = i18n( "Summary unspecified" ); + if ( ! event->summary().isEmpty() ) { + sSummary = string2HTML( event->summary() ); + } + + QString sLocation = i18n( "Location unspecified" ); + if ( ! event->location().isEmpty() ) { + sLocation = string2HTML( event->location() ); + } + + QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" ); + html = QString("<div dir=\"%1\">\n").arg(dir); + + html += "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n"; + + // Meeting summary & location rows + html += invitationRow( i18n( "What:" ), sSummary ); + html += invitationRow( i18n( "Where:" ), sLocation ); + + // Meeting Start Time Row + html += invitationRow( i18n( "Start Time:" ), eventStartTimeStr( event ) ); + + // Meeting End Time Row + html += invitationRow( i18n( "End Time:" ), eventEndTimeStr( event ) ); + + // Meeting Duration Row + if ( !event->doesFloat() && event->hasEndDate() ) { + tmp = QString::null; + QTime sDuration(0,0,0), t; + int secs = event->dtStart().secsTo( event->dtEnd() ); + t = sDuration.addSecs( secs ); + if ( t.hour() > 0 ) { + tmp += i18n( "1 hour ", "%n hours ", t.hour() ); + } + if ( t.minute() > 0 ) { + tmp += i18n( "1 minute ", "%n minutes ", t.minute() ); + } + + html += invitationRow( i18n( "Duration:" ), tmp ); + } + + html += "</table>\n"; + html += invitationsDetailsIncidence( event ); + html += "</div>\n"; + + return html; +} + +static QString invitationDetailsTodo( Todo *todo ) +{ + // Task details are formatted into an HTML table + if ( !todo ) + return QString::null; + + QString sSummary = i18n( "Summary unspecified" ); + QString sDescr = i18n( "Description unspecified" ); + if ( ! todo->summary().isEmpty() ) { + sSummary = todo->summary(); + } + if ( ! todo->description().isEmpty() ) { + sDescr = todo->description(); + } + QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" ); + html += invitationRow( i18n( "Summary:" ), sSummary ); + html += invitationRow( i18n( "Description:" ), sDescr ); + html += "</table>\n"; + html += invitationsDetailsIncidence( todo ); + + return html; +} + +static QString invitationDetailsJournal( Journal *journal ) +{ + if ( !journal ) + return QString::null; + + QString sSummary = i18n( "Summary unspecified" ); + QString sDescr = i18n( "Description unspecified" ); + if ( ! journal->summary().isEmpty() ) { + sSummary = journal->summary(); + } + if ( ! journal->description().isEmpty() ) { + sDescr = journal->description(); + } + QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" ); + html += invitationRow( i18n( "Summary:" ), sSummary ); + html += invitationRow( i18n( "Date:" ), journal->dtStartDateStr( false ) ); + html += invitationRow( i18n( "Description:" ), sDescr ); + html += "</table>\n"; + html += invitationsDetailsIncidence( journal ); + + return html; +} + +static QString invitationDetailsFreeBusy( FreeBusy *fb ) +{ + if ( !fb ) + return QString::null; + QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" ); + + html += invitationRow( i18n("Person:"), fb->organizer().fullName() ); + html += invitationRow( i18n("Start date:"), fb->dtStartDateStr() ); + html += invitationRow( i18n("End date:"), + KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) ); + html += "<tr><td colspan=2><hr></td></tr>\n"; + html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n"; + + QValueList<Period> periods = fb->busyPeriods(); + + QValueList<Period>::iterator it; + for ( it = periods.begin(); it != periods.end(); ++it ) { + Period per = *it; + if ( per.hasDuration() ) { + int dur = per.duration().asSeconds(); + QString cont; + if ( dur >= 3600 ) { + cont += i18n("1 hour ", "%n hours ", dur / 3600); + dur %= 3600; + } + if ( dur >= 60 ) { + cont += i18n("1 minute", "%n minutes ", dur / 60); + dur %= 60; + } + if ( dur > 0 ) { + cont += i18n("1 second", "%n seconds", dur); + } + html += invitationRow( QString::null, i18n("startDate for duration", "%1 for %2") + .arg( KGlobal::locale()->formatDateTime( per.start(), false ) ) + .arg(cont) ); + } else { + QString cont; + if ( per.start().date() == per.end().date() ) { + cont = i18n("date, fromTime - toTime ", "%1, %2 - %3") + .arg( KGlobal::locale()->formatDate( per.start().date() ) ) + .arg( KGlobal::locale()->formatTime( per.start().time() ) ) + .arg( KGlobal::locale()->formatTime( per.end().time() ) ); + } else { + cont = i18n("fromDateTime - toDateTime", "%1 - %2") + .arg( KGlobal::locale()->formatDateTime( per.start(), false ) ) + .arg( KGlobal::locale()->formatDateTime( per.end(), false ) ); + } + + html += invitationRow( QString::null, cont ); + } + } + + html += "</table>\n"; + return html; +} + +static QString invitationHeaderEvent( Event *event, ScheduleMessage *msg ) +{ + if ( !msg || !event ) + return QString::null; + switch ( msg->method() ) { + case Scheduler::Publish: + return i18n("This event has been published"); + case Scheduler::Request: + if ( event->revision() > 0 ) + return i18n( "This meeting has been updated" ); + return i18n( "You have been invited to this meeting" ); + case Scheduler::Refresh: + return i18n( "This invitation was refreshed" ); + case Scheduler::Cancel: + return i18n( "This meeting has been canceled" ); + case Scheduler::Add: + return i18n( "Addition to the meeting invitation" ); + case Scheduler::Reply: { + Attendee::List attendees = event->attendees(); + if( attendees.count() == 0 ) { + kdDebug(5850) << "No attendees in the iCal reply!\n"; + return QString::null; + } + if( attendees.count() != 1 ) + kdDebug(5850) << "Warning: attendeecount in the reply should be 1 " + << "but is " << attendees.count() << endl; + Attendee* attendee = *attendees.begin(); + QString attendeeName = attendee->name(); + if ( attendeeName.isEmpty() ) + attendeeName = attendee->email(); + if ( attendeeName.isEmpty() ) + attendeeName = i18n( "Sender" ); + + QString delegatorName, dummy; + KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy ); + if ( delegatorName.isEmpty() ) + delegatorName = attendee->delegator(); + + switch( attendee->status() ) { + case Attendee::NeedsAction: + return i18n( "%1 indicates this invitation still needs some action" ).arg( attendeeName ); + case Attendee::Accepted: + if ( delegatorName.isEmpty() ) + return i18n( "%1 accepts this meeting invitation" ).arg( attendeeName ); + return i18n( "%1 accepts this meeting invitation on behalf of %2" ) + .arg( attendeeName ).arg( delegatorName ); + case Attendee::Tentative: + if ( delegatorName.isEmpty() ) + return i18n( "%1 tentatively accepts this meeting invitation" ).arg( attendeeName ); + return i18n( "%1 tentatively accepts this meeting invitation on behalf of %2" ) + .arg( attendeeName ).arg( delegatorName ); + case Attendee::Declined: + if ( delegatorName.isEmpty() ) + return i18n( "%1 declines this meeting invitation" ).arg( attendeeName ); + return i18n( "%1 declines this meeting invitation on behalf of %2" ) + .arg( attendeeName ).arg( delegatorName ); + case Attendee::Delegated: { + QString delegate, dummy; + KPIM::getNameAndMail( attendee->delegate(), delegate, dummy ); + if ( delegate.isEmpty() ) + delegate = attendee->delegate(); + if ( !delegate.isEmpty() ) + return i18n( "%1 has delegated this meeting invitation to %2" ) + .arg( attendeeName ) .arg( delegate ); + return i18n( "%1 has delegated this meeting invitation" ).arg( attendeeName ); + } + case Attendee::Completed: + return i18n( "This meeting invitation is now completed" ); + case Attendee::InProcess: + return i18n( "%1 is still processing the invitation" ).arg( attendeeName ); + default: + return i18n( "Unknown response to this meeting invitation" ); + } + break; } + case Scheduler::Counter: + return i18n( "Sender makes this counter proposal" ); + case Scheduler::Declinecounter: + return i18n( "Sender declines the counter proposal" ); + case Scheduler::NoMethod: + return i18n("Error: iMIP message with unknown method: '%1'") + .arg( msg->method() ); + } + return QString::null; +} + +static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg ) +{ + if ( !msg || !todo ) + return QString::null; + switch ( msg->method() ) { + case Scheduler::Publish: + return i18n("This task has been published"); + case Scheduler::Request: + if ( todo->revision() > 0 ) + return i18n( "This task has been updated" ); + return i18n( "You have been assigned this task" ); + case Scheduler::Refresh: + return i18n( "This task was refreshed" ); + case Scheduler::Cancel: + return i18n( "This task was canceled" ); + case Scheduler::Add: + return i18n( "Addition to the task" ); + case Scheduler::Reply: { + Attendee::List attendees = todo->attendees(); + if( attendees.count() == 0 ) { + kdDebug(5850) << "No attendees in the iCal reply!\n"; + return QString::null; + } + if( attendees.count() != 1 ) + kdDebug(5850) << "Warning: attendeecount in the reply should be 1 " + << "but is " << attendees.count() << endl; + Attendee* attendee = *attendees.begin(); + + switch( attendee->status() ) { + case Attendee::NeedsAction: + return i18n( "Sender indicates this task assignment still needs some action" ); + case Attendee::Accepted: + return i18n( "Sender accepts this task" ); + case Attendee::Tentative: + return i18n( "Sender tentatively accepts this task" ); + case Attendee::Declined: + return i18n( "Sender declines this task" ); + case Attendee::Delegated: { + QString delegate, dummy; + KPIM::getNameAndMail( attendee->delegate(), delegate, dummy ); + if ( delegate.isEmpty() ) + delegate = attendee->delegate(); + if ( !delegate.isEmpty() ) + return i18n( "Sender has delegated this request for the task to %1" ).arg( delegate ); + return i18n( "Sender has delegated this request for the task " ); + } + case Attendee::Completed: + return i18n( "The request for this task is now completed" ); + case Attendee::InProcess: + return i18n( "Sender is still processing the invitation" ); + default: + return i18n( "Unknown response to this task" ); + } + break; } + case Scheduler::Counter: + return i18n( "Sender makes this counter proposal" ); + case Scheduler::Declinecounter: + return i18n( "Sender declines the counter proposal" ); + case Scheduler::NoMethod: + return i18n("Error: iMIP message with unknown method: '%1'") + .arg( msg->method() ); + } + return QString::null; +} + +static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg ) +{ + // TODO: Several of the methods are not allowed for journals, so remove them. + if ( !msg || !journal ) + return QString::null; + switch ( msg->method() ) { + case Scheduler::Publish: + return i18n("This journal has been published"); + case Scheduler::Request: + return i18n( "You have been assigned this journal" ); + case Scheduler::Refresh: + return i18n( "This journal was refreshed" ); + case Scheduler::Cancel: + return i18n( "This journal was canceled" ); + case Scheduler::Add: + return i18n( "Addition to the journal" ); + case Scheduler::Reply: { + Attendee::List attendees = journal->attendees(); + if( attendees.count() == 0 ) { + kdDebug(5850) << "No attendees in the iCal reply!\n"; + return QString::null; + } + if( attendees.count() != 1 ) + kdDebug(5850) << "Warning: attendeecount in the reply should be 1 " + << "but is " << attendees.count() << endl; + Attendee* attendee = *attendees.begin(); + + switch( attendee->status() ) { + case Attendee::NeedsAction: + return i18n( "Sender indicates this journal assignment still needs some action" ); + case Attendee::Accepted: + return i18n( "Sender accepts this journal" ); + case Attendee::Tentative: + return i18n( "Sender tentatively accepts this journal" ); + case Attendee::Declined: + return i18n( "Sender declines this journal" ); + case Attendee::Delegated: + return i18n( "Sender has delegated this request for the journal" ); + case Attendee::Completed: + return i18n( "The request for this journal is now completed" ); + case Attendee::InProcess: + return i18n( "Sender is still processing the invitation" ); + default: + return i18n( "Unknown response to this journal" ); + } + break; } + case Scheduler::Counter: + return i18n( "Sender makes this counter proposal" ); + case Scheduler::Declinecounter: + return i18n( "Sender declines the counter proposal" ); + case Scheduler::NoMethod: + return i18n("Error: iMIP message with unknown method: '%1'") + .arg( msg->method() ); + } + return QString::null; +} + +static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg ) +{ + if ( !msg || !fb ) + return QString::null; + switch ( msg->method() ) { + case Scheduler::Publish: + return i18n("This free/busy list has been published"); + case Scheduler::Request: + return i18n( "The free/busy list has been requested" ); + case Scheduler::Refresh: + return i18n( "This free/busy list was refreshed" ); + case Scheduler::Cancel: + return i18n( "This free/busy list was canceled" ); + case Scheduler::Add: + return i18n( "Addition to the free/busy list" ); + case Scheduler::NoMethod: + default: + return i18n("Error: Free/Busy iMIP message with unknown method: '%1'") + .arg( msg->method() ); + } +} + +class IncidenceFormatter::ScheduleMessageVisitor : public IncidenceBase::Visitor +{ + public: + ScheduleMessageVisitor() : mMessage(0) { mResult = ""; } + bool act( IncidenceBase *incidence, ScheduleMessage *msg ) { mMessage = msg; return incidence->accept( *this ); } + QString result() const { return mResult; } + + protected: + QString mResult; + ScheduleMessage *mMessage; +}; + +class IncidenceFormatter::InvitationHeaderVisitor : + public IncidenceFormatter::ScheduleMessageVisitor +{ + protected: + bool visit( Event *event ) + { + mResult = invitationHeaderEvent( event, mMessage ); + return !mResult.isEmpty(); + } + bool visit( Todo *todo ) + { + mResult = invitationHeaderTodo( todo, mMessage ); + return !mResult.isEmpty(); + } + bool visit( Journal *journal ) + { + mResult = invitationHeaderJournal( journal, mMessage ); + return !mResult.isEmpty(); + } + bool visit( FreeBusy *fb ) + { + mResult = invitationHeaderFreeBusy( fb, mMessage ); + return !mResult.isEmpty(); + } +}; + +class IncidenceFormatter::InvitationBodyVisitor : + public IncidenceFormatter::ScheduleMessageVisitor +{ + protected: + bool visit( Event *event ) + { + mResult = invitationDetailsEvent( event ); + return !mResult.isEmpty(); + } + bool visit( Todo *todo ) + { + mResult = invitationDetailsTodo( todo ); + return !mResult.isEmpty(); + } + bool visit( Journal *journal ) + { + mResult = invitationDetailsJournal( journal ); + return !mResult.isEmpty(); + } + bool visit( FreeBusy *fb ) + { + mResult = invitationDetailsFreeBusy( fb ); + return !mResult.isEmpty(); + } +}; + +class IncidenceFormatter::IncidenceCompareVisitor : + public IncidenceBase::Visitor +{ + public: + IncidenceCompareVisitor() : mExistingIncidence(0) {} + bool act( IncidenceBase *incidence, Incidence* existingIncidence ) + { + mExistingIncidence = existingIncidence; + return incidence->accept( *this ); + } + + QString result() const + { + if ( mChanges.isEmpty() ) + return QString(); + QString html = "<div align=\"left\"><ul><li>"; + html += mChanges.join( "</li><li>" ); + html += "</li><ul></div>"; + return html; + } + + protected: + bool visit( Event *event ) + { + compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) ); + compareIncidences( event, mExistingIncidence ); + return !mChanges.isEmpty(); + } + bool visit( Todo *todo ) + { + compareIncidences( todo, mExistingIncidence ); + return !mChanges.isEmpty(); + } + bool visit( Journal *journal ) + { + compareIncidences( journal, mExistingIncidence ); + return !mChanges.isEmpty(); + } + bool visit( FreeBusy *fb ) + { + Q_UNUSED( fb ); + return !mChanges.isEmpty(); + } + + private: + void compareEvents( Event *newEvent, Event *oldEvent ) + { + if ( !oldEvent || !newEvent ) + return; + if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->doesFloat() != newEvent->doesFloat() ) + mChanges += i18n( "The begin of the meeting has been changed from %1 to %2" ) + .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) ); + if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->doesFloat() != newEvent->doesFloat() ) + mChanges += i18n( "The end of the meeting has been changed from %1 to %2" ) + .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) ); + } + + void compareIncidences( Incidence *newInc, Incidence *oldInc ) + { + if ( !oldInc || !newInc ) + return; + if ( oldInc->summary() != newInc->summary() ) + mChanges += i18n( "The summary has been changed to: \"%1\"" ).arg( newInc->summary() ); + if ( oldInc->location() != newInc->location() ) + mChanges += i18n( "The location has been changed to: \"%1\"" ).arg( newInc->location() ); + if ( oldInc->description() != newInc->description() ) + mChanges += i18n( "The description has been changed to: \"%1\"" ).arg( newInc->description() ); + Attendee::List oldAttendees = oldInc->attendees(); + Attendee::List newAttendees = newInc->attendees(); + for ( Attendee::List::ConstIterator it = newAttendees.constBegin(); it != newAttendees.constEnd(); ++it ) { + Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() ); + if ( !oldAtt ) { + mChanges += i18n( "Attendee %1 has been added" ).arg( (*it)->fullName() ); + } else { + if ( oldAtt->status() != (*it)->status() ) + mChanges += i18n( "The status of attendee %1 has been changed to: %2" ).arg( (*it)->fullName() ) + .arg( (*it)->statusStr() ); + } + } + for ( Attendee::List::ConstIterator it = oldAttendees.constBegin(); it != oldAttendees.constEnd(); ++it ) { + Attendee *newAtt = newInc->attendeeByMail( (*it)->email() ); + if ( !newAtt ) + mChanges += i18n( "Attendee %1 has been removed" ).arg( (*it)->fullName() ); + } + } + + private: + Incidence* mExistingIncidence; + QStringList mChanges; +}; + + +QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text ) +{ + QString res( "<a href=\"%1\"><b>%2</b></a>" ); + return res.arg( generateLinkURL( id ) ).arg( text ); + return res; +} + +// Check if the given incidence is likely one that we own instead one from +// a shared calendar (Kolab-specific) +static bool incidenceOwnedByMe( Calendar* calendar, Incidence *incidence ) +{ + CalendarResources* cal = dynamic_cast<CalendarResources*>( calendar ); + if ( !cal || !incidence ) + return true; + ResourceCalendar* res = cal->resource( incidence ); + if ( !res ) + return true; + const QString subRes = res->subresourceIdentifier( incidence ); + if ( !subRes.contains( "/.INBOX.directory/" ) ) + return false; + return true; +} + +QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar, + InvitationFormatterHelper *helper ) +{ + if ( invitation.isEmpty() ) return QString::null; + + ICalFormat format; + // parseScheduleMessage takes the tz from the calendar, no need to set it manually here for the format! + ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation ); + + if( !msg ) { + kdDebug( 5850 ) << "Failed to parse the scheduling message" << endl; + Q_ASSERT( format.exception() ); + kdDebug( 5850 ) << format.exception()->message() << endl; + return QString::null; + } + + IncidenceBase *incBase = msg->event(); + + Incidence* existingIncidence = 0; + if ( helper->calendar() ) { + existingIncidence = helper->calendar()->incidence( incBase->uid() ); + if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) + existingIncidence = 0; + if ( !existingIncidence ) { + const Incidence::List list = helper->calendar()->incidences(); + for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) { + if ( (*it)->schedulingID() == incBase->uid() && incidenceOwnedByMe( helper->calendar(), *it ) ) { + existingIncidence = *it; + break; + } + } + } + } + + // First make the text of the message + QString html; + + QString tableStyle = QString::fromLatin1( + "style=\"border: solid 1px; margin: 0em;\"" ); + QString tableHead = QString::fromLatin1( + "<div align=\"center\">" + "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>" + "<tr><td>").arg(tableStyle); + + html += tableHead; + InvitationHeaderVisitor headerVisitor; + // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled + if ( !headerVisitor.act( incBase, msg ) ) + return QString::null; + html += "<b>" + headerVisitor.result() + "</b>"; + + InvitationBodyVisitor bodyVisitor; + if ( !bodyVisitor.act( incBase, msg ) ) + return QString::null; + html += bodyVisitor.result(); + + if ( msg->method() == Scheduler::Request ) { // ### Scheduler::Publish/Refresh/Add as well? + IncidenceCompareVisitor compareVisitor; + if ( compareVisitor.act( incBase, existingIncidence ) ) { + html += i18n("<p align=\"left\">The following changes have been made by the organizer:</p>"); + html += compareVisitor.result(); + } + } + + html += "<br/>"; + html += "<table border=\"0\" cellspacing=\"0\"><tr><td> </td></tr><tr>"; + +#if 0 + html += helper->makeLinkURL( "accept", i18n("[Enter this into my calendar]") ); + html += "</td><td> </td><td>"; +#endif + + // Add groupware links + + switch ( msg->method() ) { + case Scheduler::Publish: + case Scheduler::Request: + case Scheduler::Refresh: + case Scheduler::Add: + { + Incidence *inc = dynamic_cast<Incidence*>( incBase ); + if ( inc && inc->revision() > 0 && (existingIncidence || !helper->calendar()) ) { + if ( incBase->type() == "Todo" ) { + html += "<td colspan=\"9\">"; + html += helper->makeLink( "reply", i18n( "[Enter this into my task list]" ) ); + } else { + html += "<td colspan=\"13\">"; + html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) ); + } + html += "</td></tr><tr>"; + } + html += "<td>"; + + if ( !existingIncidence ) { + // Accept + html += helper->makeLink( "accept", i18n( "[Accept]" ) ); + html += "</td><td> </td><td>"; + html += helper->makeLink( "accept_conditionally", + i18n( "Accept conditionally", "[Accept cond.]" ) ); + html += "</td><td> </td><td>"; + // counter proposal + html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) ); + html += "</td><td> </td><td>"; + // Decline + html += helper->makeLink( "decline", i18n( "[Decline]" ) ); + html += "</td><td> </td><td>"; + + // Delegate + html += helper->makeLink( "delegate", i18n( "[Delegate]" ) ); + html += "</td><td> </td><td>"; + + // Forward + html += helper->makeLink( "forward", i18n( "[Forward]" ) ); + + if ( incBase->type() == "Event" ) { + html += "</b></a></td><td> </td><td>"; + html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) ); + } + } + break; + } + + case Scheduler::Cancel: + // Cancel event from my calendar + html += helper->makeLink( "cancel", i18n( "[Remove this from my calendar]" ) ); + break; + + case Scheduler::Reply: + // Enter this into my calendar + if ( incBase->type() == "Todo" ) { + html += helper->makeLink( "reply", i18n( "[Enter this into my task list]" ) ); + } else { + html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) ); + } + break; + + case Scheduler::Counter: + html += helper->makeLink( "accept_counter", i18n("[Accept]") ); + html += " "; + html += helper->makeLink( "decline_counter", i18n("[Decline]") ); + html += " "; + html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) ); + break; + case Scheduler::Declinecounter: + case Scheduler::NoMethod: + break; + } + + html += "</td></tr></table>"; + + html += "</td></tr></table><br></div>"; + + return html; +} + + + + +/******************************************************************* + * Helper functions for the msTNEF -> VPart converter + *******************************************************************/ + + +//----------------------------------------------------------------------------- + +static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key, + const QString& fallback = QString::null) +{ + return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16, + fallback ); +} + +static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name, + const QString& fallback = QString::null ) +{ + return tnefMsg->findNamedProp( name, fallback ); +} + +struct save_tz { char* old_tz; char* tz_env_str; }; + +/* temporarily go to a different timezone */ +static struct save_tz set_tz( const char* _tc ) +{ + const char *tc = _tc?_tc:"UTC"; + + struct save_tz rv; + + rv.old_tz = 0; + rv.tz_env_str = 0; + + //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl; + + char* tz_env = 0; + if( getenv( "TZ" ) ) { + tz_env = strdup( getenv( "TZ" ) ); + rv.old_tz = tz_env; + } + char* tmp_env = (char*)malloc( strlen( tc ) + 4 ); + strcpy( tmp_env, "TZ=" ); + strcpy( tmp_env+3, tc ); + putenv( tmp_env ); + + rv.tz_env_str = tmp_env; + + /* tmp_env is not free'ed -- it is part of the environment */ + + tzset(); + //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl; + + return rv; +} + +/* restore previous timezone */ +static void unset_tz( struct save_tz old_tz ) +{ + if( old_tz.old_tz ) { + char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 ); + strcpy( tmp_env, "TZ=" ); + strcpy( tmp_env+3, old_tz.old_tz ); + putenv( tmp_env ); + /* tmp_env is not free'ed -- it is part of the environment */ + free( old_tz.old_tz ); + } else { + /* clear TZ from env */ + putenv( strdup("TZ") ); + } + tzset(); + + /* is this OK? */ + if( old_tz.tz_env_str ) free( old_tz.tz_env_str ); +} + +static QDateTime utc2Local( const QDateTime& utcdt ) +{ + struct tm tmL; + + save_tz tmp_tz = set_tz("UTC"); + time_t utc = utcdt.toTime_t(); + unset_tz( tmp_tz ); + + localtime_r( &utc, &tmL ); + return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ), + QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) ); +} + + +static QDateTime pureISOToLocalQDateTime( const QString& dtStr, + bool bDateOnly = false ) +{ + QDate tmpDate; + QTime tmpTime; + int year, month, day, hour, minute, second; + + if( bDateOnly ) { + year = dtStr.left( 4 ).toInt(); + month = dtStr.mid( 4, 2 ).toInt(); + day = dtStr.mid( 6, 2 ).toInt(); + hour = 0; + minute = 0; + second = 0; + } else { + year = dtStr.left( 4 ).toInt(); + month = dtStr.mid( 4, 2 ).toInt(); + day = dtStr.mid( 6, 2 ).toInt(); + hour = dtStr.mid( 9, 2 ).toInt(); + minute = dtStr.mid( 11, 2 ).toInt(); + second = dtStr.mid( 13, 2 ).toInt(); + } + tmpDate.setYMD( year, month, day ); + tmpTime.setHMS( hour, minute, second ); + + if( tmpDate.isValid() && tmpTime.isValid() ) { + QDateTime dT = QDateTime( tmpDate, tmpTime ); + + if( !bDateOnly ) { + // correct for GMT ( == Zulu time == UTC ) + if (dtStr.at(dtStr.length()-1) == 'Z') { + //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() ); + //localUTCOffset( dT ) ); + dT = utc2Local( dT ); + } + } + return dT; + } else + return QDateTime(); +} + + + +QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef ) +{ + bool bOk = false; + + KTNEFParser parser; + QBuffer buf( tnef ); + CalendarLocal cal ( QString::fromLatin1( "UTC" ) ); + KABC::Addressee addressee; + KABC::VCardConverter cardConv; + ICalFormat calFormat; + Event* event = new Event(); + + if( parser.openDevice( &buf ) ) { + KTNEFMessage* tnefMsg = parser.message(); + //QMap<int,KTNEFProperty*> props = parser.message()->properties(); + + // Everything depends from property PR_MESSAGE_CLASS + // (this is added by KTNEFParser): + QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true ) + .upper(); + if( !msgClass.isEmpty() ) { + // Match the old class names that might be used by Outlook for + // compatibility with Microsoft Mail for Windows for Workgroups 3.1. + bool bCompatClassAppointment = false; + bool bCompatMethodRequest = false; + bool bCompatMethodCancled = false; + bool bCompatMethodAccepted = false; + bool bCompatMethodAcceptedCond = false; + bool bCompatMethodDeclined = false; + if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) { + bCompatClassAppointment = true; + if( msgClass.endsWith( ".MTGREQ" ) ) + bCompatMethodRequest = true; + if( msgClass.endsWith( ".MTGCNCL" ) ) + bCompatMethodCancled = true; + if( msgClass.endsWith( ".MTGRESPP" ) ) + bCompatMethodAccepted = true; + if( msgClass.endsWith( ".MTGRESPA" ) ) + bCompatMethodAcceptedCond = true; + if( msgClass.endsWith( ".MTGRESPN" ) ) + bCompatMethodDeclined = true; + } + bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" ); + + if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) { + // Compose a vCal + bool bIsReply = false; + QString prodID = "-//Microsoft Corporation//Outlook "; + prodID += tnefMsg->findNamedProp( "0x8554", "9.0" ); + prodID += "MIMEDIR/EN\n"; + prodID += "VERSION:2.0\n"; + calFormat.setApplication( "Outlook", prodID ); + + Scheduler::Method method; + if( bCompatMethodRequest ) + method = Scheduler::Request; + else if( bCompatMethodCancled ) + method = Scheduler::Cancel; + else if( bCompatMethodAccepted || bCompatMethodAcceptedCond || + bCompatMethodDeclined ) { + method = Scheduler::Reply; + bIsReply = true; + } else { + // pending(khz): verify whether "0x0c17" is the right tag ??? + // + // at the moment we think there are REQUESTS and UPDATES + // + // but WHAT ABOUT REPLIES ??? + // + // + + if( tnefMsg->findProp(0x0c17) == "1" ) + bIsReply = true; + method = Scheduler::Request; + } + + /// ### FIXME Need to get this attribute written + ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown ); + + QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) ); + + if( !sSenderSearchKeyEmail.isEmpty() ) { + int colon = sSenderSearchKeyEmail.find( ':' ); + // May be e.g. "SMTP:KHZ@KDE.ORG" + if( sSenderSearchKeyEmail.find( ':' ) == -1 ) + sSenderSearchKeyEmail.remove( 0, colon+1 ); + } + + QString s( tnefMsg->findProp( 0x0e04 ) ); + QStringList attendees = QStringList::split( ';', s ); + if( attendees.count() ) { + for( QStringList::Iterator it = attendees.begin(); + it != attendees.end(); ++it ) { + // Skip all entries that have no '@' since these are + // no mail addresses + if( (*it).find('@') == -1 ) { + s = (*it).stripWhiteSpace(); + + Attendee *attendee = new Attendee( s, s, true ); + if( bIsReply ) { + if( bCompatMethodAccepted ) + attendee->setStatus( Attendee::Accepted ); + if( bCompatMethodDeclined ) + attendee->setStatus( Attendee::Declined ); + if( bCompatMethodAcceptedCond ) + attendee->setStatus(Attendee::Tentative); + } else { + attendee->setStatus( Attendee::NeedsAction ); + attendee->setRole( Attendee::ReqParticipant ); + } + event->addAttendee(attendee); + } + } + } else { + // Oops, no attendees? + // This must be old style, let us use the PR_SENDER_SEARCH_KEY. + s = sSenderSearchKeyEmail; + if( !s.isEmpty() ) { + Attendee *attendee = new Attendee( QString::null, QString::null, + true ); + if( bIsReply ) { + if( bCompatMethodAccepted ) + attendee->setStatus( Attendee::Accepted ); + if( bCompatMethodAcceptedCond ) + attendee->setStatus( Attendee::Declined ); + if( bCompatMethodDeclined ) + attendee->setStatus( Attendee::Tentative ); + } else { + attendee->setStatus(Attendee::NeedsAction); + attendee->setRole(Attendee::ReqParticipant); + } + event->addAttendee(attendee); + } + } + s = tnefMsg->findProp( 0x0c1f ); // look for organizer property + if( s.isEmpty() && !bIsReply ) + s = sSenderSearchKeyEmail; + // TODO: Use the common name? + if( !s.isEmpty() ) + event->setOrganizer( s ); + + s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null ) + .replace( QChar( ':' ), QString::null ); + event->setDtStart( QDateTime::fromString( s ) ); // ## Format?? + + s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null ) + .replace( QChar( ':' ), QString::null ); + event->setDtEnd( QDateTime::fromString( s ) ); + + s = tnefMsg->findProp( 0x8208 ); + event->setLocation( s ); + + // is it OK to set this to OPAQUE always ?? + //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme! + //vPart += "SEQUENCE:0\n"; + + // is "0x0023" OK - or should we look for "0x0003" ?? + s = tnefMsg->findProp( 0x0023 ); + event->setUid( s ); + + // PENDING(khz): is this value in local timezone? Must it be + // adjusted? Most likely this is a bug in the server or in + // Outlook - we ignore it for now. + s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null ) + .replace( QChar( ':' ), QString::null ); + // ### libkcal always uses currentDateTime() + // event->setDtStamp(QDateTime::fromString(s)); + + s = tnefMsg->findNamedProp( "Keywords" ); + event->setCategories( s ); + + s = tnefMsg->findProp( 0x1000 ); + event->setDescription( s ); + + s = tnefMsg->findProp( 0x0070 ); + event->setSummary( s ); + + s = tnefMsg->findProp( 0x0026 ); + event->setPriority( s.toInt() ); + + // is reminder flag set ? + if(!tnefMsg->findProp(0x8503).isEmpty()) { + Alarm *alarm = new Alarm(event); + QDateTime highNoonTime = + pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 ) + .replace( QChar( '-' ), "" ) + .replace( QChar( ':' ), "" ) ); + QDateTime wakeMeUpTime = + pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" ) + .replace( QChar( '-' ), "" ) + .replace( QChar( ':' ), "" ) ); + alarm->setTime(wakeMeUpTime); + + if( highNoonTime.isValid() && wakeMeUpTime.isValid() ) + alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) ); + else + // default: wake them up 15 minutes before the appointment + alarm->setStartOffset( Duration( 15*60 ) ); + alarm->setDisplayAlarm( i18n( "Reminder" ) ); + + // Sorry: the different action types are not known (yet) + // so we always set 'DISPLAY' (no sounds, no images...) + event->addAlarm( alarm ); + } + cal.addEvent( event ); + bOk = true; + // we finished composing a vCal + } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) { + addressee.setUid( stringProp( tnefMsg, attMSGID ) ); + addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) ); + addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true ); + addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false ); + addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false ); + addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) ); + addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) ); + addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) ); + addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) ); + addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) ); + addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) ); + addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) ); + + QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY ) + .replace( QChar( '-' ), QString::null ) + .replace( QChar( ':' ), QString::null ); + if( !s.isEmpty() ) + addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s ); + + addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE ) ) ); + + // collect parts of Name entry + addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) ); + addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) ); + addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) ); + addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) ); + addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) ); + + addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) ); + addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) ); + addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) ); + /* + the MAPI property ID of this (multiline) )field is unknown: + vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" ); + */ + + KABC::Address adr; + adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) ); + adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) ); + adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) ); + adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) ); + adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) ); + adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) ); + adr.setType(KABC::Address::Home); + addressee.insertAddress(adr); + + adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) ); + adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) ); + adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) ); + adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) ); + adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) ); + adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) ); + adr.setType( KABC::Address::Work ); + addressee.insertAddress( adr ); + + adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) ); + adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) ); + adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) ); + adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) ); + adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) ); + adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) ); + adr.setType( KABC::Address::Dom ); + addressee.insertAddress(adr); + + // problem: the 'other' address was stored by KOrganizer in + // a line looking like the following one: + // 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 + + QString nr; + nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER ); + addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) ); + nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER ); + addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) ); + nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER ); + addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) ); + nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER ); + addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) ); + nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER ); + addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) ); + + s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY ) + .replace( QChar( '-' ), QString::null ) + .replace( QChar( ':' ), QString::null ); + if( !s.isEmpty() ) + addressee.setBirthday( QDateTime::fromString( s ) ); + + bOk = ( !addressee.isEmpty() ); + } else if( "IPM.NOTE" == msgClass ) { + + } // else if ... and so on ... + } + } + + // Compose return string + QString iCal = calFormat.toString( &cal ); + if( !iCal.isEmpty() ) + // This was an iCal + return iCal; + + // Not an iCal - try a vCard + KABC::VCardConverter converter; + return converter.createVCard( addressee ); +} + + +QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef, + Calendar *mCalendar, InvitationFormatterHelper *helper ) +{ + QString vPart = IncidenceFormatter::msTNEFToVPart( tnef ); + QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper ); + if( !iCal.isEmpty() ) + return iCal; + return vPart; +} + + + + +/******************************************************************* + * Helper functions for the Incidence tooltips + *******************************************************************/ + +class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor +{ + public: + ToolTipVisitor() : mRichText( true ), mResult( "" ) {} + + bool act( IncidenceBase *incidence, bool richText=true) + { + mRichText = richText; + mResult = ""; + return incidence ? incidence->accept( *this ) : false; + } + QString result() const { return mResult; } + + protected: + bool visit( Event *event ); + bool visit( Todo *todo ); + bool visit( Journal *journal ); + bool visit( FreeBusy *fb ); + + QString dateRangeText( Event*event ); + QString dateRangeText( Todo *todo ); + QString dateRangeText( Journal *journal ); + QString dateRangeText( FreeBusy *fb ); + + QString generateToolTip( Incidence* incidence, QString dtRangeText ); + + protected: + bool mRichText; + QString mResult; +}; + +QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event*event ) +{ + QString ret; + QString tmp; + if ( event->isMultiDay() ) { + + tmp = "<br>" + i18n("Event start", "<i>From:</i> %1"); + if (event->doesFloat()) + ret += tmp.arg( event->dtStartDateStr().replace(" ", " ") ); + else + ret += tmp.arg( event->dtStartStr().replace(" ", " ") ); + + tmp = "<br>" + i18n("Event end","<i>To:</i> %1"); + if (event->doesFloat()) + ret += tmp.arg( event->dtEndDateStr().replace(" ", " ") ); + else + ret += tmp.arg( event->dtEndStr().replace(" ", " ") ); + + } else { + + ret += "<br>"+i18n("<i>Date:</i> %1"). + arg( event->dtStartDateStr().replace(" ", " ") ); + if ( !event->doesFloat() ) { + const QString dtStartTime = event->dtStartTimeStr().replace( " ", " " ); + const QString dtEndTime = event->dtEndTimeStr().replace( " ", " " ); + if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00' + tmp = "<br>" + i18n("time for event, to prevent ugly line breaks", + "<i>Time:</i> %1"). + arg( dtStartTime ); + } else { + tmp = "<br>" + i18n("time range for event, to prevent ugly line breaks", + "<i>Time:</i> %1 - %2"). + arg( dtStartTime, dtEndTime ); + } + ret += tmp; + } + + } + return ret; +} + +QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo*todo ) +{ + QString ret; + bool floats( todo->doesFloat() ); + if (todo->hasStartDate()) + // No need to add <i> here. This is separated issue and each line + // is very visible on its own. On the other hand... Yes, I like it + // italics here :) + ret += "<br>" + i18n("<i>Start:</i> %1").arg( + (floats) + ?(todo->dtStartDateStr().replace(" ", " ")) + :(todo->dtStartStr().replace(" ", " ")) ) ; + if (todo->hasDueDate()) + ret += "<br>" + i18n("<i>Due:</i> %1").arg( + (floats) + ?(todo->dtDueDateStr().replace(" ", " ")) + :(todo->dtDueStr().replace(" ", " ")) ); + if (todo->isCompleted()) + ret += "<br>" + i18n("<i>Completed:</i> %1").arg( todo->completedStr().replace(" ", " ") ); + else + ret += "<br>" + i18n("%1 % completed").arg(todo->percentComplete()); + + return ret; +} + +QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal ) +{ + QString ret; + if (journal->dtStart().isValid() ) { + ret += "<br>" + i18n("<i>Date:</i> %1").arg( journal->dtStartDateStr( false ) ); + } + return ret; +} + +QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb ) +{ + QString tmp( "<br>" + i18n("<i>Period start:</i> %1") ); + QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) ); + tmp = "<br>" + i18n("<i>Period start:</i> %1"); + ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) ); + return ret; +} + + + +bool IncidenceFormatter::ToolTipVisitor::visit( Event *event ) +{ + mResult = generateToolTip( event, dateRangeText( event ) ); + return !mResult.isEmpty(); +} + +bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo ) +{ + mResult = generateToolTip( todo, dateRangeText( todo ) ); + return !mResult.isEmpty(); +} + +bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal ) +{ + mResult = generateToolTip( journal, dateRangeText( journal ) ); + return !mResult.isEmpty(); +} + +bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb ) +{ + mResult = "<qt><b>" + i18n("Free/Busy information for %1") + .arg(fb->organizer().fullName()) + "</b>"; + mResult += dateRangeText( fb ); + mResult += "</qt>"; + return !mResult.isEmpty(); +} + +QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence* incidence, QString dtRangeText ) +{ + if ( !incidence ) + return QString::null; + + QString tmp = "<qt><b>"+ incidence->summary().replace("\n", "<br>")+"</b>"; + + tmp += dtRangeText; + + if (!incidence->location().isEmpty()) { + // Put Location: in italics + tmp += "<br>"+i18n("<i>Location:</i> %1"). + arg( incidence->location().replace("\n", "<br>") ); + } + if (!incidence->description().isEmpty()) { + QString desc(incidence->description()); + if (desc.length()>120) { + desc = desc.left(120) + "..."; + } + tmp += "<br>----------<br>" + i18n("<i>Description:</i><br>") + desc.replace("\n", "<br>"); + } + tmp += "</qt>"; + return tmp; +} + +QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText ) +{ + ToolTipVisitor v; + if ( v.act( incidence, richText ) ) { + return v.result(); + } else + return QString::null; +} + + + + +/******************************************************************* + * Helper functions for the Incidence tooltips + *******************************************************************/ + +class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor +{ + public: + MailBodyVisitor() : mResult( "" ) {} + + bool act( IncidenceBase *incidence ) + { + mResult = ""; + return incidence ? incidence->accept( *this ) : false; + } + QString result() const { return mResult; } + + protected: + bool visit( Event *event ); + bool visit( Todo *todo ); + bool visit( Journal *journal ); + bool visit( FreeBusy * ) { mResult = i18n("This is a Free Busy Object"); return !mResult.isEmpty(); } + protected: + QString mResult; +}; + + +static QString mailBodyIncidence( Incidence *incidence ) +{ + QString body; + if ( !incidence->summary().isEmpty() ) { + body += i18n("Summary: %1\n").arg( incidence->summary() ); + } + if ( !incidence->organizer().isEmpty() ) { + body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() ); + } + if ( !incidence->location().isEmpty() ) { + body += i18n("Location: %1\n").arg( incidence->location() ); + } + return body; +} + +bool IncidenceFormatter::MailBodyVisitor::visit( Event *event ) +{ + QString recurrence[]= {i18n("no recurrence", "None"), + i18n("Minutely"), i18n("Hourly"), i18n("Daily"), + i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"), + i18n("Yearly"), i18n("Yearly"), i18n("Yearly")}; + + mResult = mailBodyIncidence( event ); + mResult += i18n("Start Date: %1\n").arg( event->dtStartDateStr() ); + if ( !event->doesFloat() ) { + mResult += i18n("Start Time: %1\n").arg( event->dtStartTimeStr() ); + } + if ( event->dtStart() != event->dtEnd() ) { + mResult += i18n("End Date: %1\n").arg( event->dtEndDateStr() ); + } + if ( !event->doesFloat() ) { + mResult += i18n("End Time: %1\n").arg( event->dtEndTimeStr() ); + } + if ( event->doesRecur() ) { + Recurrence *recur = event->recurrence(); + // TODO: Merge these two to one of the form "Recurs every 3 days" + mResult += i18n("Recurs: %1\n") + .arg( recurrence[ recur->recurrenceType() ] ); + mResult += i18n("Frequency: %1\n") + .arg( event->recurrence()->frequency() ); + + if ( recur->duration() > 0 ) { + mResult += i18n ("Repeats once", "Repeats %n times", recur->duration()); + mResult += '\n'; + } else { + if ( recur->duration() != -1 ) { +// TODO_Recurrence: What to do with floating + QString endstr; + if ( event->doesFloat() ) { + endstr = KGlobal::locale()->formatDate( recur->endDate() ); + } else { + endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() ); + } + mResult += i18n("Repeat until: %1\n").arg( endstr ); + } else { + mResult += i18n("Repeats forever\n"); + } + } + } + QString details = event->description(); + if ( !details.isEmpty() ) { + mResult += i18n("Details:\n%1\n").arg( details ); + } + return !mResult.isEmpty(); +} + +bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo ) +{ + mResult = mailBodyIncidence( todo ); + + if ( todo->hasStartDate() ) { + mResult += i18n("Start Date: %1\n").arg( todo->dtStartDateStr() ); + if ( !todo->doesFloat() ) { + mResult += i18n("Start Time: %1\n").arg( todo->dtStartTimeStr() ); + } + } + if ( todo->hasDueDate() ) { + mResult += i18n("Due Date: %1\n").arg( todo->dtDueDateStr() ); + if ( !todo->doesFloat() ) { + mResult += i18n("Due Time: %1\n").arg( todo->dtDueTimeStr() ); + } + } + QString details = todo->description(); + if ( !details.isEmpty() ) { + mResult += i18n("Details:\n%1\n").arg( details ); + } + return !mResult.isEmpty(); +} + +bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal ) +{ + mResult = mailBodyIncidence( journal ); + mResult += i18n("Date: %1\n").arg( journal->dtStartDateStr() ); + if ( !journal->doesFloat() ) { + mResult += i18n("Time: %1\n").arg( journal->dtStartTimeStr() ); + } + if ( !journal->description().isEmpty() ) + mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() ); + return !mResult.isEmpty(); +} + + + +QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence ) +{ + if ( !incidence ) + return QString::null; + + MailBodyVisitor v; + if ( v.act( incidence ) ) { + return v.result(); + } + return QString::null; +} + +static QString recurEnd( Incidence *incidence ) +{ + QString endstr; + if ( incidence->doesFloat() ) { + endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() ); + } else { + endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() ); + } + return endstr; +} + +QString IncidenceFormatter::recurrenceString(Incidence * incidence) +{ + if ( !incidence->doesRecur() ) + return i18n( "No recurrence" ); + + Recurrence *recur = incidence->recurrence(); + switch ( recur->recurrenceType() ) { + case Recurrence::rNone: + return i18n( "No recurrence" ); + case Recurrence::rMinutely: + if ( recur->duration() != -1 ) + return i18n( "Recurs every minute until %1", "Recurs every %n minutes until %1", recur->frequency() ) + .arg( recurEnd( incidence ) ); + return i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() ); + case Recurrence::rHourly: + if ( recur->duration() != -1 ) + return i18n( "Recurs hourly until %1", "Recurs every %n hours until %1", recur->frequency() ) + .arg( recurEnd( incidence ) ); + return i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() ); + case Recurrence::rDaily: + if ( recur->duration() != -1 ) + return i18n( "Recurs daily until %1", "Recurs every %n days until %1", recur->frequency() ) + .arg( recurEnd( incidence ) ); + return i18n( "Recurs daily", "Recurs every %n days", recur->frequency() ); + case Recurrence::rWeekly: + if ( recur->duration() != -1 ) + return i18n( "Recurs weekly until %1", "Recurs every %n weeks until %1", recur->frequency() ) + .arg( recurEnd( incidence ) ); + return i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() ); + case Recurrence::rMonthlyPos: + case Recurrence::rMonthlyDay: + if ( recur->duration() != -1 ) + return i18n( "Recurs monthly until %1" ).arg( recurEnd( incidence ) ); + return i18n( "Recurs monthly" ); + case Recurrence::rYearlyMonth: + case Recurrence::rYearlyDay: + case Recurrence::rYearlyPos: + if ( recur->duration() != -1 ) + return i18n( "Recurs yearly until %1" ).arg( recurEnd( incidence ) ); + return i18n( "Recurs yearly" ); + default: + return i18n( "Incidence recurs" ); + } +} |