/* This file is part of libkcal. Copyright (c) 2001 Cornelius Schumacher Copyright (c) 2004 Reinhold Kainhofer 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // #include #include #include #include #include #include #include #include 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( "" + text + "" ); 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 + "
"; } } else { tmpStr += tmpText; } } tmpStr += ""; 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 = "
  • "; 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 += "
  • \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 += "
      "; tmpStr += linkPerson( event->organizer().email(), event->organizer().name(), QString::null ); tmpStr += "
    "; // Add attendees links tmpStr += eventViewerAddTag( "i", i18n("Attendees") ); tmpStr += "
      "; 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 += "
    "; } 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 += "
    "; } } } 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 = "
      "; 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 += "
    "; return tmpString; } static QString eventViewerFormatHeader( Incidence *incidence ) { QString tmpStr = ""; // show icons { tmpStr += ""; } tmpStr += ""; tmpStr += "
    "; if ( incidence->type() == "Event" ) { tmpStr += "iconPath( "appointment", KIcon::Small ) + "\">"; } if ( incidence->type() == "Todo" ) { tmpStr += "iconPath( "todo", KIcon::Small ) + "\">"; } if ( incidence->type() == "Journal" ) { tmpStr += "iconPath( "journal", KIcon::Small ) + "\">"; } if ( incidence->isAlarmEnabled() ) { tmpStr += "iconPath( "bell", KIcon::Small ) + "\">"; } if ( incidence->doesRecur() ) { tmpStr += "iconPath( "recur", KIcon::Small ) + "\">"; } if ( incidence->isReadOnly() ) { tmpStr += "iconPath( "readonlyevent", KIcon::Small ) + "\">"; } tmpStr += "" + eventViewerAddTag( "u", eventViewerAddTag( "b", incidence->summary() ) ) + "

    "; return tmpStr; } static QString eventViewerFormatEvent( Event *event ) { if ( !event ) return QString::null; QString tmpStr = eventViewerFormatHeader( event ); tmpStr += ""; tmpStr += ""; if ( event->doesFloat() ) { if ( event->isMultiDay() ) { tmpStr += ""; tmpStr += ""; } else { tmpStr += ""; tmpStr += ""; } } else { if ( event->isMultiDay() ) { tmpStr += ""; tmpStr += ""; } else { tmpStr += ""; if ( event->hasEndDate() && event->dtStart() != event->dtEnd()) { tmpStr += ""; } else { tmpStr += ""; } tmpStr += ""; tmpStr += ""; tmpStr += ""; } } tmpStr += ""; if ( event->customProperty("KABC","BIRTHDAY")== "YES" ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += "
    " + i18n( "Time" ) + "" + i18n(" - ","%1 - %2") .arg( event->dtStartDateStr() ) .arg( event->dtEndDateStr() ) + "" + i18n( "Date" ) + "" + i18n("date as string","%1").arg( event->dtStartDateStr() ) + "" + i18n( "Time" ) + "" + i18n(" - ","%1 - %2") .arg( event->dtStartStr() ) .arg( event->dtEndStr() ) + "" + i18n( "Time" ) + "" + i18n(" - ","%1 - %2") .arg( event->dtStartTimeStr() ) .arg( event->dtEndTimeStr() ) + "" + event->dtStartTimeStr() + "
    " + i18n( "Date" ) + "" + i18n("date as string","%1") .arg( event->dtStartDateStr() ) + "
    " + i18n( "Birthday" ) + "" + eventViewerFormatBirthday( event ) + "
    "; return tmpStr; } if ( !event->description().isEmpty() ) { tmpStr += ""; tmpStr += "" + i18n( "Description" ) + ""; tmpStr += "" + eventViewerAddTag( "p", event->description() ) + ""; tmpStr += ""; } if ( !event->location().isEmpty() ) { tmpStr += ""; tmpStr += "" + i18n( "Location" ) + ""; tmpStr += "" + event->location() + ""; tmpStr += ""; } if ( event->categories().count() > 0 ) { tmpStr += ""; tmpStr += "" + i18n( "1 Category", "%n Categories", event->categories().count() )+ ""; tmpStr += "" + event->categoriesStr() + ""; tmpStr += ""; } if ( event->doesRecur() ) { QDateTime dt = event->recurrence()->getNextDateTime( QDateTime::currentDateTime() ); tmpStr += ""; tmpStr += "" + i18n( "Next on" ) + ""; if ( !event->doesFloat() ) { tmpStr += "" + KGlobal::locale()->formatDateTime( dt, true ) + ""; } else { tmpStr += "" + KGlobal::locale()->formatDate( dt.date(), true ) + ""; } tmpStr += ""; } int attendeeCount = event->attendees().count(); if ( attendeeCount > 0 ) { tmpStr += ""; tmpStr += eventViewerFormatAttendees( event ); tmpStr += ""; } int attachmentCount = event->attachments().count(); if ( attachmentCount > 0 ) { tmpStr += ""; tmpStr += "" + i18n( "1 attachment", "%n attachments", attachmentCount )+ ""; tmpStr += "" + eventViewerFormatAttachments( event ) + ""; tmpStr += ""; } tmpStr += ""; tmpStr += "" + i18n( "Creation date: %1.").arg( KGlobal::locale()->formatDateTime( event->created() , true ) ) + ""; return tmpStr; } static QString eventViewerFormatTodo( Todo *todo ) { if ( !todo ) return QString::null; QString tmpStr = eventViewerFormatHeader( todo ); tmpStr += ""; if ( todo->hasDueDate() ) { tmpStr += ""; tmpStr += ""; if ( !todo->doesFloat() ) { tmpStr += ""; } else { tmpStr += ""; } tmpStr += ""; } if ( !todo->description().isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } if ( !todo->location().isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } if ( todo->categories().count() > 0 ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } tmpStr += ""; tmpStr += ""; if ( todo->priority() > 0 ) { tmpStr += ""; } else { tmpStr += ""; } tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; if ( todo->doesRecur() ) { QDateTime dt = todo->recurrence()->getNextDateTime( QDateTime::currentDateTime() ); tmpStr += ""; tmpStr += ""; if ( !todo->doesFloat() ) { tmpStr += ""; } else { tmpStr += ""; } tmpStr += ""; } int attendeeCount = todo->attendees().count(); if ( attendeeCount > 0 ) { tmpStr += ""; } int attachmentCount = todo->attachments().count(); if ( attachmentCount > 0 ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } tmpStr += "
    " + i18n( "Due on" ) + "" + KGlobal::locale()->formatDateTime( todo->dtDue(), true ) + "" + KGlobal::locale()->formatDate( todo->dtDue().date(), true ) + "
    " + i18n( "Description" ) + "" + todo->description() + "
    " + i18n( "Location" ) + "" + todo->location() + "
    " + i18n( "1 Category", "%n Categories", todo->categories().count() )+ "" + todo->categoriesStr() + "
    " + i18n( "Priority" ) + "" + QString::number( todo->priority() ) + "" + i18n( "Unspecified" ) + "
    " + i18n( "Completed" ) + "" + i18n( "%1 %" ).arg( todo->percentComplete() ) + "
    " + i18n( "Next on" ) + "" + KGlobal::locale()->formatDateTime( dt, true ) + "" + KGlobal::locale()->formatDate( dt.date(), true ) + "
    "; tmpStr += eventViewerFormatAttendees( todo ); tmpStr += "
    " + i18n( "1 attachment", "%n attachments", attachmentCount )+ "" + eventViewerFormatAttachments( todo ) + "
    "; tmpStr += "" + i18n( "Creation date: %1.").arg( KGlobal::locale()->formatDateTime( todo->created(), true ) ) + ""; 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 periods = fb->busyPeriods(); QString text = eventViewerAddTag( "em", eventViewerAddTag( "b", i18n("Busy:") ) ); QValueList::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 += "
    "; } 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 += "
    "; } } 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 "" + cell1 + "" + cell2 + "\n"; } static QString invitationsDetailsIncidence( Incidence *incidence ) { QString html; QString descr = incidence->description(); if( !descr.isEmpty() ) { html += "
    " + i18n("Description:") + "
     "; html += string2HTML(descr) + "
    "; } QStringList comments = incidence->comments(); if ( !comments.isEmpty() ) { html += "
    " + i18n("Comments:") + "
     
      "; for ( uint i = 0; i < comments.count(); ++i ) html += "
    • " + string2HTML( comments[i] ) + "
    • "; html += "
    "; } 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("
    \n").arg(dir); html += "\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 += "
    \n"; html += invitationsDetailsIncidence( event ); html += "
    \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( "\n" ); html += invitationRow( i18n( "Summary:" ), sSummary ); html += invitationRow( i18n( "Description:" ), sDescr ); html += "
    \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( "\n" ); html += invitationRow( i18n( "Summary:" ), sSummary ); html += invitationRow( i18n( "Date:" ), journal->dtStartDateStr( false ) ); html += invitationRow( i18n( "Description:" ), sDescr ); html += "
    \n"; html += invitationsDetailsIncidence( journal ); return html; } static QString invitationDetailsFreeBusy( FreeBusy *fb ) { if ( !fb ) return QString::null; QString html( "\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 += "\n"; html += "\n"; QValueList periods = fb->busyPeriods(); QValueList::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 += "

    Busy periods given in this free/busy object:
    \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 = "
    • "; html += mChanges.join( "
    • " ); html += "
      "; return html; } protected: bool visit( Event *event ) { compareEvents( event, dynamic_cast( 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( "%2" ); 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( 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( "
      " "" "
      ").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 += "" + headerVisitor.result() + ""; 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("

      The following changes have been made by the organizer:

      "); html += compareVisitor.result(); } } html += "
      "; html += ""; #if 0 html += helper->makeLinkURL( "accept", i18n("[Enter this into my calendar]") ); html += ""; } html += "
       
        "; #endif // Add groupware links switch ( msg->method() ) { case Scheduler::Publish: case Scheduler::Request: case Scheduler::Refresh: case Scheduler::Add: { Incidence *inc = dynamic_cast( incBase ); if ( inc && inc->revision() > 0 && (existingIncidence || !helper->calendar()) ) { if ( incBase->type() == "Todo" ) { html += ""; html += helper->makeLink( "reply", i18n( "[Enter this into my task list]" ) ); } else { html += ""; html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) ); } html += "
      "; if ( !existingIncidence ) { // Accept html += helper->makeLink( "accept", i18n( "[Accept]" ) ); html += "   "; html += helper->makeLink( "accept_conditionally", i18n( "Accept conditionally", "[Accept cond.]" ) ); html += "   "; // counter proposal html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) ); html += "   "; // Decline html += helper->makeLink( "decline", i18n( "[Decline]" ) ); html += "   "; // Delegate html += helper->makeLink( "delegate", i18n( "[Delegate]" ) ); html += "   "; // Forward html += helper->makeLink( "forward", i18n( "[Forward]" ) ); if ( incBase->type() == "Event" ) { html += "   "; 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 += "
      "; html += "

      "; 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 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 = "
      " + i18n("Event start", "From: %1"); if (event->doesFloat()) ret += tmp.arg( event->dtStartDateStr().replace(" ", " ") ); else ret += tmp.arg( event->dtStartStr().replace(" ", " ") ); tmp = "
      " + i18n("Event end","To: %1"); if (event->doesFloat()) ret += tmp.arg( event->dtEndDateStr().replace(" ", " ") ); else ret += tmp.arg( event->dtEndStr().replace(" ", " ") ); } else { ret += "
      "+i18n("Date: %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 = "
      " + i18n("time for event,   to prevent ugly line breaks", "Time: %1"). arg( dtStartTime ); } else { tmp = "
      " + i18n("time range for event,   to prevent ugly line breaks", "Time: %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 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 += "
      " + i18n("Start: %1").arg( (floats) ?(todo->dtStartDateStr().replace(" ", " ")) :(todo->dtStartStr().replace(" ", " ")) ) ; if (todo->hasDueDate()) ret += "
      " + i18n("Due: %1").arg( (floats) ?(todo->dtDueDateStr().replace(" ", " ")) :(todo->dtDueStr().replace(" ", " ")) ); if (todo->isCompleted()) ret += "
      " + i18n("Completed: %1").arg( todo->completedStr().replace(" ", " ") ); else ret += "
      " + i18n("%1 % completed").arg(todo->percentComplete()); return ret; } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal ) { QString ret; if (journal->dtStart().isValid() ) { ret += "
      " + i18n("Date: %1").arg( journal->dtStartDateStr( false ) ); } return ret; } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb ) { QString tmp( "
      " + i18n("Period start: %1") ); QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) ); tmp = "
      " + i18n("Period start: %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 = "" + i18n("Free/Busy information for %1") .arg(fb->organizer().fullName()) + ""; mResult += dateRangeText( fb ); mResult += ""; return !mResult.isEmpty(); } QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence* incidence, QString dtRangeText ) { if ( !incidence ) return QString::null; QString tmp = ""+ incidence->summary().replace("\n", "
      ")+"
      "; tmp += dtRangeText; if (!incidence->location().isEmpty()) { // Put Location: in italics tmp += "
      "+i18n("Location: %1"). arg( incidence->location().replace("\n", "
      ") ); } if (!incidence->description().isEmpty()) { QString desc(incidence->description()); if (desc.length()>120) { desc = desc.left(120) + "..."; } tmp += "
      ----------
      " + i18n("Description:
      ") + desc.replace("\n", "
      "); } tmp += "
      "; 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" ); } }