/* This file is part of the kcal library. Copyright (c) 2001 Cornelius Schumacher Copyright (c) 2004 Reinhold Kainhofer Copyright (c) 2005 Rafal Rzepecki Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company 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. */ /** @file This file is part of the API for handling calendar data and provides static functions for formatting Incidences for various purposes. @brief Provides methods to format Incidences in various ways for display purposes. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ @author Allen Winter \ */ #include "incidenceformatter.h" #include "attachment.h" #include "calendar.h" #include "calendarlocal.h" #ifndef KDEPIM_NO_KRESOURCES #include "calendarresources.h" #endif #include "event.h" #include "freebusy.h" #include "icalformat.h" #include "journal.h" #include "todo.h" #include "kpimutils/email.h" #include "kabc/phonenumber.h" #include "kabc/vcardconverter.h" #include "kabc/stdaddressbook.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KCal; using namespace IncidenceFormatter; /******************* * General helpers *******************/ //@cond PRIVATE static QString htmlAddLink( const QString &ref, const QString &text, bool newline = true ) { QString tmpStr( "" + text + "" ); if ( newline ) { tmpStr += '\n'; } return tmpStr; } static QString htmlAddTag( const QString &tag, const QString &text ) { int numLineBreaks = text.count( "\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.indexOf( "\n" ); tmp = tmpText.left( pos ); tmpText = tmpText.right( tmpText.length() - pos - 1 ); tmpStr += tmp + "
"; } } else { tmpStr += tmpText; } } tmpStr += "'; return tmpStr; } static bool iamAttendee( Attendee *attendee ) { // Check if I'm this attendee bool iam = false; KEMailSettings settings; QStringList profiles = settings.profiles(); for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) { settings.setProfile( *it ); if ( settings.getSetting( KEMailSettings::EmailAddress ) == attendee->email() ) { iam = true; break; } } return iam; } static bool iamOrganizer( Incidence *incidence ) { // Check if I'm the organizer for this incidence if ( !incidence ) { return false; } bool iam = false; KEMailSettings settings; QStringList profiles = settings.profiles(); for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) { settings.setProfile( *it ); if ( settings.getSetting( KEMailSettings::EmailAddress ) == incidence->organizer().email() ) { iam = true; break; } } return iam; } static bool senderIsOrganizer( Incidence *incidence, const QString &sender ) { // Check if the specified sender is the organizer if ( !incidence || sender.isEmpty() ) { return true; } bool isorg = true; QString senderName, senderEmail; if ( KPIMUtils::extractEmailAddressAndName( sender, senderEmail, senderName ) ) { // for this heuristic, we say the sender is the organizer if either the name or the email match. if ( incidence->organizer().email() != senderEmail && incidence->organizer().name() != senderName ) { isorg = false; } } return isorg; } static QString firstAttendeeName( Incidence *incidence, const QString &defName ) { QString name; if ( !incidence ) { return name; } Attendee::List attendees = incidence->attendees(); if( attendees.count() > 0 ) { Attendee *attendee = *attendees.begin(); name = attendee->name(); if ( name.isEmpty() ) { name = attendee->email(); } if ( name.isEmpty() ) { name = defName; } } return name; } //@endcond /******************************************************************* * Helper functions for the extensive display (display viewer) *******************************************************************/ //@cond PRIVATE static QString displayViewLinkPerson( const QString &email, QString name, QString uid, const QString &iconPath ) { // 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() ) ) { #ifndef KDEPIM_NO_KRESOURCES KABC::AddressBook *add_book = KABC::StdAddressBook::self( true ); KABC::Addressee::List addressList = add_book->findByEmail( email ); KABC::Addressee o = ( !addressList.isEmpty() ? addressList.first() : KABC::Addressee() ); 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.clear(); } #else uid.clear(); #endif } // 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 += htmlAddLink( "uid:" + uid, email ); } else { tmpString += htmlAddLink( "uid:" + uid, name ); } } else { // No UID, just show some text tmpString += ( name.isEmpty() ? email : name ); } // Make the mailto link if ( !email.isEmpty() && !iconPath.isNull() ) { KUrl mailto; mailto.setProtocol( "mailto" ); mailto.setPath( email ); tmpString += htmlAddLink( mailto.url(), "" ); } return tmpString; } static QString displayViewFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role ) { QString tmpStr; Attendee::List::ConstIterator it; Attendee::List attendees = incidence->attendees(); KIconLoader *iconLoader = KIconLoader::global(); const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small ); for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { Attendee *a = *it; if ( a->role() != role ) { // skip this role continue; } if ( a->email() == incidence->organizer().email() ) { // skip attendee that is also the organizer continue; } tmpStr += displayViewLinkPerson( a->email(), a->name(), a->uid(), iconPath ); if ( !a->delegator().isEmpty() ) { tmpStr += i18n( " (delegated by %1)", a->delegator() ); } if ( !a->delegate().isEmpty() ) { tmpStr += i18n( " (delegated to %1)", a->delegate() ); } tmpStr += "
"; } if ( tmpStr.endsWith( QLatin1String( "
" ) ) ) { tmpStr.chop( 4 ); } return tmpStr; } static QString displayViewFormatAttendees( Incidence *incidence ) { QString tmpStr, str; KIconLoader *iconLoader = KIconLoader::global(); const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small ); // Add organizer link int attendeeCount = incidence->attendees().count(); if ( attendeeCount > 1 || ( attendeeCount == 1 && incidence->organizer().email() != incidence->attendees().first()->email() ) ) { tmpStr += ""; tmpStr += "" + i18n( "Organizer:" ) + ""; tmpStr += "" + displayViewLinkPerson( incidence->organizer().email(), incidence->organizer().name(), QString(), iconPath ) + ""; tmpStr += ""; } // Add "chair" str = displayViewFormatAttendeeRoleList( incidence, Attendee::Chair ); if ( !str.isEmpty() ) { tmpStr += ""; tmpStr += "" + i18n( "Chair:" ) + ""; tmpStr += "" + str + ""; tmpStr += ""; } // Add required participants str = displayViewFormatAttendeeRoleList( incidence, Attendee::ReqParticipant ); if ( !str.isEmpty() ) { tmpStr += ""; tmpStr += "" + i18n( "Required Participants:" ) + ""; tmpStr += "" + str + ""; tmpStr += ""; } // Add optional participants str = displayViewFormatAttendeeRoleList( incidence, Attendee::OptParticipant ); if ( !str.isEmpty() ) { tmpStr += ""; tmpStr += "" + i18n( "Optional Participants:" ) + ""; tmpStr += "" + str + ""; tmpStr += ""; } // Add observers str = displayViewFormatAttendeeRoleList( incidence, Attendee::NonParticipant ); if ( !str.isEmpty() ) { tmpStr += ""; tmpStr += "" + i18n( "Observers:" ) + ""; tmpStr += "" + str + ""; tmpStr += ""; } return tmpStr; } static QString displayViewFormatAttachments( Incidence *incidence ) { QString tmpStr; Attachment::List as = incidence->attachments(); Attachment::List::ConstIterator it; int count = 0; for ( it = as.constBegin(); it != as.constEnd(); ++it ) { count++; if ( (*it)->isUri() ) { QString name; if ( (*it)->uri().startsWith( QLatin1String( "kmail:" ) ) ) { name = i18n( "Show mail" ); } else { name = (*it)->label(); } tmpStr += htmlAddLink( (*it)->uri(), name ); } else { tmpStr += (*it)->label(); } if ( count < as.count() ) { tmpStr += "
"; } } return tmpStr; } static QString displayViewFormatCategories( Incidence *incidence ) { // We do not use Incidence::categoriesStr() since it does not have whitespace return incidence->categories().join( ", " ); } static QString displayViewFormatCreationDate( Incidence *incidence, KDateTime::Spec spec ) { KDateTime kdt = incidence->created().toTimeSpec( spec ); return i18n( "Creation date: %1", dateTimeToString( incidence->created(), false, true, spec ) ); } static QString displayViewFormatBirthday( Event *event ) { if ( !event ) { return QString(); } if ( event->customProperty( "KABC", "BIRTHDAY" ) != "YES" && event->customProperty( "KABC", "ANNIVERSARY" ) != "YES" ) { return QString(); } QString uid_1 = event->customProperty( "KABC", "UID-1" ); QString name_1 = event->customProperty( "KABC", "NAME-1" ); QString email_1= event->customProperty( "KABC", "EMAIL-1" ); KIconLoader *iconLoader = KIconLoader::global(); const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small ); //TODO: add a birthday cake icon QString tmpStr = displayViewLinkPerson( email_1, name_1, uid_1, iconPath ); return tmpStr; } static QString displayViewFormatHeader( Incidence *incidence ) { QString tmpStr = ""; // show icons KIconLoader *iconLoader = KIconLoader::global(); tmpStr += ""; tmpStr += ""; tmpStr += "
"; // TODO: KDE5. Make the function QString Incidence::getPixmap() so we don't // need downcasting. if ( incidence->type() == "Todo" ) { tmpStr += "( incidence ); if ( !todo->isCompleted() ) { tmpStr += iconLoader->iconPath( "view-calendar-tasks", KIconLoader::Small ); } else { tmpStr += iconLoader->iconPath( "task-complete", KIconLoader::Small ); } tmpStr += "\">"; } if ( incidence->type() == "Event" ) { QString iconPath; if ( incidence->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) { iconPath = iconLoader->iconPath( "view-calendar-birthday", KIconLoader::Small ); } else if ( incidence->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) { iconPath = iconLoader->iconPath( "view-calendar-wedding-anniversary", KIconLoader::Small ); } else { iconPath = iconLoader->iconPath( "view-calendar-day", KIconLoader::Small ); } tmpStr += ""; } if ( incidence->type() == "Journal" ) { tmpStr += "iconPath( "view-pim-journal", KIconLoader::Small ) + "\">"; } if ( incidence->isAlarmEnabled() ) { tmpStr += "iconPath( "preferences-desktop-notification-bell", KIconLoader::Small ) + "\">"; } if ( incidence->recurs() ) { tmpStr += "iconPath( "edit-redo", KIconLoader::Small ) + "\">"; } if ( incidence->isReadOnly() ) { tmpStr += "iconPath( "object-locked", KIconLoader::Small ) + "\">"; } tmpStr += ""; tmpStr += "" + incidence->richSummary() + ""; tmpStr += "
"; return tmpStr; } static QString displayViewFormatEvent( const QString &calStr, Event *event, const QDate &date, KDateTime::Spec spec ) { if ( !event ) { return QString(); } QString tmpStr = displayViewFormatHeader( event ); tmpStr += ""; tmpStr += ""; tmpStr += ""; if ( !calStr.isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } if ( !event->location().isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } KDateTime startDt = event->dtStart(); KDateTime endDt = event->dtEnd(); if ( event->recurs() ) { if ( date.isValid() ) { KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() ); int diffDays = startDt.daysTo( kdt ); kdt = kdt.addSecs( -1 ); startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() ); if ( event->hasEndDate() ) { endDt = endDt.addDays( diffDays ); if ( startDt > endDt ) { startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() ); endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) ); } } } } tmpStr += ""; if ( event->allDay() ) { if ( event->isMultiDay() ) { tmpStr += ""; tmpStr += ""; } else { tmpStr += ""; tmpStr += ""; } } else { if ( event->isMultiDay() ) { tmpStr += ""; tmpStr += ""; } else { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; if ( event->hasEndDate() && startDt != endDt ) { tmpStr += ""; } else { tmpStr += ""; } } } tmpStr += ""; QString durStr = durationString( event ); if ( !durStr.isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } if ( event->recurs() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } const bool isBirthday = event->customProperty( "KABC", "BIRTHDAY" ) == "YES"; const bool isAnniversary = event->customProperty( "KABC", "ANNIVERSARY" ) == "YES"; if ( isBirthday || isAnniversary ) { tmpStr += ""; if ( isAnniversary ) { tmpStr += ""; } else { tmpStr += ""; } tmpStr += ""; tmpStr += ""; tmpStr += "
" + i18n( "Calendar:" ) + "" + calStr + "
" + i18nc( "@title:column event location", "Location:" ) + "" + event->richLocation() + "
" + i18n( "Date:" ) + "" + i18nc( " - ","%1 - %2", dateToString( startDt, false, spec ), dateToString( endDt, false, spec ) ) + "" + i18n( "Date:" ) + "" + i18nc( "date as string","%1", dateToString( startDt, false, spec ) ) + "" + i18n( "Date:" ) + "" + i18nc( " - ","%1 - %2", dateToString( startDt, false, spec ), dateToString( endDt, false, spec ) ) + "" + i18n( "Date:" ) + "" + i18nc( "date as string", "%1", dateToString( startDt, false, spec ) ) + "
" + i18n( "Time:" ) + "" + i18nc( " - ","%1 - %2", timeToString( startDt, true, spec ), timeToString( endDt, true, spec ) ) + "" + timeToString( startDt, true, spec ) + "
" + i18n( "Duration:" ) + "" + durStr + "
" + i18n( "Recurrence:" ) + "" + recurrenceString( event ) + "
" + i18n( "Anniversary:" ) + "" + i18n( "Birthday:" ) + "" + displayViewFormatBirthday( event ) + "
"; return tmpStr; } if ( !event->description().isEmpty() ) { tmpStr += ""; tmpStr += "" + i18n( "Description:" ) + ""; tmpStr += "" + event->richDescription() + ""; tmpStr += ""; } // TODO: print comments? int reminderCount = event->alarms().count(); if ( reminderCount > 0 && event->isAlarmEnabled() ) { tmpStr += ""; tmpStr += "" + i18np( "Reminder:", "Reminders:", reminderCount ) + ""; tmpStr += "" + reminderStringList( event ).join( "
" ) + ""; tmpStr += ""; } tmpStr += displayViewFormatAttendees( event ); int categoryCount = event->categories().count(); if ( categoryCount > 0 ) { tmpStr += ""; tmpStr += ""; tmpStr += i18np( "Category:", "Categories:", categoryCount ) + ""; tmpStr += "" + displayViewFormatCategories( event ) + ""; tmpStr += ""; } int attachmentCount = event->attachments().count(); if ( attachmentCount > 0 ) { tmpStr += ""; tmpStr += "" + i18np( "Attachment:", "Attachments:", attachmentCount ) + ""; tmpStr += "" + displayViewFormatAttachments( event ) + ""; tmpStr += ""; } tmpStr += ""; tmpStr += "

" + displayViewFormatCreationDate( event, spec ) + ""; return tmpStr; } static QString displayViewFormatTodo( const QString &calStr, Todo *todo, const QDate &date, KDateTime::Spec spec ) { if ( !todo ) { return QString(); } QString tmpStr = displayViewFormatHeader( todo ); tmpStr += ""; tmpStr += ""; tmpStr += ""; if ( !calStr.isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } if ( !todo->location().isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } if ( todo->hasStartDate() && todo->dtStart().isValid() ) { KDateTime startDt = todo->dtStart(); if ( todo->recurs() ) { if ( date.isValid() ) { startDt.setDate( date ); } } tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } if ( todo->hasDueDate() && todo->dtDue().isValid() ) { KDateTime dueDt = todo->dtDue(); if ( todo->recurs() ) { if ( date.isValid() ) { KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() ); kdt = kdt.addSecs( -1 ); dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() ); } } tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } QString durStr = durationString( todo ); if ( !durStr.isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } if ( todo->recurs() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } if ( !todo->description().isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } // TODO: print comments? int reminderCount = todo->alarms().count(); if ( reminderCount > 0 && todo->isAlarmEnabled() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } tmpStr += displayViewFormatAttendees( todo ); int categoryCount = todo->categories().count(); if ( categoryCount > 0 ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } if ( todo->priority() > 0 ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } tmpStr += ""; if ( todo->isCompleted() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; int attachmentCount = todo->attachments().count(); if ( attachmentCount > 0 ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } tmpStr += "
" + i18n( "Calendar:" ) + "" + calStr + "
" + i18nc( "@title:column to-do location", "Location:" ) + "" + todo->richLocation() + "
" + i18nc( "to-do start date/time", "Start:" ) + "" + dateTimeToString( startDt, todo->allDay(), false, spec ) + "
" + i18nc( "to-do due date/time", "Due:" ) + "" + dateTimeToString( dueDt, todo->allDay(), false, spec ) + "
" + i18n( "Duration:" ) + "" + durStr + "
" + i18n( "Recurrence:" ) + "" + recurrenceString( todo ) + "
" + i18n( "Description:" ) + "" + todo->richDescription() + "
" + i18np( "Reminder:", "Reminders:", reminderCount ) + "" + reminderStringList( todo ).join( "
" ) + "
" + i18np( "Category:", "Categories:", categoryCount ) + "" + displayViewFormatCategories( todo ) + "
" + i18n( "Priority:" ) + ""; tmpStr += QString::number( todo->priority() ); tmpStr += "
" + i18nc( "Completed: date", "Completed:" ) + ""; tmpStr += todo->completedStr(); } else { tmpStr += "" + i18n( "Percent Done:" ) + ""; tmpStr += i18n( "%1%", todo->percentComplete() ); } tmpStr += "
" + i18np( "Attachment:", "Attachments:", attachmentCount ) + "" + displayViewFormatAttachments( todo ) + "
"; tmpStr += "

" + displayViewFormatCreationDate( todo, spec ) + ""; return tmpStr; } static QString displayViewFormatJournal( const QString &calStr, Journal *journal, KDateTime::Spec spec ) { if ( !journal ) { return QString(); } QString tmpStr = displayViewFormatHeader( journal ); tmpStr += ""; tmpStr += ""; tmpStr += ""; if ( !calStr.isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; if ( !journal->description().isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } int categoryCount = journal->categories().count(); if ( categoryCount > 0 ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } tmpStr += "
" + i18n( "Calendar:" ) + "" + calStr + "
" + i18n( "Date:" ) + "" + dateToString( journal->dtStart(), false, spec ) + "
" + i18n( "Description:" ) + "" + journal->richDescription() + "
" + i18np( "Category:", "Categories:", categoryCount ) + "" + displayViewFormatCategories( journal ) + "
"; tmpStr += "

" + displayViewFormatCreationDate( journal, spec ) + ""; return tmpStr; } static QString displayViewFormatFreeBusy( const QString &calStr, FreeBusy *fb, KDateTime::Spec spec ) { Q_UNUSED( calStr ); if ( !fb ) { return QString(); } QString tmpStr( htmlAddTag( "h2", i18n( "Free/Busy information for %1", fb->organizer().fullName() ) ) ); tmpStr += htmlAddTag( "h4", i18n( "Busy times in date range %1 - %2:", dateToString( fb->dtStart(), true, spec ), dateToString( fb->dtEnd(), true, spec ) ) ); QList periods = fb->busyPeriods(); QString text = htmlAddTag( "em", htmlAddTag( "b", i18nc( "tag for busy periods list", "Busy:" ) ) ); QList::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 += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 ); dur %= 3600; } if ( dur >= 60 ) { cont += i18ncp( "minutes part duration", "1 minute ", "%1 minutes ", dur / 60 ); dur %= 60; } if ( dur > 0 ) { cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur ); } text += i18nc( "startDate for duration", "%1 for %2", dateTimeToString( per.start(), false, true, spec ), cont ); text += "
"; } else { if ( per.start().date() == per.end().date() ) { text += i18nc( "date, fromTime - toTime ", "%1, %2 - %3", dateToString( per.start(), true, spec ), timeToString( per.start(), true, spec ), timeToString( per.end(), true, spec ) ); } else { text += i18nc( "fromDateTime - toDateTime", "%1 - %2", dateTimeToString( per.start(), false, true, spec ), dateTimeToString( per.end(), false, true, spec ) ); } text += "
"; } } tmpStr += htmlAddTag( "p", text ); return tmpStr; } //@endcond //@cond PRIVATE class KCal::IncidenceFormatter::EventViewerVisitor : public IncidenceBase::Visitor { public: EventViewerVisitor() : mCalendar( 0 ), mSpec( KDateTime::Spec() ), mResult( "" ) {} bool act( Calendar *calendar, IncidenceBase *incidence, const QDate &date, KDateTime::Spec spec=KDateTime::Spec() ) { mCalendar = calendar; mSourceName.clear(); mDate = date; mSpec = spec; mResult = ""; return incidence->accept( *this ); } bool act( const QString &sourceName, IncidenceBase *incidence, const QDate &date, KDateTime::Spec spec=KDateTime::Spec() ) { mCalendar = 0; mSourceName = sourceName; mDate = date; mSpec = spec; mResult = ""; return incidence->accept( *this ); } QString result() const { return mResult; } protected: bool visit( Event *event ) { const QString calStr = mCalendar ? resourceString( mCalendar, event ) : mSourceName; mResult = displayViewFormatEvent( calStr, event, mDate, mSpec ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { const QString calStr = mCalendar ? resourceString( mCalendar, todo ) : mSourceName; mResult = displayViewFormatTodo( calStr, todo, mDate, mSpec ); return !mResult.isEmpty(); } bool visit( Journal *journal ) { const QString calStr = mCalendar ? resourceString( mCalendar, journal ) : mSourceName; mResult = displayViewFormatJournal( calStr, journal, mSpec ); return !mResult.isEmpty(); } bool visit( FreeBusy *fb ) { mResult = displayViewFormatFreeBusy( mSourceName, fb, mSpec ); return !mResult.isEmpty(); } protected: Calendar *mCalendar; QString mSourceName; QDate mDate; KDateTime::Spec mSpec; QString mResult; }; //@endcond QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence ) { return extensiveDisplayStr( 0, incidence, QDate(), KDateTime::Spec() ); } QString IncidenceFormatter::extensiveDisplayStr( IncidenceBase *incidence, KDateTime::Spec spec ) { if ( !incidence ) { return QString(); } EventViewerVisitor v; if ( v.act( 0, incidence, QDate(), spec ) ) { return v.result(); } else { return QString(); } } QString IncidenceFormatter::extensiveDisplayStr( Calendar *calendar, IncidenceBase *incidence, const QDate &date, KDateTime::Spec spec ) { if ( !incidence ) { return QString(); } EventViewerVisitor v; if ( v.act( calendar, incidence, date, spec ) ) { return v.result(); } else { return QString(); } } QString IncidenceFormatter::extensiveDisplayStr( const QString &sourceName, IncidenceBase *incidence, const QDate &date, KDateTime::Spec spec ) { if ( !incidence ) { return QString(); } EventViewerVisitor v; if ( v.act( sourceName, incidence, date, spec ) ) { return v.result(); } else { return QString(); } } /*********************************************************************** * Helper functions for the body part formatter of kmail (Invitations) ***********************************************************************/ //@cond PRIVATE static QString string2HTML( const QString &str ) { return Qt::convertFromPlainText( str, Qt::WhiteSpaceNormal ); } static QString cleanHtml( const QString &html ) { QRegExp rx( "]*>(.*)", Qt::CaseInsensitive ); rx.indexIn( html ); QString body = rx.cap( 1 ); return Qt::escape( body.remove( QRegExp( "<[^>]*>" ) ).trimmed() ); } static QString eventStartTimeStr( Event *event ) { QString tmp; if ( !event->allDay() ) { tmp = i18nc( "%1: Start Date, %2: Start Time", "%1 %2", dateToString( event->dtStart(), true, KSystemTimeZones::local() ), timeToString( event->dtStart(), true, KSystemTimeZones::local() ) ); } else { tmp = i18nc( "%1: Start Date", "%1 (all day)", dateToString( event->dtStart(), true, KSystemTimeZones::local() ) ); } return tmp; } static QString eventEndTimeStr( Event *event ) { QString tmp; if ( event->hasEndDate() && event->dtEnd().isValid() ) { if ( !event->allDay() ) { tmp = i18nc( "%1: End Date, %2: End Time", "%1 %2", dateToString( event->dtEnd(), true, KSystemTimeZones::local() ), timeToString( event->dtEnd(), true, KSystemTimeZones::local() ) ); } else { tmp = i18nc( "%1: End Date", "%1 (all day)", dateToString( event->dtEnd(), true, KSystemTimeZones::local() ) ); } } return tmp; } static QString invitationRow( const QString &cell1, const QString &cell2 ) { return "" + cell1 + "" + cell2 + "\n"; } static Attendee *findDelegatedFromMyAttendee( Incidence *incidence ) { // Return the first attendee that was delegated-from me Attendee *attendee = 0; if ( !incidence ) { return attendee; } KEMailSettings settings; QStringList profiles = settings.profiles(); for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) { settings.setProfile( *it ); QString delegatorName, delegatorEmail; Attendee::List attendees = incidence->attendees(); Attendee::List::ConstIterator it2; for ( it2 = attendees.constBegin(); it2 != attendees.constEnd(); ++it2 ) { Attendee *a = *it2; KPIMUtils::extractEmailAddressAndName( a->delegator(), delegatorEmail, delegatorName ); if ( settings.getSetting( KEMailSettings::EmailAddress ) == delegatorEmail ) { attendee = a; break; } } } return attendee; } static Attendee *findMyAttendee( Incidence *incidence ) { // Return the attendee for the incidence that is probably me Attendee *attendee = 0; if ( !incidence ) { return attendee; } KEMailSettings settings; QStringList profiles = settings.profiles(); for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) { settings.setProfile( *it ); Attendee::List attendees = incidence->attendees(); Attendee::List::ConstIterator it2; for ( it2 = attendees.constBegin(); it2 != attendees.constEnd(); ++it2 ) { Attendee *a = *it2; if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) { attendee = a; break; } } } return attendee; } static Attendee *findAttendee( Incidence *incidence, const QString &email ) { // Search for an attendee by email address Attendee *attendee = 0; if ( !incidence ) { return attendee; } Attendee::List attendees = incidence->attendees(); Attendee::List::ConstIterator it; for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { Attendee *a = *it; if ( email == a->email() ) { attendee = a; break; } } return attendee; } static bool rsvpRequested( Incidence *incidence ) { if ( !incidence ) { return false; } //use a heuristic to determine if a response is requested. bool rsvp = true; // better send superfluously than not at all Attendee::List attendees = incidence->attendees(); Attendee::List::ConstIterator it; for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { if ( it == attendees.constBegin() ) { rsvp = (*it)->RSVP(); // use what the first one has } else { if ( (*it)->RSVP() != rsvp ) { rsvp = true; // they differ, default break; } } } return rsvp; } static QString rsvpRequestedStr( bool rsvpRequested, const QString &role ) { if ( rsvpRequested ) { if ( role.isEmpty() ) { return i18n( "Your response is requested" ); } else { return i18n( "Your response as %1 is requested", role ); } } else { if ( role.isEmpty() ) { return i18n( "No response is necessary" ); } else { return i18n( "No response as %1 is necessary", role ); } } } static QString myStatusStr( Incidence *incidence ) { QString ret; Attendee *a = findMyAttendee( incidence ); if ( a && a->status() != Attendee::NeedsAction && a->status() != Attendee::Delegated ) { ret = i18n( "(Note: the Organizer preset your response to %1)", Attendee::statusName( a->status() ) ); } return ret; } static QString invitationPerson( 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() ) ) { #ifndef KDEPIM_NO_KRESOURCES KABC::AddressBook *add_book = KABC::StdAddressBook::self( true ); KABC::Addressee::List addressList = add_book->findByEmail( email ); if ( !addressList.isEmpty() ) { 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.clear(); } } #else uid.clear(); #endif } // 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 += htmlAddLink( "uid:" + uid, email ); } else { tmpString += htmlAddLink( "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() ); const QString iconPath = KIconLoader::global()->iconPath( "mail-message-new", KIconLoader::Small ); tmpString += htmlAddLink( mailto.url(), "" ); } tmpString += '\n'; return tmpString; } static QString invitationsDetailsIncidence( Incidence *incidence, bool noHtmlMode ) { // if description and comment -> use both // if description, but no comment -> use the desc as the comment (and no desc) // if comment, but no description -> use the comment and no description QString html; QString descr; QStringList comments; if ( incidence->comments().isEmpty() ) { if ( !incidence->description().isEmpty() ) { // use description as comments if ( !incidence->descriptionIsRich() ) { comments << string2HTML( incidence->description() ); } else { comments << incidence->richDescription(); if ( noHtmlMode ) { comments[0] = cleanHtml( comments[0] ); } comments[0] = htmlAddTag( "p", comments[0] ); } } //else desc and comments are empty } else { // non-empty comments foreach ( const QString &c, incidence->comments() ) { if ( !c.isEmpty() ) { // kcal doesn't know about richtext comments, so we need to guess if ( !Qt::mightBeRichText( c ) ) { comments << string2HTML( c ); } else { if ( noHtmlMode ) { comments << cleanHtml( cleanHtml( "" + c + "" ) ); } else { comments << c; } } } } if ( !incidence->description().isEmpty() ) { // use description too if ( !incidence->descriptionIsRich() ) { descr = string2HTML( incidence->description() ); } else { descr = incidence->richDescription(); if ( noHtmlMode ) { descr = cleanHtml( descr ); } descr = htmlAddTag( "p", descr ); } } } if( !descr.isEmpty() ) { html += "

"; html += ""; html += ""; html += ""; html += "
" + htmlAddTag( "u", i18n( "Description:" ) ) + "
" + descr + "
"; } if ( !comments.isEmpty() ) { html += "

"; html += ""; html += ""; html += ""; html += "
" + htmlAddTag( "u", i18n( "Comments:" ) ) + "
"; if ( comments.count() > 1 ) { html += "
    "; for ( int i=0; i < comments.count(); ++i ) { html += "
  • " + comments[i] + "
  • "; } html += "
"; } else { html += comments[0]; } html += "
"; } return html; } static QString invitationDetailsEvent( Event *event, bool noHtmlMode, KDateTime::Spec spec ) { // Invitation details are formatted into an HTML table if ( !event ) { return QString(); } QString sSummary = i18n( "Summary unspecified" ); if ( !event->summary().isEmpty() ) { if ( !event->summaryIsRich() ) { sSummary = Qt::escape( event->summary() ); } else { sSummary = event->richSummary(); if ( noHtmlMode ) { sSummary = cleanHtml( sSummary ); } } } QString sLocation = i18nc( "event location", "Location unspecified" ); if ( !event->location().isEmpty() ) { if ( !event->locationIsRich() ) { sLocation = Qt::escape( event->location() ); } else { sLocation = event->richLocation(); if ( noHtmlMode ) { sLocation = cleanHtml( sLocation ); } } } QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" ); QString html = QString( "

\n" ).arg( dir ); html += ""; // Invitation summary & location rows html += invitationRow( i18n( "What:" ), sSummary ); html += invitationRow( i18n( "Where:" ), sLocation ); // If a 1 day event if ( event->dtStart().date() == event->dtEnd().date() ) { html += invitationRow( i18n( "Date:" ), dateToString( event->dtStart(), false, spec ) ); if ( !event->allDay() ) { html += invitationRow( i18n( "Time:" ), timeToString( event->dtStart(), true, spec ) + " - " + timeToString( event->dtEnd(), true, spec ) ); } } else { html += invitationRow( i18nc( "starting date", "From:" ), dateToString( event->dtStart(), false, spec ) ); if ( !event->allDay() ) { html += invitationRow( i18nc( "starting time", "At:" ), timeToString( event->dtStart(), true, spec ) ); } if ( event->hasEndDate() ) { html += invitationRow( i18nc( "ending date", "To:" ), dateToString( event->dtEnd(), false, spec ) ); if ( !event->allDay() ) { html += invitationRow( i18nc( "ending time", "At:" ), timeToString( event->dtEnd(), true, spec ) ); } } else { html += invitationRow( i18nc( "ending date", "To:" ), i18n( "no end date specified" ) ); } } // Invitation Duration Row QString durStr = durationString( event ); if ( !durStr.isEmpty() ) { html += invitationRow( i18n( "Duration:" ), durStr ); } if ( event->recurs() ) { html += invitationRow( i18n( "Recurrence:" ), recurrenceString( event ) ); } html += "
\n"; html += invitationsDetailsIncidence( event, noHtmlMode ); return html; } static QString invitationDetailsTodo( Todo *todo, bool noHtmlMode, KDateTime::Spec spec ) { // To-do details are formatted into an HTML table if ( !todo ) { return QString(); } QString sSummary = i18n( "Summary unspecified" ); if ( !todo->summary().isEmpty() ) { if ( !todo->summaryIsRich() ) { sSummary = Qt::escape( todo->summary() ); } else { sSummary = todo->richSummary(); if ( noHtmlMode ) { sSummary = cleanHtml( sSummary ); } } } QString sLocation = i18nc( "todo location", "Location unspecified" ); if ( !todo->location().isEmpty() ) { if ( !todo->locationIsRich() ) { sLocation = Qt::escape( todo->location() ); } else { sLocation = todo->richLocation(); if ( noHtmlMode ) { sLocation = cleanHtml( sLocation ); } } } QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" ); QString html = QString( "
\n" ).arg( dir ); html += ""; // Invitation summary & location rows html += invitationRow( i18n( "What:" ), sSummary ); html += invitationRow( i18n( "Where:" ), sLocation ); if ( todo->hasStartDate() && todo->dtStart().isValid() ) { html += invitationRow( i18n( "Start Date:" ), dateToString( todo->dtStart(), false, spec ) ); if ( !todo->allDay() ) { html += invitationRow( i18n( "Start Time:" ), timeToString( todo->dtStart(), false, spec ) ); } } if ( todo->hasDueDate() && todo->dtDue().isValid() ) { html += invitationRow( i18n( "Due Date:" ), dateToString( todo->dtDue(), false, spec ) ); if ( !todo->allDay() ) { html += invitationRow( i18n( "Due Time:" ), timeToString( todo->dtDue(), false, spec ) ); } } else { html += invitationRow( i18n( "Due Date:" ), i18nc( "no to-do due date", "None" ) ); } html += "
\n"; html += invitationsDetailsIncidence( todo, noHtmlMode ); return html; } static QString invitationDetailsJournal( Journal *journal, bool noHtmlMode, KDateTime::Spec spec ) { if ( !journal ) { return QString(); } QString sSummary = i18n( "Summary unspecified" ); QString sDescr = i18n( "Description unspecified" ); if ( ! journal->summary().isEmpty() ) { sSummary = journal->richSummary(); if ( noHtmlMode ) { sSummary = cleanHtml( sSummary ); } } if ( ! journal->description().isEmpty() ) { sDescr = journal->richDescription(); if ( noHtmlMode ) { sDescr = cleanHtml( sDescr ); } } QString html( "\n" ); html += invitationRow( i18n( "Summary:" ), sSummary ); html += invitationRow( i18n( "Date:" ), dateToString( journal->dtStart(), false, spec ) ); html += invitationRow( i18n( "Description:" ), sDescr ); html += "
\n"; html += invitationsDetailsIncidence( journal, noHtmlMode ); return html; } static QString invitationDetailsFreeBusy( FreeBusy *fb, bool noHtmlMode, KDateTime::Spec spec ) { Q_UNUSED( noHtmlMode ); if ( !fb ) { return QString(); } QString html( "\n" ); html += invitationRow( i18n( "Person:" ), fb->organizer().fullName() ); html += invitationRow( i18n( "Start date:" ), dateToString( fb->dtStart(), true, spec ) ); html += invitationRow( i18n( "End date:" ), dateToString( fb->dtEnd(), true, spec ) ); html += "\n"; html += "\n"; QList periods = fb->busyPeriods(); QList::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 += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 ); dur %= 3600; } if ( dur >= 60 ) { cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 ); dur %= 60; } if ( dur > 0 ) { cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur ); } html += invitationRow( QString(), i18nc( "startDate for duration", "%1 for %2", KGlobal::locale()->formatDateTime( per.start().dateTime(), KLocale::LongDate ), cont ) ); } else { QString cont; if ( per.start().date() == per.end().date() ) { cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3", KGlobal::locale()->formatDate( per.start().date() ), KGlobal::locale()->formatTime( per.start().time() ), KGlobal::locale()->formatTime( per.end().time() ) ); } else { cont = i18nc( "fromDateTime - toDateTime", "%1 - %2", KGlobal::locale()->formatDateTime( per.start().dateTime(), KLocale::LongDate ), KGlobal::locale()->formatDateTime( per.end().dateTime(), KLocale::LongDate ) ); } html += invitationRow( QString(), cont ); } } html += "

Busy periods given in this free/busy object:
\n"; return html; } static bool replyMeansCounter( Incidence */*incidence*/ ) { return false; /** see kolab/issue 3665 for an example of when we might use this for something bool status = false; if ( incidence ) { // put code here that looks at the incidence and determines that // the reply is meant to be a counter proposal. We think this happens // with Outlook counter proposals, but we aren't sure how yet. if ( condition ) { status = true; } } return status; */ } static QString invitationHeaderEvent( Event *event, Incidence *existingIncidence, ScheduleMessage *msg, const QString &sender ) { if ( !msg || !event ) { return QString(); } switch ( msg->method() ) { case iTIPPublish: return i18n( "This invitation has been published" ); case iTIPRequest: if ( existingIncidence && event->revision() > 0 ) { return i18n( "This invitation has been updated by the organizer %1", event->organizer().fullName() ); } if ( iamOrganizer( event ) ) { return i18n( "I created this invitation" ); } else { if ( senderIsOrganizer( event, sender ) ) { if ( !event->organizer().fullName().isEmpty() ) { return i18n( "You received an invitation from %1", event->organizer().fullName() ); } else { return i18n( "You received an invitation" ); } } else { if ( !event->organizer().fullName().isEmpty() ) { return i18n( "You received an invitation from %1 as a representative of %2", sender, event->organizer().fullName() ); } else { return i18n( "You received an invitation from %1 as the organizer's representative", sender ); } } } case iTIPRefresh: return i18n( "This invitation was refreshed" ); case iTIPCancel: return i18n( "This invitation has been canceled" ); case iTIPAdd: return i18n( "Addition to the invitation" ); case iTIPReply: { if ( replyMeansCounter( event ) ) { return i18n( "%1 makes this counter proposal", firstAttendeeName( event, i18n( "Sender" ) ) ); } Attendee::List attendees = event->attendees(); if( attendees.count() == 0 ) { kDebug() << "No attendees in the iCal reply!"; return QString(); } if ( attendees.count() != 1 ) { kDebug() << "Warning: attendeecount in the reply should be 1" << "but is" << attendees.count(); } QString attendeeName = firstAttendeeName( event, i18n( "Sender" ) ); QString delegatorName, dummy; Attendee *attendee = *attendees.begin(); KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName ); if ( delegatorName.isEmpty() ) { delegatorName = attendee->delegator(); } switch( attendee->status() ) { case Attendee::NeedsAction: return i18n( "%1 indicates this invitation still needs some action", attendeeName ); case Attendee::Accepted: if ( event->revision() > 0 ) { if ( !sender.isEmpty() ) { return i18n( "This invitation has been updated by attendee %1", sender ); } else { return i18n( "This invitation has been updated by an attendee" ); } } else { if ( delegatorName.isEmpty() ) { return i18n( "%1 accepts this invitation", attendeeName ); } else { return i18n( "%1 accepts this invitation on behalf of %2", attendeeName, delegatorName ); } } case Attendee::Tentative: if ( delegatorName.isEmpty() ) { return i18n( "%1 tentatively accepts this invitation", attendeeName ); } else { return i18n( "%1 tentatively accepts this invitation on behalf of %2", attendeeName, delegatorName ); } case Attendee::Declined: if ( delegatorName.isEmpty() ) { return i18n( "%1 declines this invitation", attendeeName ); } else { return i18n( "%1 declines this invitation on behalf of %2", attendeeName, delegatorName ); } case Attendee::Delegated: { QString delegate, dummy; KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate ); if ( delegate.isEmpty() ) { delegate = attendee->delegate(); } if ( !delegate.isEmpty() ) { return i18n( "%1 has delegated this invitation to %2", attendeeName, delegate ); } else { return i18n( "%1 has delegated this invitation", attendeeName ); } } case Attendee::Completed: return i18n( "This invitation is now completed" ); case Attendee::InProcess: return i18n( "%1 is still processing the invitation", attendeeName ); case Attendee::None: return i18n( "Unknown response to this invitation" ); } break; } case iTIPCounter: return i18n( "%1 makes this counter proposal", firstAttendeeName( event, i18n( "Sender" ) ) ); case iTIPDeclineCounter: return i18n( "%1 declines the counter proposal", firstAttendeeName( event, i18n( "Sender" ) ) ); case iTIPNoMethod: return i18n( "Error: Event iTIP message with unknown method" ); } kError() << "encountered an iTIP method that we do not support"; return QString(); } static QString invitationHeaderTodo( Todo *todo, Incidence *existingIncidence, ScheduleMessage *msg, const QString &sender ) { if ( !msg || !todo ) { return QString(); } switch ( msg->method() ) { case iTIPPublish: return i18n( "This to-do has been published" ); case iTIPRequest: if ( existingIncidence && todo->revision() > 0 ) { return i18n( "This to-do has been updated by the organizer %1", todo->organizer().fullName() ); } else { if ( iamOrganizer( todo ) ) { return i18n( "I created this to-do" ); } else { if ( senderIsOrganizer( todo, sender ) ) { if ( !todo->organizer().fullName().isEmpty() ) { return i18n( "You have been assigned this to-do by %1", todo->organizer().fullName() ); } else { return i18n( "You have been assigned this to-do" ); } } else { if ( !todo->organizer().fullName().isEmpty() ) { return i18n( "You have been assigned this to-do by %1 as a representative of %2", sender, todo->organizer().fullName() ); } else { return i18n( "You have been assigned this to-do by %1 as the " "organizer's representative", sender ); } } } } case iTIPRefresh: return i18n( "This to-do was refreshed" ); case iTIPCancel: return i18n( "This to-do was canceled" ); case iTIPAdd: return i18n( "Addition to the to-do" ); case iTIPReply: { if ( replyMeansCounter( todo ) ) { return i18n( "%1 makes this counter proposal", firstAttendeeName( todo, i18n( "Sender" ) ) ); } Attendee::List attendees = todo->attendees(); if ( attendees.count() == 0 ) { kDebug() << "No attendees in the iCal reply!"; return QString(); } if ( attendees.count() != 1 ) { kDebug() << "Warning: attendeecount in the reply should be 1" << "but is" << attendees.count(); } QString attendeeName = firstAttendeeName( todo, i18n( "Sender" ) ); QString delegatorName, dummy; Attendee *attendee = *attendees.begin(); KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegatorName ); if ( delegatorName.isEmpty() ) { delegatorName = attendee->delegator(); } switch( attendee->status() ) { case Attendee::NeedsAction: return i18n( "%1 indicates this to-do assignment still needs some action", attendeeName ); case Attendee::Accepted: if ( todo->revision() > 0 ) { if ( !sender.isEmpty() ) { if ( todo->isCompleted() ) { return i18n( "This to-do has been completed by assignee %1", sender ); } else { return i18n( "This to-do has been updated by assignee %1", sender ); } } else { if ( todo->isCompleted() ) { return i18n( "This to-do has been completed by an assignee" ); } else { return i18n( "This to-do has been updated by an assignee" ); } } } else { if ( delegatorName.isEmpty() ) { return i18n( "%1 accepts this to-do", attendeeName ); } else { return i18n( "%1 accepts this to-do on behalf of %2", attendeeName, delegatorName ); } } case Attendee::Tentative: if ( delegatorName.isEmpty() ) { return i18n( "%1 tentatively accepts this to-do", attendeeName ); } else { return i18n( "%1 tentatively accepts this to-do on behalf of %2", attendeeName, delegatorName ); } case Attendee::Declined: if ( delegatorName.isEmpty() ) { return i18n( "%1 declines this to-do", attendeeName ); } else { return i18n( "%1 declines this to-do on behalf of %2", attendeeName, delegatorName ); } case Attendee::Delegated: { QString delegate, dummy; KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate ); if ( delegate.isEmpty() ) { delegate = attendee->delegate(); } if ( !delegate.isEmpty() ) { return i18n( "%1 has delegated this to-do to %2", attendeeName, delegate ); } else { return i18n( "%1 has delegated this to-do", attendeeName ); } } case Attendee::Completed: return i18n( "The request for this to-do is now completed" ); case Attendee::InProcess: return i18n( "%1 is still processing the to-do", attendeeName ); case Attendee::None: return i18n( "Unknown response to this to-do" ); } break; } case iTIPCounter: return i18n( "%1 makes this counter proposal", firstAttendeeName( todo, i18n( "Sender" ) ) ); case iTIPDeclineCounter: return i18n( "%1 declines the counter proposal", firstAttendeeName( todo, i18n( "Sender" ) ) ); case iTIPNoMethod: return i18n( "Error: To-do iTIP message with unknown method" ); } kError() << "encountered an iTIP method that we do not support"; return QString(); } static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg ) { if ( !msg || !journal ) { return QString(); } switch ( msg->method() ) { case iTIPPublish: return i18n( "This journal has been published" ); case iTIPRequest: return i18n( "You have been assigned this journal" ); case iTIPRefresh: return i18n( "This journal was refreshed" ); case iTIPCancel: return i18n( "This journal was canceled" ); case iTIPAdd: return i18n( "Addition to the journal" ); case iTIPReply: { if ( replyMeansCounter( journal ) ) { return i18n( "Sender makes this counter proposal" ); } Attendee::List attendees = journal->attendees(); if ( attendees.count() == 0 ) { kDebug() << "No attendees in the iCal reply!"; return QString(); } if( attendees.count() != 1 ) { kDebug() << "Warning: attendeecount in the reply should be 1 " << "but is " << attendees.count(); } 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" ); case Attendee::None: return i18n( "Unknown response to this journal" ); } break; } case iTIPCounter: return i18n( "Sender makes this counter proposal" ); case iTIPDeclineCounter: return i18n( "Sender declines the counter proposal" ); case iTIPNoMethod: return i18n( "Error: Journal iTIP message with unknown method" ); } kError() << "encountered an iTIP method that we do not support"; return QString(); } static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg ) { if ( !msg || !fb ) { return QString(); } switch ( msg->method() ) { case iTIPPublish: return i18n( "This free/busy list has been published" ); case iTIPRequest: return i18n( "The free/busy list has been requested" ); case iTIPRefresh: return i18n( "This free/busy list was refreshed" ); case iTIPCancel: return i18n( "This free/busy list was canceled" ); case iTIPAdd: return i18n( "Addition to the free/busy list" ); case iTIPReply: return i18n( "Reply to the free/busy list" ); case iTIPCounter: return i18n( "Sender makes this counter proposal" ); case iTIPDeclineCounter: return i18n( "Sender declines the counter proposal" ); case iTIPNoMethod: return i18n( "Error: Free/Busy iTIP message with unknown method" ); } kError() << "encountered an iTIP method that we do not support"; return QString(); } //@endcond static QString invitationAttendees( Incidence *incidence ) { QString tmpStr; if ( !incidence ) { return tmpStr; } tmpStr += i18n( "Invitation List" ); int count=0; Attendee::List attendees = incidence->attendees(); if ( !attendees.isEmpty() ) { Attendee::List::ConstIterator it; for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { Attendee *a = *it; if ( !iamAttendee( a ) ) { count++; if ( count == 1 ) { tmpStr += ""; } tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } } } if ( count ) { tmpStr += "
"; tmpStr += invitationPerson( a->email(), a->name(), QString() ); if ( !a->delegator().isEmpty() ) { tmpStr += i18n( " (delegated by %1)", a->delegator() ); } if ( !a->delegate().isEmpty() ) { tmpStr += i18n( " (delegated to %1)", a->delegate() ); } tmpStr += "" + a->statusStr() + "
"; } else { tmpStr += " " + i18nc( "no attendees", "None" ) + ""; } return tmpStr; } static QString invitationAttachments( InvitationFormatterHelper *helper, Incidence *incidence ) { QString tmpStr; if ( !incidence ) { return tmpStr; } Attachment::List attachments = incidence->attachments(); if ( !attachments.isEmpty() ) { tmpStr += i18n( "Attached Documents:" ) + "
    "; Attachment::List::ConstIterator it; for ( it = attachments.constBegin(); it != attachments.constEnd(); ++it ) { Attachment *a = *it; tmpStr += "
  1. "; // Attachment icon KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() ); const QString iconStr = ( mimeType ? mimeType->iconName( a->uri() ) : QString( "application-octet-stream" ) ); const QString iconPath = KIconLoader::global()->iconPath( iconStr, KIconLoader::Small ); if ( !iconPath.isEmpty() ) { tmpStr += ""; } tmpStr += helper->makeLink( "ATTACH:" + a->label(), a->label() ); tmpStr += "
  2. "; } tmpStr += "
"; } return tmpStr; } //@cond PRIVATE class KCal::IncidenceFormatter::ScheduleMessageVisitor : public IncidenceBase::Visitor { public: ScheduleMessageVisitor() : mExistingIncidence( 0 ), mMessage( 0 ) { mResult = ""; } bool act( IncidenceBase *incidence, Incidence *existingIncidence, ScheduleMessage *msg, const QString &sender ) { mExistingIncidence = existingIncidence; mMessage = msg; mSender = sender; return incidence->accept( *this ); } QString result() const { return mResult; } protected: QString mResult; Incidence *mExistingIncidence; ScheduleMessage *mMessage; QString mSender; }; class KCal::IncidenceFormatter::InvitationHeaderVisitor : public IncidenceFormatter::ScheduleMessageVisitor { protected: bool visit( Event *event ) { mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender ); 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 KCal::IncidenceFormatter::InvitationBodyVisitor : public IncidenceFormatter::ScheduleMessageVisitor { public: InvitationBodyVisitor( bool noHtmlMode, KDateTime::Spec spec ) : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ), mSpec( spec ) {} protected: bool visit( Event *event ) { mResult = invitationDetailsEvent( event, mNoHtmlMode, mSpec ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { mResult = invitationDetailsTodo( todo, mNoHtmlMode, mSpec ); return !mResult.isEmpty(); } bool visit( Journal *journal ) { mResult = invitationDetailsJournal( journal, mNoHtmlMode, mSpec ); return !mResult.isEmpty(); } bool visit( FreeBusy *fb ) { mResult = invitationDetailsFreeBusy( fb, mNoHtmlMode, mSpec ); return !mResult.isEmpty(); } private: bool mNoHtmlMode; KDateTime::Spec mSpec; }; //@endcond QString InvitationFormatterHelper::generateLinkURL( const QString &id ) { return id; } //@cond PRIVATE class IncidenceFormatter::IncidenceCompareVisitor : public IncidenceBase::Visitor { public: IncidenceCompareVisitor() : mExistingIncidence( 0 ) {} bool act( IncidenceBase *incidence, Incidence *existingIncidence, iTIPMethod method ) { if ( !existingIncidence ) { return false; } Incidence *inc = dynamic_cast( incidence ); if ( !inc || !existingIncidence || inc->revision() <= existingIncidence->revision() ) { return false; } mExistingIncidence = existingIncidence; mMethod = method; 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, mMethod ); return !mChanges.isEmpty(); } bool visit( Todo *todo ) { compareTodos( todo, dynamic_cast( mExistingIncidence ) ); compareIncidences( todo, mExistingIncidence, mMethod ); return !mChanges.isEmpty(); } bool visit( Journal *journal ) { compareIncidences( journal, mExistingIncidence, mMethod ); 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->allDay() != newEvent->allDay() ) { mChanges += i18n( "The invitation starting time has been changed from %1 to %2", eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) ); } if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->allDay() != newEvent->allDay() ) { mChanges += i18n( "The invitation ending time has been changed from %1 to %2", eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) ); } } void compareTodos( Todo *newTodo, Todo *oldTodo ) { if ( !oldTodo || !newTodo ) { return; } if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) { mChanges += i18n( "The to-do has been completed" ); } if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) { mChanges += i18n( "The to-do is no longer completed" ); } if ( oldTodo->percentComplete() != newTodo->percentComplete() ) { const QString oldPer = i18n( "%1%", oldTodo->percentComplete() ); const QString newPer = i18n( "%1%", newTodo->percentComplete() ); mChanges += i18n( "The task completed percentage has changed from %1 to %2", oldPer, newPer ); } if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) { mChanges += i18n( "A to-do starting time has been added" ); } if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) { mChanges += i18n( "The to-do starting time has been removed" ); } if ( oldTodo->hasStartDate() && newTodo->hasStartDate() && oldTodo->dtStart() != newTodo->dtStart() ) { mChanges += i18n( "The to-do starting time has been changed from %1 to %2", dateTimeToString( oldTodo->dtStart(), oldTodo->allDay(), false ), dateTimeToString( newTodo->dtStart(), newTodo->allDay(), false ) ); } if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) { mChanges += i18n( "A to-do due time has been added" ); } if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) { mChanges += i18n( "The to-do due time has been removed" ); } if ( oldTodo->hasDueDate() && newTodo->hasDueDate() && oldTodo->dtDue() != newTodo->dtDue() ) { mChanges += i18n( "The to-do due time has been changed from %1 to %2", dateTimeToString( oldTodo->dtDue(), oldTodo->allDay(), false ), dateTimeToString( newTodo->dtDue(), newTodo->allDay(), false ) ); } } void compareIncidences( Incidence *newInc, Incidence *oldInc, iTIPMethod method ) { if ( !oldInc || !newInc ) { return; } if ( oldInc->summary() != newInc->summary() ) { mChanges += i18n( "The summary has been changed to: \"%1\"", newInc->richSummary() ); } if ( oldInc->location() != newInc->location() ) { mChanges += i18nc( "event/todo location", "The location has been changed to: \"%1\"", newInc->richLocation() ); } if ( oldInc->description() != newInc->description() ) { mChanges += i18n( "The description has been changed to: \"%1\"", newInc->richDescription() ); } 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", (*it)->fullName() ); } else { if ( oldAtt->status() != (*it)->status() ) { mChanges += i18n( "The status of attendee %1 has been changed to: %2", (*it)->fullName(), (*it)->statusStr() ); } } } if ( method == iTIPRequest ) { for ( Attendee::List::ConstIterator it = oldAttendees.constBegin(); it != oldAttendees.constEnd(); ++it ) { if ( (*it)->email() != oldInc->organizer().email() ) { Attendee *newAtt = newInc->attendeeByMail( (*it)->email() ); if ( !newAtt ) { mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() ); } } } } } private: Incidence *mExistingIncidence; iTIPMethod mMethod; QStringList mChanges; }; //@endcond QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text ) { if ( !id.startsWith( QLatin1String( "ATTACH:" ) ) ) { QString res = QString( "%2" ). arg( generateLinkURL( id ), text ); return res; } else { // draw the attachment links in non-bold face QString res = QString( "%2" ). arg( generateLinkURL( id ), 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 ) { #ifndef KDEPIM_NO_KRESOURCES 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; } #endif return true; } // The open & close table cell tags for the invitation buttons static QString tdOpen = ""; static QString tdClose = ""; static QString responseButtons( Incidence *inc, bool rsvpReq, bool rsvpRec, InvitationFormatterHelper *helper ) { QString html; if ( !helper ) { return html; } if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) { // Record only html += tdOpen; html += helper->makeLink( "record", i18n( "[Record]" ) ); html += tdClose; // Move to trash html += tdOpen; html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) ); html += tdClose; } else { // Accept html += tdOpen; html += helper->makeLink( "accept", i18nc( "accept invitation", "Accept" ) ); html += tdClose; // Tentative html += tdOpen; html += helper->makeLink( "accept_conditionally", i18nc( "Accept invitation conditionally", "Accept cond." ) ); html += tdClose; // Counter proposal html += tdOpen; html += helper->makeLink( "counter", i18nc( "invitation counter proposal", "Counter proposal" ) ); html += tdClose; // Decline html += tdOpen; html += helper->makeLink( "decline", i18nc( "decline invitation", "Decline" ) ); html += tdClose; } if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) { // Delegate html += tdOpen; html += helper->makeLink( "delegate", i18nc( "delegate inviation to another", "Delegate" ) ); html += tdClose; // Forward html += tdOpen; html += helper->makeLink( "forward", i18nc( "forward request to another", "Forward" ) ); html += tdClose; // Check calendar if ( inc && inc->type() == "Event" ) { html += tdOpen; html += helper->makeLink( "check_calendar", i18nc( "look for scheduling conflicts", "Check my calendar" ) ); html += tdClose; } } return html; } static QString counterButtons( Incidence *incidence, InvitationFormatterHelper *helper ) { QString html; if ( !helper ) { return html; } // Accept proposal html += tdOpen; html += helper->makeLink( "accept_counter", i18n( "[Accept]" ) ); html += tdClose; // Decline proposal html += tdOpen; html += helper->makeLink( "decline_counter", i18n( "[Decline]" ) ); html += tdClose; // Check calendar if ( incidence && incidence->type() == "Event" ) { html += tdOpen; html += helper->makeLink( "check_calendar", i18n( "[Check my calendar] " ) ); html += tdClose; } return html; } Calendar *InvitationFormatterHelper::calendar() const { return 0; } static QString formatICalInvitationHelper( QString invitation, Calendar *mCalendar, InvitationFormatterHelper *helper, bool noHtmlMode, KDateTime::Spec spec, const QString &sender ) { if ( invitation.isEmpty() ) { return QString(); } 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 ) { kDebug() << "Failed to parse the scheduling message"; Q_ASSERT( format.exception() ); kDebug() << format.exception()->message(); return QString(); } IncidenceBase *incBase = msg->event(); incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() ); // Determine if this incidence is in my calendar (and owned by me) Incidence *existingIncidence = 0; if ( incBase && 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; html += "
    "; IncidenceFormatter::InvitationHeaderVisitor headerVisitor; // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled if ( !headerVisitor.act( incBase, existingIncidence, msg, sender ) ) { return QString(); } html += htmlAddTag( "h3", headerVisitor.result() ); IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode, spec ); if ( !bodyVisitor.act( incBase, existingIncidence, msg, sender ) ) { return QString(); } html += bodyVisitor.result(); if ( msg->method() == iTIPRequest ) { IncidenceFormatter::IncidenceCompareVisitor compareVisitor; if ( compareVisitor.act( incBase, existingIncidence, msg->method() ) ) { html += "

    "; html += i18n( "The following changes have been made by the organizer:" ); html += "

    "; html += compareVisitor.result(); } } if ( msg->method() == iTIPReply ) { IncidenceCompareVisitor compareVisitor; if ( compareVisitor.act( incBase, existingIncidence, msg->method() ) ) { html += "

    "; if ( !sender.isEmpty() ) { html += i18n( "The following changes have been made by %1:", sender ); } else { html += i18n( "The following changes have been made by an attendee:" ); } html += "

    "; html += compareVisitor.result(); } } Incidence *inc = dynamic_cast( incBase ); // determine if I am the organizer for this invitation bool myInc = iamOrganizer( inc ); // determine if the invitation response has already been recorded bool rsvpRec = false; Attendee *ea = 0; if ( !myInc ) { Incidence *rsvpIncidence = existingIncidence; if ( !rsvpIncidence && inc && inc->revision() > 0 ) { rsvpIncidence = inc; } if ( rsvpIncidence ) { ea = findMyAttendee( rsvpIncidence ); } if ( ea && ( ea->status() == Attendee::Accepted || ea->status() == Attendee::Declined || ea->status() == Attendee::Tentative ) ) { rsvpRec = true; } } // determine invitation role QString role; bool isDelegated = false; Attendee *a = findMyAttendee( inc ); if ( !a && inc ) { if ( !inc->attendees().isEmpty() ) { a = inc->attendees().first(); } } if ( a ) { isDelegated = ( a->status() == Attendee::Delegated ); role = Attendee::roleName( a->role() ); } // Print if RSVP needed, not-needed, or response already recorded bool rsvpReq = rsvpRequested( inc ); if ( !myInc && a ) { html += "
    "; html += ""; if ( rsvpRec && inc ) { if ( inc->revision() == 0 ) { html += i18n( "Your %1 response has already been recorded", ea->statusStr() ); } else { html += i18n( "Your status for this invitation is %1", ea->statusStr() ); } rsvpReq = false; } else if ( msg->method() == iTIPCancel ) { html += i18n( "This invitation was declined" ); } else if ( msg->method() == iTIPAdd ) { html += i18n( "This invitation was accepted" ); } else { if ( !isDelegated ) { html += rsvpRequestedStr( rsvpReq, role ); } else { html += i18n( "Awaiting delegation response" ); } } html += ""; } // Print if the organizer gave you a preset status if ( !myInc ) { if ( inc && inc->revision() == 0 ) { QString statStr = myStatusStr( inc ); if ( !statStr.isEmpty() ) { html += "
    "; html += ""; html += statStr; html += ""; } } } // Add groupware links html += "

    "; html += ""; switch ( msg->method() ) { case iTIPPublish: case iTIPRequest: case iTIPRefresh: case iTIPAdd: { if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) { if ( inc->type() == "Todo" ) { html += helper->makeLink( "reply", i18n( "[Record invitation in my to-do list]" ) ); } else { html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) ); } } if ( !myInc && a ) { html += responseButtons( inc, rsvpReq, rsvpRec, helper ); } break; } case iTIPCancel: // Remove invitation if ( inc ) { html += tdOpen; if ( inc->type() == "Todo" ) { html += helper->makeLink( "cancel", i18n( "Remove invitation from my to-do list" ) ); } else { html += helper->makeLink( "cancel", i18n( "Remove invitation from my calendar" ) ); } html += tdClose; } break; case iTIPReply: { // Record invitation response Attendee *a = 0; Attendee *ea = 0; if ( inc ) { // First, determine if this reply is really a counter in disguise. if ( replyMeansCounter( inc ) ) { html += "" + counterButtons( inc, helper ) + ""; break; } // Next, maybe this is a declined reply that was delegated from me? // find first attendee who is delegated-from me // look a their PARTSTAT response, if the response is declined, // then we need to start over which means putting all the action // buttons and NOT putting on the [Record response..] button a = findDelegatedFromMyAttendee( inc ); if ( a ) { if ( a->status() != Attendee::Accepted || a->status() != Attendee::Tentative ) { html += responseButtons( inc, rsvpReq, rsvpRec, helper ); break; } } // Finally, simply allow a Record of the reply if ( !inc->attendees().isEmpty() ) { a = inc->attendees().first(); } if ( a && helper->calendar() ) { ea = findAttendee( existingIncidence, a->email() ); } } if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) { html += tdOpen; html += htmlAddTag( "i", i18n( "The response has already been recorded" ) ); html += tdClose; } else { if ( inc ) { if ( inc->type() == "Todo" ) { html += helper->makeLink( "reply", i18n( "[Record response in my to-do list]" ) ); } else { html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) ); } } } break; } case iTIPCounter: // Counter proposal html += counterButtons( inc, helper ); break; case iTIPDeclineCounter: case iTIPNoMethod: break; } // close the groupware table html += "
    "; // Add the attendee list if I am the organizer if ( myInc && helper->calendar() ) { html += invitationAttendees( helper->calendar()->incidence( inc->uid() ) ); } // close the top-level html += "

    "; // Add the attachment list html += invitationAttachments( helper, inc ); return html; } //@endcond QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *calendar, InvitationFormatterHelper *helper ) { return formatICalInvitationHelper( invitation, calendar, helper, false, KSystemTimeZones::local(), QString() ); } QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation, Calendar *calendar, InvitationFormatterHelper *helper ) { return formatICalInvitationHelper( invitation, calendar, helper, true, KSystemTimeZones::local(), QString() ); } QString IncidenceFormatter::formatICalInvitationNoHtml( const QString &invitation, Calendar *calendar, InvitationFormatterHelper *helper, const QString &sender ) { return formatICalInvitationHelper( invitation, calendar, helper, true, KSystemTimeZones::local(), sender ); } /******************************************************************* * Helper functions for the Incidence tooltips *******************************************************************/ //@cond PRIVATE class KCal::IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor { public: ToolTipVisitor() : mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {} bool act( Calendar *calendar, IncidenceBase *incidence, const QDate &date=QDate(), bool richText=true, KDateTime::Spec spec=KDateTime::Spec() ) { mCalendar = calendar; mLocation.clear(); mDate = date; mRichText = richText; mSpec = spec; mResult = ""; return incidence ? incidence->accept( *this ) : false; } bool act( const QString &location, IncidenceBase *incidence, const QDate &date=QDate(), bool richText=true, KDateTime::Spec spec=KDateTime::Spec() ) { mCalendar = 0; mLocation = location; mDate = date; mRichText = richText; mSpec = spec; 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, const QDate &date ); QString dateRangeText( Todo *todo, const QDate &date ); QString dateRangeText( Journal *journal ); QString dateRangeText( FreeBusy *fb ); QString generateToolTip( Incidence *incidence, QString dtRangeText ); protected: Calendar *mCalendar; QString mLocation; QDate mDate; bool mRichText; KDateTime::Spec mSpec; QString mResult; }; QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const QDate &date ) { //FIXME: support mRichText==false QString ret; QString tmp; KDateTime startDt = event->dtStart(); KDateTime endDt = event->dtEnd(); if ( event->recurs() ) { if ( date.isValid() ) { KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() ); int diffDays = startDt.daysTo( kdt ); kdt = kdt.addSecs( -1 ); startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() ); if ( event->hasEndDate() ) { endDt = endDt.addDays( diffDays ); if ( startDt > endDt ) { startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() ); endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) ); } } } } if ( event->isMultiDay() ) { tmp = dateToString( startDt, true, mSpec ); ret += "
    " + i18nc( "Event start", "From: %1", tmp ); tmp = dateToString( endDt, true, mSpec ); ret += "
    " + i18nc( "Event end","To: %1", tmp ); } else { ret += "
    " + i18n( "Date: %1", dateToString( startDt, false, mSpec ) ); if ( !event->allDay() ) { const QString dtStartTime = timeToString( startDt, true, mSpec ); const QString dtEndTime = timeToString( endDt, true, mSpec ); if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00' tmp = "
    " + i18nc( "time for event", "Time: %1", dtStartTime ); } else { tmp = "
    " + i18nc( "time range for event", "Time: %1 - %2", dtStartTime, dtEndTime ); } ret += tmp; } } return ret.replace( ' ', " " ); } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const QDate &date ) { //FIXME: support mRichText==false QString ret; if ( todo->hasStartDate() && todo->dtStart().isValid() ) { KDateTime startDt = todo->dtStart(); if ( todo->recurs() ) { if ( date.isValid() ) { startDt.setDate( date ); } } ret += "
    " + i18n( "Start: %1", dateToString( startDt, false, mSpec ) ); } if ( todo->hasDueDate() && todo->dtDue().isValid() ) { KDateTime dueDt = todo->dtDue(); if ( todo->recurs() ) { if ( date.isValid() ) { KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() ); kdt = kdt.addSecs( -1 ); dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() ); } } ret += "
    " + i18n( "Due: %1", dateTimeToString( dueDt, todo->allDay(), false, mSpec ) ); } // Print priority and completed info here, for lack of a better place if ( todo->priority() > 0 ) { ret += "
    "; ret += "" + i18n( "Priority:" ) + "" + " "; ret += QString::number( todo->priority() ); } ret += "
    "; if ( todo->isCompleted() ) { ret += "" + i18nc( "Completed: date", "Completed:" ) + "" + " "; ret += todo->completedStr().replace( ' ', " " ); } else { ret += "" + i18n( "Percent Done:" ) + "" + " "; ret += i18n( "%1%", todo->percentComplete() ); } return ret.replace( ' ', " " ); } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal *journal ) { //FIXME: support mRichText==false QString ret; if ( journal->dtStart().isValid() ) { ret += "
    " + i18n( "Date: %1", dateToString( journal->dtStart(), false, mSpec ) ); } return ret.replace( ' ', " " ); } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb ) { //FIXME: support mRichText==false QString ret; ret = "
    " + i18n( "Period start: %1", KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) ); ret += "
    " + i18n( "Period start: %1", KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) ); return ret.replace( ' ', " " ); } bool IncidenceFormatter::ToolTipVisitor::visit( Event *event ) { mResult = generateToolTip( event, dateRangeText( event, mDate ) ); return !mResult.isEmpty(); } bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo ) { mResult = generateToolTip( todo, dateRangeText( todo, mDate ) ); return !mResult.isEmpty(); } bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal ) { mResult = generateToolTip( journal, dateRangeText( journal ) ); return !mResult.isEmpty(); } bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb ) { //FIXME: support mRichText==false mResult = "" + i18n( "Free/Busy information for %1", fb->organizer().fullName() ) + ""; mResult += dateRangeText( fb ); mResult += ""; return !mResult.isEmpty(); } static QString tooltipPerson( const QString &email, QString name ) { // Make the search, if there is an email address to search on, // and name is missing if ( name.isEmpty() && !email.isEmpty() ) { #ifndef KDEPIM_NO_KRESOURCES KABC::AddressBook *add_book = KABC::StdAddressBook::self( true ); KABC::Addressee::List addressList = add_book->findByEmail( email ); if ( !addressList.isEmpty() ) { KABC::Addressee o = addressList.first(); if ( !o.isEmpty() && addressList.size() < 2 ) { // use the name from the addressbook name = o.formattedName(); } } #endif } // Show the attendee QString tmpString = ( name.isEmpty() ? email : name ); return tmpString; } static QString tooltipFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role ) { int maxNumAtts = 8; // maximum number of people to print per attendee role QString sep = i18nc( "separator for lists of people names", ", " ); int sepLen = sep.length(); int i = 0; QString tmpStr; Attendee::List::ConstIterator it; Attendee::List attendees = incidence->attendees(); for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { Attendee *a = *it; if ( a->role() != role ) { // skip not this role continue; } if ( a->email() == incidence->organizer().email() ) { // skip attendee that is also the organizer continue; } if ( i == maxNumAtts ) { static QString etc = i18nc( "elipsis", "..." ); tmpStr += etc; break; } tmpStr += tooltipPerson( a->email(), a->name() ); if ( !a->delegator().isEmpty() ) { tmpStr += i18n( " (delegated by %1)", a->delegator() ); } if ( !a->delegate().isEmpty() ) { tmpStr += i18n( " (delegated to %1)", a->delegate() ); } tmpStr += sep; i++; } if ( tmpStr.endsWith( sep ) ) { tmpStr.truncate( tmpStr.length() - sepLen ); } return tmpStr; } static QString tooltipFormatAttendees( Incidence *incidence ) { QString tmpStr, str; // Add organizer link int attendeeCount = incidence->attendees().count(); if ( attendeeCount > 1 || ( attendeeCount == 1 && incidence->organizer().email() != incidence->attendees().first()->email() ) ) { tmpStr += "" + i18n( "Organizer:" ) + "" + " "; tmpStr += tooltipPerson( incidence->organizer().email(), incidence->organizer().name() ); } // Add "chair" str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair ); if ( !str.isEmpty() ) { tmpStr += "
    " + i18n( "Chair:" ) + "" + " "; tmpStr += str; } // Add required participants str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant ); if ( !str.isEmpty() ) { tmpStr += "
    " + i18n( "Required Participants:" ) + "" + " "; tmpStr += str; } // Add optional participants str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant ); if ( !str.isEmpty() ) { tmpStr += "
    " + i18n( "Optional Participants:" ) + "" + " "; tmpStr += str; } // Add observers str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant ); if ( !str.isEmpty() ) { tmpStr += "
    " + i18n( "Observers:" ) + "" + " "; tmpStr += str; } return tmpStr; } QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence, QString dtRangeText ) { int maxDescLen = 120; // maximum description chars to print (before elipsis) //FIXME: support mRichText==false if ( !incidence ) { return QString(); } QString tmp = ""; // header tmp += "" + incidence->richSummary() + ""; tmp += "
    "; QString calStr = mLocation; if ( mCalendar ) { calStr = resourceString( mCalendar, incidence ); } if ( !calStr.isEmpty() ) { tmp += "" + i18n( "Calendar:" ) + "" + " "; tmp += calStr; } tmp += dtRangeText; if ( !incidence->location().isEmpty() ) { tmp += "
    "; tmp += "" + i18nc( "event/todo location", "Location:" ) + "" + " "; tmp += incidence->richLocation(); } QString durStr = durationString( incidence ); if ( !durStr.isEmpty() ) { tmp += "
    "; tmp += "" + i18n( "Duration:" ) + "" + " "; tmp += durStr; } if ( incidence->recurs() ) { tmp += "
    "; tmp += "" + i18n( "Recurrence:" ) + "" + " "; tmp += recurrenceString( incidence ); } if ( !incidence->description().isEmpty() ) { QString desc( incidence->description() ); if ( !incidence->descriptionIsRich() ) { if ( desc.length() > maxDescLen ) { static QString etc = i18nc( "elipsis", "..." ); desc = desc.left( maxDescLen ) + etc; } desc = Qt::escape( desc ).replace( '\n', "
    " ); } else { // TODO: truncate the description when it's rich text } tmp += "
    "; tmp += "" + i18n( "Description:" ) + "" + "
    "; tmp += desc; tmp += "
    "; } int reminderCount = incidence->alarms().count(); if ( reminderCount > 0 && incidence->isAlarmEnabled() ) { tmp += "
    "; tmp += "" + i18np( "Reminder:", "Reminders:", reminderCount ) + "" + " "; tmp += reminderStringList( incidence ).join( ", " ); } tmp += "
    "; tmp += tooltipFormatAttendees( incidence ); int categoryCount = incidence->categories().count(); if ( categoryCount > 0 ) { tmp += "
    "; tmp += "" + i18np( "Category:", "Categories:", categoryCount ) + "" + " "; tmp += incidence->categories().join( ", " ); } tmp += "
    "; return tmp; } //@endcond QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText ) { return toolTipStr( 0, incidence, QDate(), richText, KDateTime::Spec() ); } QString IncidenceFormatter::toolTipStr( IncidenceBase *incidence, bool richText, KDateTime::Spec spec ) { ToolTipVisitor v; if ( v.act( 0, incidence, QDate(), richText, spec ) ) { return v.result(); } else { return QString(); } } QString IncidenceFormatter::toolTipStr( Calendar *calendar, IncidenceBase *incidence, const QDate &date, bool richText, KDateTime::Spec spec ) { ToolTipVisitor v; if ( v.act( calendar, incidence, date, richText, spec ) ) { return v.result(); } else { return QString(); } } QString IncidenceFormatter::toolTipStr( const QString &sourceName, IncidenceBase *incidence, const QDate &date, bool richText, KDateTime::Spec spec ) { ToolTipVisitor v; if ( v.act( sourceName, incidence, date, richText, spec ) ) { return v.result(); } else { return QString(); } } /******************************************************************* * Helper functions for the Incidence tooltips *******************************************************************/ //@cond PRIVATE static QString mailBodyIncidence( Incidence *incidence ) { QString body; if ( !incidence->summary().isEmpty() ) { body += i18n( "Summary: %1\n", incidence->richSummary() ); } if ( !incidence->organizer().isEmpty() ) { body += i18n( "Organizer: %1\n", incidence->organizer().fullName() ); } if ( !incidence->location().isEmpty() ) { body += i18nc( "event/todo location", "Location: %1\n", incidence->richLocation() ); } return body; } //@endcond //@cond PRIVATE class KCal::IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor { public: MailBodyVisitor() : mSpec( KDateTime::Spec() ), mResult( "" ) {} bool act( IncidenceBase *incidence, KDateTime::Spec spec=KDateTime::Spec() ) { mSpec = spec; 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: KDateTime::Spec mSpec; QString mResult; }; bool IncidenceFormatter::MailBodyVisitor::visit( Event *event ) { QString recurrence[]= { i18nc( "no recurrence", "None" ), i18nc( "event recurs by minutes", "Minutely" ), i18nc( "event recurs by hours", "Hourly" ), i18nc( "event recurs by days", "Daily" ), i18nc( "event recurs by weeks", "Weekly" ), i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ), i18nc( "event recurs same day each month", "Monthly Same Day" ), i18nc( "event recurs same month each year", "Yearly Same Month" ), i18nc( "event recurs same day each year", "Yearly Same Day" ), i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" ) }; mResult = mailBodyIncidence( event ); mResult += i18n( "Start Date: %1\n", dateToString( event->dtStart(), true, mSpec ) ); if ( !event->allDay() ) { mResult += i18n( "Start Time: %1\n", timeToString( event->dtStart(), true, mSpec ) ); } if ( event->dtStart() != event->dtEnd() ) { mResult += i18n( "End Date: %1\n", dateToString( event->dtEnd(), true, mSpec ) ); } if ( !event->allDay() ) { mResult += i18n( "End Time: %1\n", timeToString( event->dtEnd(), true, mSpec ) ); } if ( event->recurs() ) { Recurrence *recur = event->recurrence(); // TODO: Merge these two to one of the form "Recurs every 3 days" mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] ); mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() ); if ( recur->duration() > 0 ) { mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() ); mResult += '\n'; } else { if ( recur->duration() != -1 ) { // TODO_Recurrence: What to do with all-day QString endstr; if ( event->allDay() ) { endstr = KGlobal::locale()->formatDate( recur->endDate() ); } else { endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() ); } mResult += i18n( "Repeat until: %1\n", endstr ); } else { mResult += i18n( "Repeats forever\n" ); } } } QString details = event->richDescription(); if ( !details.isEmpty() ) { mResult += i18n( "Details:\n%1\n", details ); } return !mResult.isEmpty(); } bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo ) { mResult = mailBodyIncidence( todo ); if ( todo->hasStartDate() && todo->dtStart().isValid() ) { mResult += i18n( "Start Date: %1\n", dateToString( todo->dtStart( false ), true, mSpec ) ); if ( !todo->allDay() ) { mResult += i18n( "Start Time: %1\n", timeToString( todo->dtStart( false ), true, mSpec ) ); } } if ( todo->hasDueDate() && todo->dtDue().isValid() ) { mResult += i18n( "Due Date: %1\n", dateToString( todo->dtDue(), true, mSpec ) ); if ( !todo->allDay() ) { mResult += i18n( "Due Time: %1\n", timeToString( todo->dtDue(), true, mSpec ) ); } } QString details = todo->richDescription(); if ( !details.isEmpty() ) { mResult += i18n( "Details:\n%1\n", details ); } return !mResult.isEmpty(); } bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal ) { mResult = mailBodyIncidence( journal ); mResult += i18n( "Date: %1\n", dateToString( journal->dtStart(), true, mSpec ) ); if ( !journal->allDay() ) { mResult += i18n( "Time: %1\n", timeToString( journal->dtStart(), true, mSpec ) ); } if ( !journal->description().isEmpty() ) { mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() ); } return !mResult.isEmpty(); } //@endcond QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence ) { return mailBodyStr( incidence, KDateTime::Spec() ); } QString IncidenceFormatter::mailBodyStr( IncidenceBase *incidence, KDateTime::Spec spec ) { if ( !incidence ) { return QString(); } MailBodyVisitor v; if ( v.act( incidence, spec ) ) { return v.result(); } return QString(); } //@cond PRIVATE static QString recurEnd( Incidence *incidence ) { QString endstr; if ( incidence->allDay() ) { endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() ); } else { endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() ); } return endstr; } //@endcond /************************************ * More static formatting functions ************************************/ QString IncidenceFormatter::recurrenceString( Incidence *incidence ) { if ( !incidence->recurs() ) { return i18n( "No recurrence" ); } QStringList dayList; dayList.append( i18n( "31st Last" ) ); dayList.append( i18n( "30th Last" ) ); dayList.append( i18n( "29th Last" ) ); dayList.append( i18n( "28th Last" ) ); dayList.append( i18n( "27th Last" ) ); dayList.append( i18n( "26th Last" ) ); dayList.append( i18n( "25th Last" ) ); dayList.append( i18n( "24th Last" ) ); dayList.append( i18n( "23rd Last" ) ); dayList.append( i18n( "22nd Last" ) ); dayList.append( i18n( "21st Last" ) ); dayList.append( i18n( "20th Last" ) ); dayList.append( i18n( "19th Last" ) ); dayList.append( i18n( "18th Last" ) ); dayList.append( i18n( "17th Last" ) ); dayList.append( i18n( "16th Last" ) ); dayList.append( i18n( "15th Last" ) ); dayList.append( i18n( "14th Last" ) ); dayList.append( i18n( "13th Last" ) ); dayList.append( i18n( "12th Last" ) ); dayList.append( i18n( "11th Last" ) ); dayList.append( i18n( "10th Last" ) ); dayList.append( i18n( "9th Last" ) ); dayList.append( i18n( "8th Last" ) ); dayList.append( i18n( "7th Last" ) ); dayList.append( i18n( "6th Last" ) ); dayList.append( i18n( "5th Last" ) ); dayList.append( i18n( "4th Last" ) ); dayList.append( i18n( "3rd Last" ) ); dayList.append( i18n( "2nd Last" ) ); dayList.append( i18nc( "last day of the month", "Last" ) ); dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI dayList.append( i18n( "1st" ) ); dayList.append( i18n( "2nd" ) ); dayList.append( i18n( "3rd" ) ); dayList.append( i18n( "4th" ) ); dayList.append( i18n( "5th" ) ); dayList.append( i18n( "6th" ) ); dayList.append( i18n( "7th" ) ); dayList.append( i18n( "8th" ) ); dayList.append( i18n( "9th" ) ); dayList.append( i18n( "10th" ) ); dayList.append( i18n( "11th" ) ); dayList.append( i18n( "12th" ) ); dayList.append( i18n( "13th" ) ); dayList.append( i18n( "14th" ) ); dayList.append( i18n( "15th" ) ); dayList.append( i18n( "16th" ) ); dayList.append( i18n( "17th" ) ); dayList.append( i18n( "18th" ) ); dayList.append( i18n( "19th" ) ); dayList.append( i18n( "20th" ) ); dayList.append( i18n( "21st" ) ); dayList.append( i18n( "22nd" ) ); dayList.append( i18n( "23rd" ) ); dayList.append( i18n( "24th" ) ); dayList.append( i18n( "25th" ) ); dayList.append( i18n( "26th" ) ); dayList.append( i18n( "27th" ) ); dayList.append( i18n( "28th" ) ); dayList.append( i18n( "29th" ) ); dayList.append( i18n( "30th" ) ); dayList.append( i18n( "31st" ) ); int weekStart = KGlobal::locale()->weekStartDay(); QString dayNames; QString txt; const KCalendarSystem *calSys = KGlobal::locale()->calendar(); Recurrence *recur = incidence->recurrence(); switch ( recur->recurrenceType() ) { case Recurrence::rNone: return i18n( "No recurrence" ); case Recurrence::rMinutely: if ( recur->duration() != -1 ) { txt = i18np( "Recurs every minute until %2", "Recurs every %1 minutes until %2", recur->frequency(), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18np( "Recurs every minute", "Recurs every %1 minutes", recur->frequency() ); case Recurrence::rHourly: if ( recur->duration() != -1 ) { txt = i18np( "Recurs hourly until %2", "Recurs every %1 hours until %2", recur->frequency(), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() ); case Recurrence::rDaily: if ( recur->duration() != -1 ) { txt = i18np( "Recurs daily until %2", "Recurs every %1 days until %2", recur->frequency(), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() ); case Recurrence::rWeekly: { bool addSpace = false; for ( int i = 0; i < 7; ++i ) { if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) { if ( addSpace ) { dayNames.append( i18nc( "separator for list of days", ", " ) ); } dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, KCalendarSystem::ShortDayName ) ); addSpace = true; } } if ( dayNames.isEmpty() ) { dayNames = i18nc( "Recurs weekly on no days", "no days" ); } if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs weekly on [list of days] until end-date", "Recurs weekly on %2 until %3", "Recurs every %1 weeks on %2 until %3", recur->frequency(), dayNames, recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Recurs weekly on [list of days]", "Recurs weekly on %2", "Recurs every %1 weeks on %2", recur->frequency(), dayNames ); } case Recurrence::rMonthlyPos: { if ( !recur->monthPositions().isEmpty() ) { KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0]; if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs every N months on the [2nd|3rd|...]" " weekdayname until end-date", "Recurs every month on the %2 %3 until %4", "Recurs every %1 months on the %2 %3 until %4", recur->frequency(), dayList[rule.pos() + 31], calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname", "Recurs every month on the %2 %3", "Recurs every %1 months on the %2 %3", recur->frequency(), dayList[rule.pos() + 31], calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) ); } break; } case Recurrence::rMonthlyDay: { if ( !recur->monthDays().isEmpty() ) { int days = recur->monthDays()[0]; if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date", "Recurs monthly on the %2 day until %3", "Recurs every %1 months on the %2 day until %3", recur->frequency(), dayList[days + 31], recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Recurs monthly on the [1st|2nd|...] day", "Recurs monthly on the %2 day", "Recurs every %1 month on the %2 day", recur->frequency(), dayList[days + 31] ); } break; } case Recurrence::rYearlyMonth: { if ( recur->duration() != -1 ) { if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) { txt = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]" " until end-date", "Recurs yearly on %2 %3 until %4", "Recurs every %1 years on %2 %3 until %4", recur->frequency(), calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), dayList[ recur->yearDates()[0] + 31 ], recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } } if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) { return i18ncp( "Recurs Every N years on month-name [1st|2nd|...]", "Recurs yearly on %2 %3", "Recurs every %1 years on %2 %3", recur->frequency(), calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), dayList[ recur->yearDates()[0] + 31 ] ); } else { if (!recur->yearMonths().isEmpty() ) { return i18nc( "Recurs Every year on month-name [1st|2nd|...]", "Recurs yearly on %1 %2", calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), dayList[ recur->startDate().day() + 31 ] ); } else { return i18nc( "Recurs Every year on month-name [1st|2nd|...]", "Recurs yearly on %1 %2", calSys->monthName( recur->startDate().month(), recur->startDate().year() ), dayList[ recur->startDate().day() + 31 ] ); } } break; } case Recurrence::rYearlyDay: if ( !recur->yearDays().isEmpty() ) { if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs every N years on day N until end-date", "Recurs every year on day %2 until %3", "Recurs every %1 years" " on day %2 until %3", recur->frequency(), recur->yearDays()[0], recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Recurs every N YEAR[S] on day N", "Recurs every year on day %2", "Recurs every %1 years" " on day %2", recur->frequency(), recur->yearDays()[0] ); } break; case Recurrence::rYearlyPos: { if ( !recur->yearMonths().isEmpty() && !recur->yearPositions().isEmpty() ) { KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0]; if ( recur->duration() != -1 ) { txt = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname " "of monthname until end-date", "Every year on the %2 %3 of %4 until %5", "Every %1 years on the %2 %3 of %4" " until %5", recur->frequency(), dayList[rule.pos() + 31], calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Every N years on the [2nd|3rd|...] weekdayname " "of monthname", "Every year on the %2 %3 of %4", "Every %1 years on the %2 %3 of %4", recur->frequency(), dayList[rule.pos() + 31], calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ); } } break; } return i18n( "Incidence recurs" ); } QString IncidenceFormatter::timeToString( const KDateTime &date, bool shortfmt, const KDateTime::Spec &spec ) { if ( spec.isValid() ) { QString timeZone; if ( spec.timeZone() != KSystemTimeZones::local() ) { timeZone = ' ' + spec.timeZone().name(); } return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone; } else { return KGlobal::locale()->formatTime( date.time(), !shortfmt ); } } QString IncidenceFormatter::dateToString( const KDateTime &date, bool shortfmt, const KDateTime::Spec &spec ) { if ( spec.isValid() ) { QString timeZone; if ( spec.timeZone() != KSystemTimeZones::local() ) { timeZone = ' ' + spec.timeZone().name(); } return KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; } else { return KGlobal::locale()->formatDate( date.date(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); } } QString IncidenceFormatter::dateTimeToString( const KDateTime &date, bool allDay, bool shortfmt, const KDateTime::Spec &spec ) { if ( allDay ) { return dateToString( date, shortfmt, spec ); } if ( spec.isValid() ) { QString timeZone; if ( spec.timeZone() != KSystemTimeZones::local() ) { timeZone = ' ' + spec.timeZone().name(); } return KGlobal::locale()->formatDateTime( date.toTimeSpec( spec ).dateTime(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; } else { return KGlobal::locale()->formatDateTime( date.dateTime(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); } } QString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence ) { #ifndef KDEPIM_NO_KRESOURCES if ( !calendar || !incidence ) { return QString(); } CalendarResources *calendarResource = dynamic_cast( calendar ); if ( !calendarResource ) { return QString(); } ResourceCalendar *resourceCalendar = calendarResource->resource( incidence ); if ( resourceCalendar ) { if ( !resourceCalendar->subresources().isEmpty() ) { QString subRes = resourceCalendar->subresourceIdentifier( incidence ); if ( subRes.isEmpty() ) { return resourceCalendar->resourceName(); } else { return resourceCalendar->labelForSubresource( subRes ); } } return resourceCalendar->resourceName(); } #endif return QString(); } static QString secs2Duration( int secs ) { QString tmp; int days = secs / 86400; if ( days > 0 ) { tmp += i18np( "1 day", "%1 days", days ); tmp += ' '; secs -= ( days * 86400 ); } int hours = secs / 3600; if ( hours > 0 ) { tmp += i18np( "1 hour", "%1 hours", hours ); tmp += ' '; secs -= ( hours * 3600 ); } int mins = secs / 60; if ( mins > 0 ) { tmp += i18np( "1 minute", "%1 minutes", mins ); } return tmp; } QString IncidenceFormatter::durationString( Incidence *incidence ) { QString tmp; if ( incidence->type() == "Event" ) { Event *event = static_cast( incidence ); if ( event->hasEndDate() ) { if ( !event->allDay() ) { tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) ); } else { tmp = i18np( "1 day", "%1 days", event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 ); } } else { tmp = i18n( "forever" ); } } else if ( incidence->type() == "Todo" ) { Todo *todo = static_cast( incidence ); if ( todo->hasDueDate() ) { if ( todo->hasStartDate() ) { if ( !todo->allDay() ) { tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) ); } else { tmp = i18np( "1 day", "%1 days", todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 ); } } } } return tmp; } QStringList IncidenceFormatter::reminderStringList( Incidence *incidence, bool shortfmt ) { //TODO: implement shortfmt=false Q_UNUSED( shortfmt ); QStringList reminderStringList; if ( incidence ) { Alarm::List alarms = incidence->alarms(); Alarm::List::ConstIterator it; for ( it = alarms.constBegin(); it != alarms.constEnd(); ++it ) { Alarm *alarm = *it; int offset = 0; QString remStr, atStr, offsetStr; if ( alarm->hasTime() ) { offset = 0; if ( alarm->time().isValid() ) { atStr = KGlobal::locale()->formatDateTime( alarm->time() ); } } else if ( alarm->hasStartOffset() ) { offset = alarm->startOffset().asSeconds(); if ( offset < 0 ) { offset = -offset; offsetStr = i18nc( "N days/hours/minutes before the start datetime", "%1 before the start", secs2Duration( offset ) ); } else if ( offset > 0 ) { offsetStr = i18nc( "N days/hours/minutes after the start datetime", "%1 after the start", secs2Duration( offset ) ); } else { //offset is 0 if ( incidence->dtStart().isValid() ) { atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() ); } } } else if ( alarm->hasEndOffset() ) { offset = alarm->endOffset().asSeconds(); if ( offset < 0 ) { offset = -offset; if ( incidence->type() == "Todo" ) { offsetStr = i18nc( "N days/hours/minutes before the due datetime", "%1 before the to-do is due", secs2Duration( offset ) ); } else { offsetStr = i18nc( "N days/hours/minutes before the end datetime", "%1 before the end", secs2Duration( offset ) ); } } else if ( offset > 0 ) { if ( incidence->type() == "Todo" ) { offsetStr = i18nc( "N days/hours/minutes after the due datetime", "%1 after the to-do is due", secs2Duration( offset ) ); } else { offsetStr = i18nc( "N days/hours/minutes after the end datetime", "%1 after the end", secs2Duration( offset ) ); } } else { //offset is 0 if ( incidence->type() == "Todo" ) { Todo *t = static_cast( incidence ); if ( t->dtDue().isValid() ) { atStr = KGlobal::locale()->formatDateTime( t->dtDue() ); } } else { Event *e = static_cast( incidence ); if ( e->dtEnd().isValid() ) { atStr = KGlobal::locale()->formatDateTime( e->dtEnd() ); } } } } if ( offset == 0 ) { if ( !atStr.isEmpty() ) { remStr = i18nc( "reminder occurs at datetime", "at %1", atStr ); } } else { remStr = offsetStr; } if ( alarm->repeatCount() > 0 ) { QString countStr = i18np( "repeats once", "repeats %1 times", alarm->repeatCount() ); QString intervalStr = i18nc( "interval is N days/hours/minutes", "interval is %1", secs2Duration( alarm->snoozeTime().asSeconds() ) ); QString repeatStr = i18nc( "(repeat string, interval string)", "(%1, %2)", countStr, intervalStr ); remStr = remStr + ' ' + repeatStr; } reminderStringList << remStr; } } return reminderStringList; }