diff options
Diffstat (limited to 'libkcal')
79 files changed, 7452 insertions, 1462 deletions
diff --git a/libkcal/Makefile.am b/libkcal/Makefile.am index f76d32044..2fa8c2889 100644 --- a/libkcal/Makefile.am +++ b/libkcal/Makefile.am @@ -10,15 +10,17 @@ libkcal_la_LDFLAGS = $(all_libraries) -no-undefined -version-info 2:0:0 $(LIB_QT libkcal_la_LIBADD = versit/libversit.la $(LIB_KIO) \ -lical -licalss \ $(top_builddir)/ktnef/lib/libktnef.la \ + ../libkmime/libkmime.la \ ../libemailfunctions/libemailfunctions.la \ -lkresources -lkabc libkcal_la_SOURCES = \ + assignmentvisitor.cpp comparisonvisitor.cpp \ incidencebase.cpp incidence.cpp journal.cpp todo.cpp event.cpp \ freebusy.cpp attendee.cpp attachment.cpp recurrencerule.cpp recurrence.cpp alarm.cpp \ customproperties.cpp calendar.cpp calendarlocal.cpp \ calformat.cpp vcalformat.cpp icalformat.cpp icalformatimpl.cpp \ - incidenceformatter.cpp \ + incidenceformatter.cpp calhelper.cpp calselectdialog.cpp \ vcaldrag.cpp icaldrag.cpp \ exceptions.cpp \ scheduler.cpp imipscheduler.cpp dummyscheduler.cpp \ @@ -34,18 +36,20 @@ libkcal_la_SOURCES = \ qtopiaformat.cpp \ htmlexportsettings.kcfgc htmlexport.cpp calendarnull.cpp \ freebusyurlstore.cpp \ - confirmsavedialog.cpp + confirmsavedialog.cpp \ + attachmenthandler.cpp libkcalincludedir = $(includedir)/libkcal libkcalinclude_HEADERS = alarm.h attachment.h attendee.h calendar.h \ calendarlocal.h calendarnull.h calendarresources.h calfilter.h calformat.h \ calstorage.h customproperties.h dndfactory.h duration.h event.h \ exceptions.h filestorage.h freebusy.h htmlexportsettings.h htmlexport.h icaldrag.h icalformat.h \ - incidencebase.h incidence.h incidenceformatter.h journal.h kcalversion.h listbase.h period.h person.h \ + incidencebase.h incidence.h incidenceformatter.h calhelper.h calselectdialog.h \ + journal.h kcalversion.h listbase.h period.h person.h \ qtopiaformat.h recurrencerule.h recurrence.h resourcecached.h resourcecalendar.h \ resourcelocalconfig.h resourcelocaldirconfig.h resourcelocaldir.h \ resourcelocal.h scheduler.h libkcal_export.h \ - todo.h vcaldrag.h vcalformat.h + todo.h vcaldrag.h vcalformat.h attachmenthandler.h kde_module_LTLIBRARIES = kcal_local.la kcal_localdir.la diff --git a/libkcal/alarm.cpp b/libkcal/alarm.cpp index b5b9b515a..8bc40f001 100644 --- a/libkcal/alarm.cpp +++ b/libkcal/alarm.cpp @@ -47,6 +47,30 @@ Alarm::~Alarm() { } +Alarm *Alarm::clone() +{ + return new Alarm( *this ); +} + +Alarm &Alarm::operator=( const Alarm &a ) +{ + mParent = a.mParent; + mType = a.mType; + mDescription = a.mDescription; + mFile = a.mFile; + mMailAttachFiles = a.mMailAttachFiles; + mMailAddresses = a.mMailAddresses; + mMailSubject = a.mMailSubject; + mAlarmSnoozeTime = a.mAlarmSnoozeTime; + mAlarmRepeatCount = a.mAlarmRepeatCount; + mAlarmTime = a.mAlarmTime; + mOffset = a.mOffset; + mEndOffset = a.mEndOffset; + mHasTime = a.mHasTime; + mAlarmEnabled = a.mAlarmEnabled; + return *this; +} + bool Alarm::operator==( const Alarm& rhs ) const { if ( mType != rhs.mType || @@ -304,19 +328,22 @@ void Alarm::setTime(const TQDateTime &alarmTime) TQDateTime Alarm::time() const { - if ( hasTime() ) + if ( hasTime() ) { return mAlarmTime; - else if ( mParent ) - { - if (mParent->type()=="Todo") { - Todo *t = static_cast<Todo*>(mParent); - return mOffset.end( t->dtDue() ); - } else if (mEndOffset) { - return mOffset.end( mParent->dtEnd() ); + } else if ( mParent ) { + if ( mEndOffset ) { + if ( mParent->type() == "Todo" ) { + Todo *t = static_cast<Todo*>( mParent ); + return mOffset.end( t->dtDue() ); + } else { + return mOffset.end( mParent->dtEnd() ); + } } else { return mOffset.end( mParent->dtStart() ); } - } else return TQDateTime(); + } else { + return TQDateTime(); + } } bool Alarm::hasTime() const @@ -324,15 +351,15 @@ bool Alarm::hasTime() const return mHasTime; } -void Alarm::setSnoozeTime(int alarmSnoozeTime) +void Alarm::setSnoozeTime(const Duration &alarmSnoozeTime) { - if (alarmSnoozeTime > 0) { + if (alarmSnoozeTime.value() > 0) { mAlarmSnoozeTime = alarmSnoozeTime; if ( mParent ) mParent->updated(); } } -int Alarm::snoozeTime() const +Duration Alarm::snoozeTime() const { return mAlarmSnoozeTime; } @@ -348,9 +375,10 @@ int Alarm::repeatCount() const return mAlarmRepeatCount; } -int Alarm::duration() const +Duration Alarm::duration() const { - return mAlarmRepeatCount * mAlarmSnoozeTime * 60; + return Duration( mAlarmSnoozeTime.value() * mAlarmRepeatCount, + mAlarmSnoozeTime.type() ); } TQDateTime Alarm::nextRepetition(const TQDateTime& preTime) const @@ -422,7 +450,7 @@ void Alarm::setStartOffset( const Duration &offset ) Duration Alarm::startOffset() const { - return (mHasTime || mEndOffset) ? 0 : mOffset; + return (mHasTime || mEndOffset) ? Duration( 0 ) : mOffset; } bool Alarm::hasStartOffset() const @@ -445,7 +473,7 @@ void Alarm::setEndOffset( const Duration &offset ) Duration Alarm::endOffset() const { - return (mHasTime || !mEndOffset) ? 0 : mOffset; + return (mHasTime || !mEndOffset) ? Duration( 0 ) : mOffset; } void Alarm::setParent( Incidence *parent ) diff --git a/libkcal/alarm.h b/libkcal/alarm.h index 7d82cf65d..893e7be41 100644 --- a/libkcal/alarm.h +++ b/libkcal/alarm.h @@ -2,6 +2,7 @@ This file is part of libkcal. Copyright (c) 2001-2003 Cornelius Schumacher <schumacher@kde.org> + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public @@ -58,6 +59,18 @@ class LIBKCAL_EXPORT Alarm : public CustomProperties ~Alarm(); /** + Returns an exact copy of this alarm. The returned object is owned by the caller. + @since 4.5 + */ + Alarm *clone(); + + /** + Copy operator. + @since 4.5 + */ + Alarm &operator=( const Alarm & ); + + /** Compare this alarm with another one. */ bool operator==( const Alarm & ) const; @@ -67,10 +80,11 @@ class LIBKCAL_EXPORT Alarm : public CustomProperties Set the type of the alarm. If the specified type is different from the current type of the alarm, the alarm's type-specific properties are initialised to null. - + @param type type of alarm. */ void setType( Type type ); + /** Return the type of the alarm. */ @@ -78,15 +92,17 @@ class LIBKCAL_EXPORT Alarm : public CustomProperties /** Set the alarm to be a display alarm. - + @param text text to display when the alarm is triggered. */ void setDisplayAlarm( const TQString &text = TQString::null ); + /** Set the text to be displayed when the alarm is triggered. Ignored if the alarm is not a display alarm. */ void setText( const TQString &text ); + /** Return the text string that displays when the alarm is triggered. */ @@ -94,7 +110,7 @@ class LIBKCAL_EXPORT Alarm : public CustomProperties /** Set the alarm to be an audio alarm. - + @param audioFile optional file to play when the alarm is triggered. */ void setAudioAlarm( const TQString &audioFile = TQString::null ); @@ -105,14 +121,14 @@ class LIBKCAL_EXPORT Alarm : public CustomProperties void setAudioFile( const TQString &audioFile ); /** Return the name of the audio file for the alarm. - + @return The audio file for the alarm, or TQString::null if not an audio alarm. */ TQString audioFile() const; /** Set the alarm to be a procedure alarm. - + @param programFile program to execute when the alarm is triggered. @param arguments arguments to supply to programFile. */ @@ -125,7 +141,7 @@ class LIBKCAL_EXPORT Alarm : public CustomProperties void setProgramFile( const TQString &programFile ); /** Return the name of the program file to execute when the alarm is triggered. - + @return the program file name, or TQString::null if not a procedure alarm. */ TQString programFile() const; @@ -136,14 +152,14 @@ class LIBKCAL_EXPORT Alarm : public CustomProperties void setProgramArguments( const TQString &arguments ); /** Return the arguments to the program to run when the alarm is triggered. - + @return the program arguments, or TQString::null if not a procedure alarm. */ TQString programArguments() const; /** Set the alarm to be an email alarm. - + @param subject subject line of email. @param text body of email. @param addressees email addresses of recipient(s). @@ -210,7 +226,7 @@ class LIBKCAL_EXPORT Alarm : public CustomProperties void setMailText( const TQString &text ); /** Return the email body text. - + @return the body text, or TQString::null if not an email alarm. */ TQString mailText() const; @@ -267,17 +283,17 @@ class LIBKCAL_EXPORT Alarm : public CustomProperties /** Set the interval between snoozes for the alarm. - + @param alarmSnoozeTime the time in minutes between snoozes. */ - void setSnoozeTime( int alarmSnoozeTime ); + void setSnoozeTime( const Duration &alarmSnoozeTime ); /** Get how long the alarm snooze interval is. - + @return the number of minutes between snoozes. */ - int snoozeTime() const; + Duration snoozeTime() const; /** Set how many times an alarm is to repeat itself after its initial @@ -308,7 +324,7 @@ class LIBKCAL_EXPORT Alarm : public CustomProperties Get how long between the alarm's initial occurrence and its final repetition. @return the number of seconds between the initial occurrence and final repetition. */ - int duration() const; + Duration duration() const; /** Toggles the value of alarm to be either on or off. @@ -350,8 +366,8 @@ class LIBKCAL_EXPORT Alarm : public CustomProperties TQValueList<Person> mMailAddresses; // who to mail for reminder TQString mMailSubject; // subject of email - int mAlarmSnoozeTime; // number of minutes after alarm to - // snooze before ringing again + Duration mAlarmSnoozeTime; // how long after alarm to snooze before + // triggering again int mAlarmRepeatCount; // number of times for alarm to repeat // after the initial time diff --git a/libkcal/assignmentvisitor.cpp b/libkcal/assignmentvisitor.cpp new file mode 100644 index 000000000..023df8572 --- /dev/null +++ b/libkcal/assignmentvisitor.cpp @@ -0,0 +1,123 @@ +/* + Copyright (c) 2009 Kevin Krammer <kevin.krammer@gmx.at> + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "assignmentvisitor.h" + +#include "event.h" +#include "freebusy.h" +#include "journal.h" +#include "todo.h" + +#include <kdebug.h> + +using namespace KCal; + +class AssignmentVisitor::Private +{ + public: + Private() : mSource( 0 ) {} + + public: + const IncidenceBase *mSource; +}; + +AssignmentVisitor::AssignmentVisitor() : d( new Private() ) +{ +} + +AssignmentVisitor::~AssignmentVisitor() +{ + delete d; +} + +bool AssignmentVisitor::assign( IncidenceBase *target, const IncidenceBase *source ) +{ + Q_ASSERT( target != 0 ); + Q_ASSERT( source != 0 ); + + d->mSource = source; + + bool result = target->accept( *this ); + + d->mSource = 0; + + return result; +} + +bool AssignmentVisitor::visit( Event *event ) +{ + Q_ASSERT( event != 0 ); + + const Event *source = dynamic_cast<const Event*>( d->mSource ); + if ( source == 0 ) { + kdError(5800) << "Type mismatch: source is" << d->mSource->type() + << "target is" << event->type(); + return false; + } + + *event = *source; + return true; +} + +bool AssignmentVisitor::visit( Todo *todo ) +{ + Q_ASSERT( todo != 0 ); + + const Todo *source = dynamic_cast<const Todo*>( d->mSource ); + if ( source == 0 ) { + kdError(5800) << "Type mismatch: source is" << d->mSource->type() + << "target is" << todo->type(); + return false; + } + + *todo = *source; + return true; +} + +bool AssignmentVisitor::visit( Journal *journal ) +{ + Q_ASSERT( journal != 0 ); + + const Journal *source = dynamic_cast<const Journal*>( d->mSource ); + if ( source == 0 ) { + kdError(5800) << "Type mismatch: source is" << d->mSource->type() + << "target is" << journal->type(); + return false; + } + + *journal = *source; + return true; +} + +bool AssignmentVisitor::visit( FreeBusy *freebusy ) +{ + Q_ASSERT( freebusy != 0 ); + + const FreeBusy *source = dynamic_cast<const FreeBusy*>( d->mSource ); + if ( source == 0 ) { + kdError(5800) << "Type mismatch: source is" << d->mSource->type() + << "target is" << freebusy->type(); + return false; + } + + *freebusy = *source; + return true; +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/libkcal/assignmentvisitor.h b/libkcal/assignmentvisitor.h new file mode 100644 index 000000000..2be0215cc --- /dev/null +++ b/libkcal/assignmentvisitor.h @@ -0,0 +1,122 @@ +/* + Copyright (c) 2009 Kevin Krammer <kevin.krammer@gmx.at> + + 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. +*/ + +#ifndef KCAL_ASSIGNMENTVISITOR_H +#define KCAL_ASSIGNMENTVISITOR_H + +#include "incidencebase.h" + +namespace KCal { + +/** + Helper for type correct assignment of incidences via pointers. + + This class provides a way of correctly assigning one incidence to another, + given two IncidenceBase derived pointers. It effectively provides a virtual + assignment method which first type checks the two pointers to ensure they + reference the same incidence type, before performing the assignment. + + Usage example: + @code + KCal::Incidence *currentIncidence; // assume this is set somewhere else + KCal::Incidence *updatedIncidence; // assume this is set somewhere else + + KCal::AssignmentVisitor visitor; + + // assign + if ( !visitor.assign(currentIncidence, updatedIncidence) ) { + // not of same type + } + @endcode + + @author Kevin Krammer \<kevin.krammer@gmx.at\> + */ +class AssignmentVisitor : public IncidenceBase::Visitor +{ + public: + /** + Creates a visitor instance. + */ + AssignmentVisitor(); + + /** + Destroys the instance. + */ + virtual ~AssignmentVisitor(); + + /** + Assigns the incidence referenced by @p source to the incidence referenced + by @p target, first ensuring that the @p source incidence can be cast to + the same class as the @p target incidence. + + Basically it is a virtual equivalent of + @code + *target = *source + @endcode + + @param target pointer to the instance to assign to + @param source pointer to the instance to assign from + + @return @c false if the two objects are of different type + */ + bool assign( IncidenceBase *target, const IncidenceBase *source ); + + /** + Tries to assign to the given @p event, using the source passed to + assign(). + + @return @c false if the source passed to assign() is not an Event + */ + virtual bool visit( Event *event ); + + /** + Tries to assign to the given @p todo, using the source passed to + assign(). + + @return @c false if the source passed to assign() is not a Todo + */ + virtual bool visit( Todo *todo ); + + /** + Tries to assign to the given @p journal, using the source passed to + assign(). + + @return @c false if the source passed to assign() is not a Journal + */ + virtual bool visit( Journal *journal ); + + /** + Tries to assign to the given @p freebusy, using the source passed to + assign(). + + @return @c false if the source passed to assign() is not a FreeBusy + */ + virtual bool visit( FreeBusy *freebusy ); + + private: + //@cond PRIVATE + class Private; + Private *const d; + //@endcond +}; + +} + +#endif +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/libkcal/attachment.cpp b/libkcal/attachment.cpp index fc319b735..51858161c 100644 --- a/libkcal/attachment.cpp +++ b/libkcal/attachment.cpp @@ -20,34 +20,46 @@ */ #include "attachment.h" +#include <kmdcodec.h> using namespace KCal; -Attachment::Attachment( const Attachment &attachment) +Attachment::Attachment( const Attachment &attachment ) { + mSize = attachment.mSize; mMimeType = attachment.mMimeType; - mData = attachment.mData; + mUri = attachment.mUri; + mData = qstrdup( attachment.mData ); + mLabel = attachment.mLabel; mBinary = attachment.mBinary; - mShowInline = attachment.mShowInline; - mLabel = attachment.mLabel; + mLocal = attachment.mLocal; + mShowInline = attachment.mShowInline; } -Attachment::Attachment(const TQString& uri, const TQString& mime) +Attachment::Attachment( const TQString &uri, const TQString &mime ) { + mSize = 0; mMimeType = mime; - mData = uri; + mUri = uri; + mData = 0; mBinary = false; - mShowInline = false; - mLabel = TQString::null; + mLocal = false; + mShowInline = false; } -Attachment::Attachment(const char *base64, const TQString& mime) +Attachment::Attachment( const char *base64, const TQString &mime ) { + mSize = 0; mMimeType = mime; - mData = TQString::fromUtf8(base64); + mData = qstrdup( base64 ); mBinary = true; - mShowInline = false; - mLabel = TQString::null; + mLocal = false; + mShowInline = false; +} + +Attachment::~Attachment() +{ + delete[] mData; } bool Attachment::isUri() const @@ -57,15 +69,16 @@ bool Attachment::isUri() const TQString Attachment::uri() const { - if (!mBinary) - return mData; - else + if ( !mBinary ) { + return mUri; + } else { return TQString::null; + } } -void Attachment::setUri(const TQString& uri) +void Attachment::setUri( const TQString &uri ) { - mData = uri; + mUri = uri; mBinary = false; } @@ -76,17 +89,60 @@ bool Attachment::isBinary() const char *Attachment::data() const { - if (mBinary) - // this method actually return a const char*, but that can't be done because of the uneededly non-const libical API - return const_cast<char*>( mData.latin1() ); //mData.utf8().data(); - else + if ( mBinary ) { + return mData; + } else { return 0; + } +} + +TQByteArray &Attachment::decodedData() +{ + if ( mDataCache.isNull() && mData ) { + // base64Decode() sometimes appends a null byte when called + // with a TQCString so work on TQByteArray's instead + TQByteArray encoded; + encoded.duplicate( mData, strlen( mData ) ); + TQByteArray decoded; + KCodecs::base64Decode( encoded, decoded ); + mDataCache = decoded; + } + + return mDataCache; } -void Attachment::setData(const char *base64) +void Attachment::setDecodedData( const TQByteArray &data ) { - mData = TQString::fromUtf8(base64); + TQByteArray encoded; + KCodecs::base64Encode( data, encoded ); + + encoded.resize( encoded.count() + 1 ); + encoded[encoded.count()-1] = '\0'; + + setData( encoded.data() ); + mDataCache = data; + mSize = mDataCache.size(); +} + +void Attachment::setData( const char *base64 ) +{ + delete[] mData; + mData = qstrdup( base64 ); mBinary = true; + mDataCache = TQByteArray(); + mSize = 0; +} + +uint Attachment::size() +{ + if ( isUri() ) { + return 0; + } + if ( !mSize ) { + mSize = decodedData().size(); + } + + return mSize; } TQString Attachment::mimeType() const @@ -119,3 +175,12 @@ void Attachment::setLabel( const TQString& label ) mLabel = label; } +bool Attachment::isLocal() const +{ + return mLocal; +} + +void Attachment::setLocal( bool local ) +{ + mLocal = local; +} diff --git a/libkcal/attachment.h b/libkcal/attachment.h index 416701850..7727a5c69 100644 --- a/libkcal/attachment.h +++ b/libkcal/attachment.h @@ -38,14 +38,14 @@ class KDE_EXPORT Attachment /** Create a Reference to some URI by copying an existing Attachment. - + @param attachment the attachment to be duplicated */ Attachment( const Attachment &attachment ); /** Create a Reference to some URI. - + @param uri the uri this attachment refers to @param mime the mime type of the resource being linked to */ @@ -53,39 +53,66 @@ class KDE_EXPORT Attachment /** Create a binary attachment. - + @param base64 the attachment in base64 format @param mime the mime type of the attachment */ Attachment( const char *base64, const TQString &mime = TQString::null ); + ~Attachment(); + /* The VALUE parameter in iCal */ bool isUri() const; TQString uri() const; void setUri( const TQString &uri ); - + bool isBinary() const; char *data() const; void setData( const char *base64 ); + void setDecodedData( const TQByteArray &data ); + TQByteArray &decodedData(); + + uint size(); + /* The optional FMTTYPE parameter in iCal */ TQString mimeType() const; void setMimeType( const TQString &mime ); - - /* The custom X-CONTENT-DISPOSITION parameter, used by OGo etc. */ - bool showInline() const; - void setShowInline( bool showinline ); - - /* The custom X-LABEL parameter to show a human-readable title */ - TQString label() const; - void setLabel( const TQString &label ); + + /* The custom X-CONTENT-DISPOSITION parameter, used by OGo etc. */ + bool showInline() const; + void setShowInline( bool showinline ); + + /* The custom X-LABEL parameter to show a human-readable title */ + TQString label() const; + void setLabel( const TQString &label ); + + /** + Sets the attachment "local" option, which is derived from the + Calendar Incidence @b X-KONTACT-TYPE parameter. + + @param local is the flag to set (true) or unset (false) for the + attachment "local" option. + + @see local() + */ + void setLocal( bool local ); + + /** + Returns the attachment "local" flag. + */ + bool isLocal() const; private: + TQByteArray mDataCache; + uint mSize; TQString mMimeType; - TQString mData; + TQString mUri; + char *mData; + TQString mLabel; bool mBinary; - bool mShowInline; - TQString mLabel; + bool mLocal; + bool mShowInline; class Private; Private *d; diff --git a/libkcal/attachmenthandler.cpp b/libkcal/attachmenthandler.cpp new file mode 100644 index 000000000..3163def07 --- /dev/null +++ b/libkcal/attachmenthandler.cpp @@ -0,0 +1,258 @@ +/* + This file is part of the kcal library. + + Copyright (c) 2010 Klar�lvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> + + 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 dealing with calendar incidence attachments. + + @brief + vCalendar/iCalendar attachment handling. + + @author Allen Winter \<winter@kde.org\> +*/ +#include "attachmenthandler.h" +#include "attachment.h" +#include "calendarresources.h" +#include "incidence.h" +#include "scheduler.h" + +#include <kapplication.h> +#include <kfiledialog.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kmimetype.h> +#include <krun.h> +#include <ktempfile.h> +#include <kio/netaccess.h> + +#include <tqfile.h> + +namespace KCal { + +Attachment *AttachmentHandler::find( TQWidget *parent, const TQString &attachmentName, + Incidence *incidence ) +{ + if ( !incidence ) { + return 0; + } + + // get the attachment by name from the incidence + Attachment::List as = incidence->attachments(); + Attachment *a = 0; + if ( as.count() > 0 ) { + Attachment::List::ConstIterator it; + for ( it = as.begin(); it != as.end(); ++it ) { + if ( (*it)->label() == attachmentName ) { + a = *it; + break; + } + } + } + + if ( !a ) { + KMessageBox::error( + parent, + i18n( "No attachment named \"%1\" found in the incidence." ).arg( attachmentName ) ); + return 0; + } + + if ( a->isUri() ) { + if ( !KIO::NetAccess::exists( a->uri(), true, parent ) ) { + KMessageBox::sorry( + parent, + i18n( "The attachment \"%1\" is a web link that is inaccessible from this computer. " ). + arg( KURL::decode_string( a->uri() ) ) ); + return 0; + } + } + return a; +} + +Attachment *AttachmentHandler::find( TQWidget *parent, + const TQString &attachmentName, const TQString &uid ) +{ + if ( uid.isEmpty() ) { + return 0; + } + + CalendarResources *cal = new CalendarResources( "UTC" ); + cal->readConfig(); + cal->load(); + Incidence *incidence = cal->incidence( uid ); + if ( !incidence ) { + KMessageBox::error( + parent, + i18n( "The incidence that owns the attachment named \"%1\" could not be found. " + "Perhaps it was removed from your calendar?" ).arg( attachmentName ) ); + return 0; + } + + return find( parent, attachmentName, incidence ); +} + +Attachment *AttachmentHandler::find( TQWidget *parent, const TQString &attachmentName, + ScheduleMessage *message ) +{ + if ( !message ) { + return 0; + } + + Incidence *incidence = dynamic_cast<Incidence*>( message->event() ); + if ( !incidence ) { + KMessageBox::error( + parent, + i18n( "The calendar invitation stored in this email message is broken in some way. " + "Unable to continue." ) ); + return 0; + } + + return find( parent, attachmentName, incidence ); +} + +static KTempFile *s_tempFile = 0; + +static KURL tempFileForAttachment( Attachment *attachment ) +{ + KURL url; + TQStringList patterns = KMimeType::mimeType( attachment->mimeType() )->patterns(); + if ( !patterns.empty() ) { + s_tempFile = new KTempFile( TQString::null, + TQString( patterns.first() ).remove( '*' ), 0600 ); + } else { + s_tempFile = new KTempFile( TQString::null, TQString::null, 0600 ); + } + + TQFile *qfile = s_tempFile->file(); + qfile->open( IO_WriteOnly ); + TQTextStream stream( qfile ); + stream.writeRawBytes( attachment->decodedData().data(), attachment->size() ); + s_tempFile->close(); + TQFile tf( s_tempFile->name() ); + if ( tf.size() != attachment->size() ) { + //whoops. failed to write the entire attachment. return an invalid URL. + delete s_tempFile; + s_tempFile = 0; + return url; + } + + url.setPath( s_tempFile->name() ); + return url; +} + +bool AttachmentHandler::view( TQWidget *parent, Attachment *attachment ) +{ + if ( !attachment ) { + return false; + } + + bool stat = true; + if ( attachment->isUri() ) { + kapp->invokeBrowser( attachment->uri() ); + } else { + // put the attachment in a temporary file and launch it + KURL tempUrl = tempFileForAttachment( attachment ); + if ( tempUrl.isValid() ) { + stat = KRun::runURL( tempUrl, attachment->mimeType(), false, true ); + } else { + stat = false; + KMessageBox::error( + parent, + i18n( "Unable to create a temporary file for the attachment." ) ); + } + delete s_tempFile; + s_tempFile = 0; + } + return stat; +} + +bool AttachmentHandler::view( TQWidget *parent, const TQString &attachmentName, Incidence *incidence ) +{ + return view( parent, find( parent, attachmentName, incidence ) ); +} + +bool AttachmentHandler::view( TQWidget *parent, const TQString &attachmentName, const TQString &uid ) +{ + return view( parent, find( parent, attachmentName, uid ) ); +} + +bool AttachmentHandler::view( TQWidget *parent, const TQString &attachmentName, + ScheduleMessage *message ) +{ + return view( parent, find( parent, attachmentName, message ) ); +} + +bool AttachmentHandler::saveAs( TQWidget *parent, Attachment *attachment ) +{ + // get the saveas file name + TQString saveAsFile = KFileDialog::getSaveFileName( attachment->label(), TQString::null, parent, + i18n( "Save Attachment" ) ); + if ( saveAsFile.isEmpty() || + ( TQFile( saveAsFile ).exists() && + ( KMessageBox::warningYesNo( + parent, + i18n( "%1 already exists. Do you want to overwrite it?"). + arg( saveAsFile ) ) == KMessageBox::No ) ) ) { + return false; + } + + bool stat = false; + if ( attachment->isUri() ) { + // save the attachment url + stat = KIO::NetAccess::file_copy( attachment->uri(), KURL( saveAsFile ), -1, true ); + } else { + // put the attachment in a temporary file and save it + KURL tempUrl = tempFileForAttachment( attachment ); + if ( tempUrl.isValid() ) { + stat = KIO::NetAccess::file_copy( tempUrl, KURL( saveAsFile ), -1, true ); + if ( !stat && KIO::NetAccess::lastError() ) { + KMessageBox::error( parent, KIO::NetAccess::lastErrorString() ); + } + } else { + stat = false; + KMessageBox::error( + parent, + i18n( "Unable to create a temporary file for the attachment." ) ); + } + delete s_tempFile; + s_tempFile = 0; + } + return stat; +} + +bool AttachmentHandler::saveAs( TQWidget *parent, const TQString &attachmentName, + Incidence *incidence ) +{ + return saveAs( parent, find( parent, attachmentName, incidence ) ); +} + +bool AttachmentHandler::saveAs( TQWidget *parent, const TQString &attachmentName, const TQString &uid ) +{ + return saveAs( parent, find( parent, attachmentName, uid ) ); +} + +bool AttachmentHandler::saveAs( TQWidget *parent, const TQString &attachmentName, + ScheduleMessage *message ) +{ + return saveAs( parent, find( parent, attachmentName, message ) ); +} + +} + diff --git a/libkcal/attachmenthandler.h b/libkcal/attachmenthandler.h new file mode 100644 index 000000000..6116f15ad --- /dev/null +++ b/libkcal/attachmenthandler.h @@ -0,0 +1,178 @@ +/* + This file is part of the kcal library. + + Copyright (c) 2010 Klar�lvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> + + 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 dealing with calendar incidence attachments. + + @author Allen Winter \<winter@kde.org\> +*/ + +#ifndef KCAL_ATTACHMENTHANDLER_H +#define KCAL_ATTACHMENTHANDLER_H + +class TQString; +class TQWidget; + +namespace KCal { + +class Attachment; +class Incidence; +class ScheduleMessage; + +/** + @brief + Provides methods to handle incidence attachments. + + Includes functions to view and save attachments. +*/ +namespace AttachmentHandler { + + /** + Finds the attachment in the user's calendar, by @p attachmentName and @p incidence. + + @param parent is the parent widget for the dialogs used in this function. + @param attachmentName is the name of the attachment + @param incidence is a pointer to a valid Incidence object containing the attachment. + + @return a pointer to the Attachment object located; 0 if no such attachment could be found. + */ + Attachment *find( TQWidget *parent, const TQString &attachmentName, Incidence *incidence ); + + /** + Finds the attachment in the user's calendar, by @p attachmentName and a scheduler message; + in other words, this function is intended to retrieve attachments from calendar invitations. + + @param parent is the parent widget for the dialogs used in this function. + @param attachmentName is the name of the attachment + @param message is a pointer to a valid ScheduleMessage object containing the attachment. + + @return a pointer to the Attachment object located; 0 if no such attachment could be found. + */ + Attachment *find( TQWidget *parent, const TQString &attachmentName, ScheduleMessage *message ); + + /** + Finds the attachment in the user's calendar, by @p attachmentName and @p uid. + + @param parent is the parent widget for the dialogs used in this function. + @param attachmentName is the name of the attachment + @param uid is a TQString containing a UID of the incidence containing the attachment. + + @return a pointer to the Attachment object located; 0 if no such attachment could be found. + */ + Attachment *find( TQWidget *parent, const TQString &attachmentName, const TQString &uid ); + + /** + Launches a viewer on the specified attachment. + + @param parent is the parent widget for the dialogs used in this function. + @param attachment is a pointer to a valid Attachment object. + + @return true if the viewer program successfully launched; false otherwise. + */ + bool view( TQWidget *parent, Attachment *attachment ); + + /** + Launches a viewer on the specified attachment. + + @param parent is the parent widget for the dialogs used in this function. + @param attachmentName is the name of the attachment + @param incidence is a pointer to a valid Incidence object containing the attachment. + + @return true if the attachment could be found and the viewer program successfully launched; + false otherwise. + */ + bool view( TQWidget *parent, const TQString &attachmentName, Incidence *incidence ); + + /** + Launches a viewer on the specified attachment. + + @param parent is the parent widget for the dialogs used in this function. + @param attachmentName is the name of the attachment + @param uid is a TQString containing a UID of the incidence containing the attachment. + + @return true if the attachment could be found and the viewer program successfully launched; + false otherwise. + */ + bool view( TQWidget *parent, const TQString &attachmentName, const TQString &uid ); + + /** + Launches a viewer on the specified attachment. + + @param parent is the parent widget for the dialogs used in this function. + @param attachmentName is the name of the attachment + @param message is a pointer to a valid ScheduleMessage object containing the attachment. + + @return true if the attachment could be found and the viewer program successfully launched; + false otherwise. + */ + bool view( TQWidget *parent, const TQString &attachmentName, ScheduleMessage *message ); + + /** + Saves the specified attachment to a file of the user's choice. + + @param parent is the parent widget for the dialogs used in this function. + @param attachment is a pointer to a valid Attachment object. + + @return true if the save operation was successful; false otherwise. + */ + bool saveAs( TQWidget *parent, Attachment *attachment ); + + /** + Saves the specified attachment to a file of the user's choice. + + @param parent is the parent widget for the dialogs used in this function. + @param attachmentName is the name of the attachment + @param incidence is a pointer to a valid Incidence object containing the attachment. + + @return true if the attachment could be found and the save operation was successful; + false otherwise. + */ + bool saveAs( TQWidget *parent, const TQString &attachmentName, Incidence *incidence ); + + /** + Saves the specified attachment to a file of the user's choice. + + @param parent is the parent widget for the dialogs used in this function. + @param attachmentName is the name of the attachment + @param uid is a TQString containing a UID of the incidence containing the attachment. + + @return true if the attachment could be found and the save operation was successful; + false otherwise. + */ + bool saveAs( TQWidget *parent, const TQString &attachmentName, const TQString &uid ); + + /** + Saves the specified attachment to a file of the user's choice. + + @param parent is the parent widget for the dialogs used in this function. + @param attachmentName is the name of the attachment + @param message is a pointer to a valid ScheduleMessage object containing the attachment. + + @return true if the attachment could be found and the save operation was successful; + false otherwise. + */ + bool saveAs( TQWidget *parent, const TQString &attachmentName, ScheduleMessage *message ); +} + +} + +#endif diff --git a/libkcal/attendee.cpp b/libkcal/attendee.cpp index 3cb2afedf..9aa3d46f7 100644 --- a/libkcal/attendee.cpp +++ b/libkcal/attendee.cpp @@ -93,6 +93,9 @@ TQString Attendee::statusName( Attendee::PartStat s ) case InProcess: return i18n("In Process"); break; + case None: + return i18n("attendee status unknown", "Unknown"); + break; } } diff --git a/libkcal/attendee.h b/libkcal/attendee.h index f460665b6..161c77ed2 100644 --- a/libkcal/attendee.h +++ b/libkcal/attendee.h @@ -37,7 +37,7 @@ class LIBKCAL_EXPORT Attendee : public Person { public: enum PartStat { NeedsAction, Accepted, Declined, Tentative, - Delegated, Completed, InProcess }; + Delegated, Completed, InProcess, None }; enum Role { ReqParticipant, OptParticipant, NonParticipant, Chair }; typedef ListBase<Attendee> List; @@ -53,7 +53,7 @@ class LIBKCAL_EXPORT Attendee : public Person @param u the uid for the attendee */ Attendee( const TQString &name, const TQString &email, - bool rsvp = false, PartStat status = NeedsAction, + bool rsvp = false, PartStat status = None, Role role = ReqParticipant, const TQString &u = TQString::null ); /** Destruct Attendee. diff --git a/libkcal/calendar.cpp b/libkcal/calendar.cpp index c3c3a9e65..711cdc12a 100644 --- a/libkcal/calendar.cpp +++ b/libkcal/calendar.cpp @@ -50,6 +50,7 @@ Calendar::Calendar( const TQString &timeZoneId ) void Calendar::init() { + mException = 0; mNewObserver = false; mObserversEnabled = true; @@ -66,9 +67,27 @@ void Calendar::init() Calendar::~Calendar() { + clearException(); delete mDefaultFilter; } +void Calendar::clearException() +{ + delete mException; + mException = 0; +} + +ErrorFormat *Calendar::exception() const +{ + return mException; +} + +void Calendar::setException( ErrorFormat *e ) +{ + delete mException; + mException = e; +} + const Person &Calendar::getOwner() const { return mOwner; @@ -122,6 +141,16 @@ CalFilter *Calendar::filter() return mFilter; } +void Calendar::beginBatchAdding() +{ + emit batchAddingBegins(); +} + +void Calendar::endBatchAdding() +{ + emit batchAddingEnds(); +} + TQStringList Calendar::categories() { Incidence::List rawInc( rawIncidences() ); @@ -161,7 +190,7 @@ Event::List Calendar::sortEvents( Event::List *eventList, SortDirection sortDirection ) { Event::List eventListSorted; - Event::List tempList, t; + Event::List tempList; Event::List alphaList; Event::List::Iterator sortIt; Event::List::Iterator eit; @@ -177,6 +206,10 @@ Event::List Calendar::sortEvents( Event::List *eventList, case EventSortStartDate: alphaList = sortEvents( eventList, EventSortSummary, sortDirection ); for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) { + if ( (*eit)->doesFloat() ) { + tempList.append( *eit ); + continue; + } sortIt = eventListSorted.begin(); if ( sortDirection == SortDirectionAscending ) { while ( sortIt != eventListSorted.end() && @@ -191,6 +224,14 @@ Event::List Calendar::sortEvents( Event::List *eventList, } eventListSorted.insert( sortIt, *eit ); } + if ( sortDirection == SortDirectionAscending ) { + // Prepend the list of all-day Events + tempList += eventListSorted; + eventListSorted = tempList; + } else { + // Append the list of all-day Events + eventListSorted += tempList; + } break; case EventSortEndDate: @@ -245,7 +286,149 @@ Event::List Calendar::sortEvents( Event::List *eventList, } return eventListSorted; +} + +Event::List Calendar::sortEventsForDate( Event::List *eventList, + const TQDate &date, + EventSortField sortField, + SortDirection sortDirection ) +{ + Event::List eventListSorted; + Event::List tempList; + Event::List alphaList; + Event::List::Iterator sortIt; + Event::List::Iterator eit; + + switch( sortField ) { + case EventSortStartDate: + alphaList = sortEvents( eventList, EventSortSummary, sortDirection ); + for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) { + if ( (*eit)->doesFloat() ) { + tempList.append( *eit ); + continue; + } + sortIt = eventListSorted.begin(); + if ( sortDirection == SortDirectionAscending ) { + while ( sortIt != eventListSorted.end() ) { + if ( !(*eit)->doesRecur() ) { + if ( (*eit)->dtStart().time() >= (*sortIt)->dtStart().time() ) { + ++sortIt; + } else { + break; + } + } else { + if ( (*eit)->recursOn( date ) ) { + if ( (*eit)->dtStart().time() >= (*sortIt)->dtStart().time() ) { + ++sortIt; + } else { + break; + } + } else { + ++sortIt; + } + } + } + } else { // descending + while ( sortIt != eventListSorted.end() ) { + if ( !(*eit)->doesRecur() ) { + if ( (*eit)->dtStart().time() < (*sortIt)->dtStart().time() ) { + ++sortIt; + } else { + break; + } + } else { + if ( (*eit)->recursOn( date ) ) { + if ( (*eit)->dtStart().time() < (*sortIt)->dtStart().time() ) { + ++sortIt; + } else { + break; + } + } else { + ++sortIt; + } + } + } + } + eventListSorted.insert( sortIt, *eit ); + } + if ( sortDirection == SortDirectionAscending ) { + // Prepend the list of all-day Events + tempList += eventListSorted; + eventListSorted = tempList; + } else { + // Append the list of all-day Events + eventListSorted += tempList; + } + break; + case EventSortEndDate: + alphaList = sortEvents( eventList, EventSortSummary, sortDirection ); + for ( eit = alphaList.begin(); eit != alphaList.end(); ++eit ) { + if ( (*eit)->hasEndDate() ) { + sortIt = eventListSorted.begin(); + if ( sortDirection == SortDirectionAscending ) { + while ( sortIt != eventListSorted.end() ) { + if ( !(*eit)->doesRecur() ) { + if ( (*eit)->dtEnd().time() >= (*sortIt)->dtEnd().time() ) { + ++sortIt; + } else { + break; + } + } else { + if ( (*eit)->recursOn( date ) ) { + if ( (*eit)->dtEnd().time() >= (*sortIt)->dtEnd().time() ) { + ++sortIt; + } else { + break; + } + } else { + ++sortIt; + } + } + } + } else { // descending + while ( sortIt != eventListSorted.end() ) { + if ( !(*eit)->doesRecur() ) { + if ( (*eit)->dtEnd().time() < (*sortIt)->dtEnd().time() ) { + ++sortIt; + } else { + break; + } + } else { + if ( (*eit)->recursOn( date ) ) { + if ( (*eit)->dtEnd().time() < (*sortIt)->dtEnd().time() ) { + ++sortIt; + } else { + break; + } + } else { + ++sortIt; + } + } + } + } + } else { + // Keep a list of the Events without End DateTimes + tempList.append( *eit ); + } + eventListSorted.insert( sortIt, *eit ); + } + if ( sortDirection == SortDirectionAscending ) { + // Prepend the list of Events without End DateTimes + tempList += eventListSorted; + eventListSorted = tempList; + } else { + // Append the list of Events without End DateTimes + eventListSorted += tempList; + } + break; + + default: + eventListSorted = sortEvents( eventList, sortField, sortDirection ); + break; + } + + return eventListSorted; } Event::List Calendar::events( const TQDate &date, diff --git a/libkcal/calendar.h b/libkcal/calendar.h index a53ef5a06..005b513ef 100644 --- a/libkcal/calendar.h +++ b/libkcal/calendar.h @@ -31,13 +31,7 @@ #ifndef KCAL_CALENDAR_H #define KCAL_CALENDAR_H -#include <tqobject.h> -#include <tqstring.h> -#include <tqdatetime.h> -#include <tqptrlist.h> -#include <tqdict.h> -#include <kdepimmacros.h> - +#include "exceptions.h" #include "customproperties.h" #include "event.h" #include "todo.h" @@ -45,6 +39,14 @@ #include "kcalversion.h" #include "person.h" +#include <kdepimmacros.h> + +#include <tqobject.h> +#include <tqstring.h> +#include <tqdatetime.h> +#include <tqptrlist.h> +#include <tqdict.h> + /** @namespace KCal Namespace KCal is for global classes, objects and/or functions in libkcal. @@ -207,6 +209,17 @@ class LIBKCAL_EXPORT Calendar : public TQObject, public CustomProperties, TQString productId(); /** + Clears the exception status. + */ + void clearException(); + + /** + Returns an exception, if there is any, containing information about the + last error that occurred. + */ + ErrorFormat *exception() const; + + /** Set the owner of the Calendar. @param owner is a Person object. @@ -472,6 +485,22 @@ class LIBKCAL_EXPORT Calendar : public TQObject, public CustomProperties, static Event::List sortEvents( Event::List *eventList, EventSortField sortField, SortDirection sortDirection ); + + /** + Sort a list of Events that occur on a specified date. + + @param eventList is a pointer to a list of Events occurring on @p date. + @param date is the date. + @param sortField specifies the EventSortField. + @param sortDirection specifies the SortDirection. + + @return a list of Events sorted as specified. + */ + static Event::List sortEventsForDate( Event::List *eventList, + const TQDate &date, + EventSortField sortField, + SortDirection sortDirection ); + /** Return a sorted, filtered list of all Events for this Calendar. @@ -755,6 +784,30 @@ class LIBKCAL_EXPORT Calendar : public TQObject, public CustomProperties, */ virtual Journal *journal( const TQString &uid ) = 0; + /** + Emits the beginBatchAdding() signal. + + This should be called before adding a batch of incidences with + addIncidence( Incidence *), addTodo( Todo *), addEvent( Event *) + or addJournal( Journal *). Some Calendars are connected to this + signal, e.g: CalendarResources uses it to know a series of + incidenceAdds are related so the user isn't prompted multiple + times which resource to save the incidence to + + @since 4.4 + */ + void beginBatchAdding(); + + /** + Emits the endBatchAdding() signal. + + Used with beginBatchAdding(). Should be called after + adding all incidences. + + @since 4.4 + */ + void endBatchAdding(); + // Relations Specific Methods // /** @@ -879,8 +932,26 @@ class LIBKCAL_EXPORT Calendar : public TQObject, public CustomProperties, */ void calendarLoaded(); + /** + @see beginBatchAdding() + @since 4.4 + */ + void batchAddingBegins(); + + /** + @see endBatchAdding() + @since 4.4 + */ + void batchAddingEnds(); + protected: /** + Sets information about the last error occurred. + The previous exception is freed. + */ + void setException( ErrorFormat *e ); + + /** The Observer interface. So far not implemented. @param incidenceBase is a pointer an IncidenceBase object. @@ -941,6 +1012,7 @@ class LIBKCAL_EXPORT Calendar : public TQObject, public CustomProperties, // returning static Alarm::List private: + /** Intialize a Calendar object with starting values. */ @@ -964,6 +1036,7 @@ class LIBKCAL_EXPORT Calendar : public TQObject, public CustomProperties, TQDict<Incidence> mOrphans; TQDict<Incidence> mOrphanUids; + ErrorFormat *mException; class Private; Private *d; }; diff --git a/libkcal/calendarlocal.cpp b/libkcal/calendarlocal.cpp index 27b27e0e2..2f2d7739b 100644 --- a/libkcal/calendarlocal.cpp +++ b/libkcal/calendarlocal.cpp @@ -327,7 +327,12 @@ Alarm::List CalendarLocal::alarms( const TQDateTime &from, const TQDateTime &to Todo::List::ConstIterator it2; for( it2 = mTodoList.begin(); it2 != mTodoList.end(); ++it2 ) { - if (! (*it2)->isCompleted() ) appendAlarms( alarms, *it2, from, to ); + Todo *t = *it2; + if ( t->isCompleted() ) { + continue; + } + if ( t->doesRecur() ) appendRecurringAlarms( alarms, t, from, to ); + else appendAlarms( alarms, t, from, to ); } return alarms; @@ -340,13 +345,14 @@ void CalendarLocal::appendAlarms( Alarm::List &alarms, Incidence *incidence, Alarm::List::ConstIterator it; for( it = incidence->alarms().begin(); it != incidence->alarms().end(); ++it ) { - if ( (*it)->enabled() ) { - TQDateTime dt = (*it)->nextRepetition(preTime); + Alarm *alarm = *it; + if ( alarm->enabled() ) { + TQDateTime dt = alarm->nextRepetition( preTime ); if ( dt.isValid() && dt <= to ) { kdDebug(5800) << "CalendarLocal::appendAlarms() '" << incidence->summary() << "': " << dt.toString() << endl; - alarms.append( *it ); + alarms.append( alarm ); } } } @@ -357,10 +363,14 @@ void CalendarLocal::appendRecurringAlarms( Alarm::List &alarms, const TQDateTime &from, const TQDateTime &to ) { - TQDateTime qdt; - int endOffset = 0; + TQDateTime dt; + Duration endOffset( 0 ); bool endOffsetValid = false; - int period = from.secsTo(to); + Duration period( from, to ); + + Event *e = static_cast<Event *>( incidence ); + Todo *t = static_cast<Todo *>( incidence ); + Alarm::List::ConstIterator it; for( it = incidence->alarms().begin(); it != incidence->alarms().end(); ++it ) { @@ -368,59 +378,118 @@ void CalendarLocal::appendRecurringAlarms( Alarm::List &alarms, if ( alarm->enabled() ) { if ( alarm->hasTime() ) { // The alarm time is defined as an absolute date/time - qdt = alarm->nextRepetition( from.addSecs(-1) ); - if ( !qdt.isValid() || qdt > to ) + dt = alarm->nextRepetition( from.addSecs(-1) ); + if ( !dt.isValid() || dt > to ) { continue; + } } else { - // The alarm time is defined by an offset from the event start or end time. + // Alarm time is defined by an offset from the event start or end time. // Find the offset from the event start time, which is also used as the // offset from the recurrence time. - int offset = 0; + Duration offset( 0 ); if ( alarm->hasStartOffset() ) { offset = alarm->startOffset().asSeconds(); } else if ( alarm->hasEndOffset() ) { + offset = alarm->endOffset().asSeconds(); if ( !endOffsetValid ) { - endOffset = incidence->dtStart().secsTo( incidence->dtEnd() ); - endOffsetValid = true; + if ( incidence->type() == "Event" ) { + endOffset = Duration( e->dtStart(), e->dtEnd() ); + endOffsetValid = true; + } else if ( incidence->type() == "Todo" && + t->hasStartDate() && t->hasDueDate() ) { + endOffset = Duration( t->dtStart(), t->dtDue() ); + endOffsetValid = true; + } } - offset = alarm->endOffset().asSeconds() + endOffset; } - // Adjust the 'from' date/time and find the next recurrence at or after it - qdt = incidence->recurrence()->getNextDateTime( from.addSecs(-offset - 1) ); - if ( !qdt.isValid() - || (qdt = qdt.addSecs( offset )) > to ) // remove the adjustment to get the alarm time + // Find the incidence's earliest alarm + TQDateTime alarmStart; + if ( incidence->type() == "Event" ) { + alarmStart = + offset.end( alarm->hasEndOffset() ? e->dtEnd() : e->dtStart() ); + } else if ( incidence->type() == "Todo" ) { + alarmStart = + offset.end( alarm->hasEndOffset() ? t->dtDue() : t->dtStart() ); + } + + if ( alarmStart.isValid() && alarmStart > to ) { + continue; + } + + TQDateTime baseStart; + if ( incidence->type() == "Event" ) { + baseStart = e->dtStart(); + } else if ( incidence->type() == "Todo" ) { + baseStart = t->dtDue(); + } + if ( alarmStart.isValid() && from > alarmStart ) { + alarmStart = from; // don't look earlier than the earliest alarm + baseStart = (-offset).end( (-endOffset).end( alarmStart ) ); + } + + // Adjust the 'alarmStart' date/time and find the next recurrence + // at or after it. Treat the two offsets separately in case one + // is daily and the other not. + dt = incidence->recurrence()->getNextDateTime( baseStart.addSecs(-1) ); + if ( !dt.isValid() || + ( dt = endOffset.end( offset.end( dt ) ) ) > to ) // adjust 'dt' to get the alarm time { // The next recurrence is too late. - if ( !alarm->repeatCount() ) + if ( !alarm->repeatCount() ) { continue; - // The alarm has repetitions, so check whether repetitions of previous - // recurrences fall within the time period. + } + + // The alarm has repetitions, so check whether repetitions of + // previous recurrences fall within the time period. bool found = false; - qdt = from.addSecs( -offset ); - while ( (qdt = incidence->recurrence()->getPreviousDateTime( qdt )).isValid() ) { - int toFrom = qdt.secsTo( from ) - offset; - if ( toFrom > alarm->duration() ) - break; // this recurrence's last repetition is too early, so give up - // The last repetition of this recurrence is at or after 'from' time. - // Check if a repetition occurs between 'from' and 'to'. - int snooze = alarm->snoozeTime() * 60; // in seconds - if ( period >= snooze - || toFrom % snooze == 0 - || (toFrom / snooze + 1) * snooze <= toFrom + period ) { - found = true; + Duration alarmDuration = alarm->duration(); + for ( TQDateTime base = baseStart; + ( dt = incidence->recurrence()->getPreviousDateTime( base ) ).isValid(); + base = dt ) { + if ( alarm->duration().end( dt ) < base ) { + break; // recurrence's last repetition is too early, so give up + } + + // The last repetition of this recurrence is on or after + // 'alarmStart' time. Check if a repetition occurs between + // 'alarmStart' and 'to'. + int snooze = alarm->snoozeTime().value(); // in seconds or days + if ( alarm->snoozeTime().isDaily() ) { + Duration toFromDuration( dt, base ); + int toFrom = toFromDuration.asDays(); + if ( alarm->snoozeTime().end( from ) <= to || + ( toFromDuration.isDaily() && toFrom % snooze == 0 ) || + ( toFrom / snooze + 1 ) * snooze <= toFrom + period.asDays() ) { + found = true; +#ifndef NDEBUG + // for debug output + dt = offset.end( dt ).addDays( ( ( toFrom - 1 ) / snooze + 1 ) * snooze ); +#endif + break; + } + } else { + int toFrom = dt.secsTo( base ); + if ( period.asSeconds() >= snooze || + toFrom % snooze == 0 || + ( toFrom / snooze + 1 ) * snooze <= toFrom + period.asSeconds() ) + { + found = true; #ifndef NDEBUG - qdt = qdt.addSecs( offset + ((toFrom-1) / snooze + 1) * snooze ); // for debug output + // for debug output + dt = offset.end( dt ).addSecs( ( ( toFrom - 1 ) / snooze + 1 ) * snooze ); #endif - break; + break; + } } } - if ( !found ) + if ( !found ) { continue; + } } } kdDebug(5800) << "CalendarLocal::appendAlarms() '" << incidence->summary() - << "': " << qdt.toString() << endl; + << "': " << dt.toString() << endl; alarms.append( alarm ); } } @@ -485,7 +554,7 @@ Event::List CalendarLocal::rawEventsForDate( const TQDate &qd, } } - return sortEvents( &eventList, sortField, sortDirection ); + return sortEventsForDate( &eventList, qd, sortField, sortDirection ); } Event::List CalendarLocal::rawEvents( const TQDate &start, const TQDate &end, diff --git a/libkcal/calendarresources.cpp b/libkcal/calendarresources.cpp index 42f618dc3..abd1cc213 100644 --- a/libkcal/calendarresources.cpp +++ b/libkcal/calendarresources.cpp @@ -55,6 +55,35 @@ using namespace KCal; + +class CalendarResources::Private { + public: + + Private() : mLastUsedResource( 0 ), mBatchAddingInProgress( false ) + { + } + + ResourceCalendar *mLastUsedResource; + bool mBatchAddingInProgress; +}; + +bool CalendarResources::DestinationPolicy::hasCalendarResources( ) +{ + CalendarResourceManager::ActiveIterator it; + for ( it = resourceManager()->activeBegin(); + it != resourceManager()->activeEnd(); ++it ) { + if ( !(*it)->readOnly() ) { + //Insert the first the Standard resource to get be the default selected. + if ( resourceManager()->standardResource() == *it ) { + return true; + } else { + return true; + } + } + } + return false; +} + ResourceCalendar *CalendarResources::StandardDestinationPolicy::destination( Incidence * ) { @@ -85,7 +114,7 @@ ResourceCalendar CalendarResources::CalendarResources( const TQString &timeZoneId, const TQString &family ) - : Calendar( timeZoneId ) + : Calendar( timeZoneId ), d( new Private() ) { init( family ); } @@ -100,6 +129,10 @@ void CalendarResources::init( const TQString &family ) mStandardPolicy = new StandardDestinationPolicy( mManager ); mAskPolicy = new AskDestinationPolicy( mManager ); mDestinationPolicy = mStandardPolicy; + mPendingDeleteFromResourceMap = false; + + connect( this, TQT_SIGNAL(batchAddingBegins()), this, TQT_SLOT(beginAddingIncidences()) ); + connect( this, TQT_SIGNAL(batchAddingEnds()), this, TQT_SLOT(endAddingIncidences()) ); } CalendarResources::~CalendarResources() @@ -158,6 +191,7 @@ void CalendarResources::load() } mOpen = true; + emit calendarLoaded(); } bool CalendarResources::reload( const TQString &tz ) @@ -276,7 +310,8 @@ bool CalendarResources::isSaving() } bool CalendarResources::addIncidence( Incidence *incidence, - ResourceCalendar *resource ) + ResourceCalendar *resource, + const TQString &subresource ) { // FIXME: Use proper locking via begin/endChange! bool validRes = false; @@ -285,52 +320,90 @@ bool CalendarResources::addIncidence( Incidence *incidence, if ( (*it) == resource ) validRes = true; } + + kdDebug(5800)<< "CalendarResources: validRes is " << validRes << endl; + ResourceCalendar *oldResource = 0; if ( mResourceMap.contains( incidence ) ) { oldResource = mResourceMap[incidence]; } mResourceMap[incidence] = resource; - if ( validRes && beginChange( incidence ) && - resource->addIncidence( incidence ) ) { -// mResourceMap[incidence] = resource; + if ( validRes && beginChange( incidence, resource, subresource ) && + resource->addIncidence( incidence, subresource ) ) { + // mResourceMap[incidence] = resource; incidence->registerObserver( this ); notifyIncidenceAdded( incidence ); setModified( true ); - endChange( incidence ); + endChange( incidence, resource, subresource ); return true; } else { - if ( oldResource ) + if ( oldResource ) { mResourceMap[incidence] = oldResource; - else + } else { mResourceMap.remove( incidence ); + } } return false; } +bool CalendarResources::addIncidence( Incidence *incidence, + ResourceCalendar *resource ) +{ + return addIncidence( incidence, resource, TQString() ); +} + bool CalendarResources::addIncidence( Incidence *incidence ) { - kdDebug(5800) << "CalendarResources::addIncidence" << this << endl; + kdDebug(5800) << "CalendarResources::addIncidence " + << incidence->summary() + << "; addingInProgress = " << d->mBatchAddingInProgress + << "; lastUsedResource = " << d->mLastUsedResource + << endl; - ResourceCalendar *resource = mDestinationPolicy->destination( incidence ); + clearException(); - if ( resource ) { - mResourceMap[ incidence ] = resource; + ResourceCalendar *resource = d->mLastUsedResource; + + if ( !d->mBatchAddingInProgress || d->mLastUsedResource == 0 ) { + resource = mDestinationPolicy->destination( incidence ); + d->mLastUsedResource = resource; + + if ( resource && d->mBatchAddingInProgress ) { + d->mLastUsedResource->beginAddingIncidences(); + } + } - if ( beginChange( incidence ) && resource->addIncidence( incidence ) ) { + if ( resource ) { + kdDebug(5800) << "CalendarResources:: resource= " + << resource->resourceName() + << " with id = " << resource->identifier() + << " with type = " << resource->type() + << endl; + mResourceMap[incidence] = resource; + + if ( beginChange( incidence, resource, TQString() ) && + resource->addIncidence( incidence ) ) { incidence->registerObserver( this ); notifyIncidenceAdded( incidence ); - mResourceMap[ incidence ] = resource; setModified( true ); - endChange( incidence ); + endChange( incidence, resource, TQString() ); return true; } else { + if ( resource->exception() ) { + setException( new ErrorFormat( resource->exception()->errorCode() ) ); + } + + // the incidence isn't going to be added, do cleanup: mResourceMap.remove( incidence ); + d->mLastUsedResource->endAddingIncidences(); + d->mLastUsedResource = 0; } - } else - kdDebug(5800) << "CalendarResources::addIncidence(): no resource" << endl; + } else { + setException( new ErrorFormat( ErrorFormat::UserCancel ) ); + } return false; } @@ -343,7 +416,13 @@ bool CalendarResources::addEvent( Event *event ) bool CalendarResources::addEvent( Event *Event, ResourceCalendar *resource ) { - return addIncidence( Event, resource ); + return addIncidence( Event, resource, TQString() ); +} + +bool CalendarResources::addEvent( Event *Event, ResourceCalendar *resource, + const TQString &subresource ) +{ + return addIncidence( Event, resource, subresource ); } bool CalendarResources::deleteEvent( Event *event ) @@ -354,7 +433,7 @@ bool CalendarResources::deleteEvent( Event *event ) if ( mResourceMap.find( event ) != mResourceMap.end() ) { status = mResourceMap[event]->deleteEvent( event ); if ( status ) - mResourceMap.remove( event ); + mPendingDeleteFromResourceMap = true; } else { status = false; CalendarResourceManager::ActiveIterator it; @@ -363,6 +442,10 @@ bool CalendarResources::deleteEvent( Event *event ) } } + if ( status ) { + notifyIncidenceDeleted( event ); + } + setModified( status ); return status; } @@ -390,7 +473,13 @@ bool CalendarResources::addTodo( Todo *todo ) bool CalendarResources::addTodo( Todo *todo, ResourceCalendar *resource ) { - return addIncidence( todo, resource ); + return addIncidence( todo, resource, TQString() ); +} + +bool CalendarResources::addTodo( Todo *todo, ResourceCalendar *resource, + const TQString &subresource ) +{ + return addIncidence( todo, resource, subresource ); } bool CalendarResources::deleteTodo( Todo *todo ) @@ -401,7 +490,7 @@ bool CalendarResources::deleteTodo( Todo *todo ) if ( mResourceMap.find( todo ) != mResourceMap.end() ) { status = mResourceMap[todo]->deleteTodo( todo ); if ( status ) - mResourceMap.remove( todo ); + mPendingDeleteFromResourceMap = true; } else { CalendarResourceManager::ActiveIterator it; status = false; @@ -494,6 +583,11 @@ Alarm::List CalendarResources::alarms( const TQDateTime &from, return result; } +bool CalendarResources::hasCalendarResources() +{ + return mDestinationPolicy->hasCalendarResources(); +} + /****************************** PROTECTED METHODS ****************************/ Event::List CalendarResources::rawEventsForDate( const TQDate &date, @@ -510,7 +604,7 @@ Event::List CalendarResources::rawEventsForDate( const TQDate &date, mResourceMap[ *it2 ] = *it; } } - return sortEvents( &result, sortField, sortDirection ); + return sortEventsForDate( &result, date, sortField, sortDirection ); } Event::List CalendarResources::rawEvents( const TQDate &start, const TQDate &end, @@ -574,6 +668,19 @@ bool CalendarResources::addJournal( Journal *journal ) return addIncidence( journal ); } +bool CalendarResources::addJournal( Journal *journal, ResourceCalendar *resource ) +{ + return addIncidence( journal, resource, TQString() ); +} + + +bool CalendarResources::addJournal( Journal *journal, ResourceCalendar *resource, + const TQString &subresource ) +{ + return addIncidence( journal, resource, subresource ); +} + + bool CalendarResources::deleteJournal( Journal *journal ) { kdDebug(5800) << "CalendarResources::deleteJournal" << endl; @@ -582,7 +689,7 @@ bool CalendarResources::deleteJournal( Journal *journal ) if ( mResourceMap.find( journal ) != mResourceMap.end() ) { status = mResourceMap[journal]->deleteJournal( journal ); if ( status ) - mResourceMap.remove( journal ); + mPendingDeleteFromResourceMap = true; } else { CalendarResourceManager::ActiveIterator it; status = false; @@ -595,13 +702,6 @@ bool CalendarResources::deleteJournal( Journal *journal ) return status; } -bool CalendarResources::addJournal( Journal *journal, - ResourceCalendar *resource - ) -{ - return addIncidence( journal, resource ); -} - Journal *CalendarResources::journal( const TQString &uid ) { kdDebug(5800) << "CalendarResources::journal(uid)" << endl; @@ -762,28 +862,40 @@ void CalendarResources::releaseSaveTicket( Ticket *ticket ) bool CalendarResources::beginChange( Incidence *incidence ) { + return beginChange( incidence, 0, TQString() ); +} + +bool CalendarResources::beginChange( Incidence *incidence, + ResourceCalendar *res, + const TQString &subres ) +{ + Q_UNUSED( subres ); // possible future use + kdDebug(5800) << "CalendarResources::beginChange()" << endl; - ResourceCalendar *r = resource( incidence ); - if ( !r ) { - r = mDestinationPolicy->destination( incidence ); - if ( !r ) { + if ( !res ) { + res = resource( incidence ); + } + if ( !res ) { + res = mDestinationPolicy->destination( incidence ); + if ( !res ) { kdError() << "Unable to get destination resource." << endl; return false; } - mResourceMap[ incidence ] = r; + mResourceMap[ incidence ] = res; } + mPendingDeleteFromResourceMap = false; - int count = incrementChangeCount( r ); + int count = incrementChangeCount( res ); if ( count == 1 ) { - Ticket *ticket = requestSaveTicket( r ); + Ticket *ticket = requestSaveTicket( res ); if ( !ticket ) { kdDebug(5800) << "CalendarResources::beginChange(): unable to get ticket." << endl; - decrementChangeCount( r ); + decrementChangeCount( res ); return false; } else { - mTickets[ r ] = ticket; + mTickets[ res ] = ticket; } } @@ -792,18 +904,34 @@ bool CalendarResources::beginChange( Incidence *incidence ) bool CalendarResources::endChange( Incidence *incidence ) { + return endChange( incidence, 0, TQString() ); +} + +bool CalendarResources::endChange( Incidence *incidence, + ResourceCalendar *res, + const TQString &subres ) +{ + Q_UNUSED( subres ); // possible future use + kdDebug(5800) << "CalendarResource::endChange()" << endl; - ResourceCalendar *r = resource( incidence ); - if ( !r ) + if ( !res ) { + res = resource( incidence ); + } + if ( !res ) return false; - int count = decrementChangeCount( r ); + int count = decrementChangeCount( res ); + + if ( mPendingDeleteFromResourceMap ) { + mResourceMap.remove( incidence ); + mPendingDeleteFromResourceMap = false; + } if ( count == 0 ) { - bool ok = save( mTickets[ r ], incidence ); + bool ok = save( mTickets[ res ], incidence ); if ( ok ) { - mTickets.remove( r ); + mTickets.remove( res ); } else { return false; } @@ -812,6 +940,24 @@ bool CalendarResources::endChange( Incidence *incidence ) return true; } +void CalendarResources::beginAddingIncidences() +{ + kdDebug(5800) << "CalendarResources: beginAddingIncidences() " << endl; + d->mBatchAddingInProgress = true; +} + +void CalendarResources::endAddingIncidences() +{ + kdDebug(5800) << "CalendarResources: endAddingIncidences() " << endl; + d->mBatchAddingInProgress = false; + + if ( d->mLastUsedResource ) { + d->mLastUsedResource->endAddingIncidences(); + } + + d->mLastUsedResource = 0; +} + int CalendarResources::incrementChangeCount( ResourceCalendar *r ) { if ( !mChangeCounts.contains( r ) ) { diff --git a/libkcal/calendarresources.h b/libkcal/calendarresources.h index 32c48ba41..a78b77bbc 100644 --- a/libkcal/calendarresources.h +++ b/libkcal/calendarresources.h @@ -80,7 +80,7 @@ class LIBKCAL_EXPORT CalendarResources : virtual ResourceCalendar *destination( Incidence *incidence ) = 0; virtual TQWidget *parent() { return mParent; } virtual void setParent( TQWidget *newparent ) { mParent = newparent; } - + bool hasCalendarResources(); protected: CalendarResourceManager *resourceManager() { return mManager; } @@ -268,20 +268,20 @@ class LIBKCAL_EXPORT CalendarResources : Resource which is queried. */ void setAskDestinationPolicy(); - - /** + + /** Returns the current parent for new dialogs. This is a bad hack, but we need to properly set the parent for the resource selection dialog. Otherwise - the dialog will not be modal to the editor dialog in korganizer and + the dialog will not be modal to the editor dialog in korganizer and the user can still work in the editor dialog (and thus crash korganizer). - Afterwards we need to reset it (to avoid pointers to widgets that are + Afterwards we need to reset it (to avoid pointers to widgets that are already deleted) so we also need the accessor */ TQWidget *dialogParentWidget(); - /** + /** Set the widget parent for new dialogs. This is a bad hack, but we need to properly set the parent for the resource selection dialog. Otherwise - the dialog will not be modal to the editor dialog in korganizer and + the dialog will not be modal to the editor dialog in korganizer and the user can still work in the editor dialog (and thus crash korganizer). */ void setDialogParentWidget( TQWidget *parent ); @@ -333,22 +333,70 @@ class LIBKCAL_EXPORT CalendarResources : @param resource is a pointer to the ResourceCalendar to be added to. @return true if the Incidence was successfully inserted; false otherwise. + @deprecated use + addIncidence(Incidence *,ResourceCalendar *,const TQString &) instead. + */ + KDE_DEPRECATED bool addIncidence( Incidence *incidence, ResourceCalendar *resource ); + + /** + Insert an Incidence into a Calendar Resource. + + @param incidence is a pointer to the Incidence to insert. + @param resource is a pointer to the ResourceCalendar to be added to. + @param subresource is the subresource name, which may not be used + by some calendar resources. + + @return true if the Incidence was successfully inserted; false otherwise. */ - bool addIncidence( Incidence *incidence, ResourceCalendar *resource ); + bool addIncidence( Incidence *incidence, + ResourceCalendar *resource, const TQString &subresource ); /** Flag that a change to a Calendar Incidence is starting. + @param incidence is a pointer to the Incidence that will be changing. + + @return false if the resource could not be computed or if a ticket + request fails; true otherwise. + */ + KDE_DEPRECATED bool beginChange( Incidence *incidence ); + /** + Flag that a change to a Calendar Incidence is starting. @param incidence is a pointer to the Incidence that will be changing. + @param resource is a pointer to the ResourceCalendar that @p incidence + belongs to; if this is 0 then the resource is queried via the + DestinationPolicy. + @param subresource is the @p Incidence subresource name, which may not + be used by some calendar resources. + + @return false if the resource could not be computed or if a ticket + request fails; true otherwise. */ - bool beginChange( Incidence *incidence ); + bool beginChange( Incidence *incidence, ResourceCalendar *resource, const TQString &subresource ); /** Flag that a change to a Calendar Incidence has completed. + @param incidence is a pointer to the Incidence that was changed. + @return false if the resource could not be computed or if a ticket + save fails; true otherwise. + */ + KDE_DEPRECATED bool endChange( Incidence *incidence ); + + /** + Flag that a change to a Calendar Incidence has completed. @param incidence is a pointer to the Incidence that was changed. + @param resource is a pointer to the ResourceCalendar that @p incidence + belongs to; if this is 0 then the resource is queried via the + DestinationPolicy. + @param subresource is the @p Incidence subresource name, which may not + be used by some calendar resources. + + @return false if the resource could not be computed or if a ticket + save fails; true otherwise. */ - bool endChange( Incidence *incidence ); + bool endChange( Incidence *incidence, + ResourceCalendar *resource, const TQString &subresource ); // Event Specific Methods // @@ -372,10 +420,25 @@ class LIBKCAL_EXPORT CalendarResources : @return true if the Event was successfully inserted; false otherwise. + @deprecated use + addIncidence(Incidence *,ResourceCalendar *,const TQString&) instead. + */ + KDE_DEPRECATED bool addEvent( Event *event, ResourceCalendar *resource ); + + /** + Insert an Event into a Calendar Resource. + + @param event is a pointer to the Event to insert. + @param resource is a pointer to the ResourceCalendar to be added to. + @param subresource is the subresource name, which may not be used + by some calendar resources. + + @return true if the Event was successfully inserted; false otherwise. + @note In most cases use - addIncidence( Incidence *incidence, ResourceCalendar *resource ) instead. + addIncidence(Incidence *,ResourceCalendar *,const TQString &) instead. */ - bool addEvent( Event *event, ResourceCalendar *resource ); + bool addEvent( Event *event, ResourceCalendar *resource, const TQString &subresource ); /** Remove an Event from the Calendar. @@ -474,10 +537,25 @@ class LIBKCAL_EXPORT CalendarResources : @return true if the Todo was successfully inserted; false otherwise. + @deprecated use + addIncidence(Incidence *,ResourceCalendar *,const TQString &) instead. + */ + KDE_DEPRECATED bool addTodo( Todo *todo, ResourceCalendar *resource ); + + /** + Insert an Todo into a Calendar Resource. + + @param todo is a pointer to the Todo to insert. + @param resource is a pointer to the ResourceCalendar to be added to. + @param subresource is the subresource name, which may not be used + by some calendar resources. + + @return true if the Todo was successfully inserted; false otherwise. + @note In most cases use - addIncidence( Incidence *incidence, ResourceCalendar *resource ) instead. + addIncidence(Incidence *, ResourceCalendar *,const TQString &) instead. */ - bool addTodo( Todo *todo, ResourceCalendar *resource ); + bool addTodo( Todo *todo, ResourceCalendar *resource, const TQString &subresource ); /** Remove an Todo from the Calendar. @@ -544,10 +622,25 @@ class LIBKCAL_EXPORT CalendarResources : @return true if the Journal was successfully inserted; false otherwise. + @deprecated use + addIncidence(Incidence *,ResourceCalendar *,const TQString &) instead. + */ + KDE_DEPRECATED bool addJournal( Journal *journal, ResourceCalendar *resource ); + + /** + Insert a Journal into a Calendar Resource. + + @param journal is a pointer to the Journal to insert. + @param resource is a pointer to the ResourceCalendar to be added to. + @param subresource is the subresource name, which may not be used + by some calendar resources. + + @return true if the Journal was successfully inserted; false otherwise. + @note In most cases use - addIncidence( Incidence *incidence, ResourceCalendar *resource ) instead. + addIncidence(Incidence *,ResourceCalendar *,const TQString &) instead. */ - bool addJournal( Journal *journal, ResourceCalendar *resource ); + bool addJournal( Journal *journal, ResourceCalendar *resource, const TQString &subresource ); /** Remove a Journal from the Calendar. @@ -622,6 +715,8 @@ class LIBKCAL_EXPORT CalendarResources : */ void setTimeZoneIdViewOnly( const TQString& tz ); + //issue 2508 + bool hasCalendarResources(); signals: /** Signal that the Resource has been modified. @@ -684,7 +779,22 @@ class LIBKCAL_EXPORT CalendarResources : void slotLoadError( ResourceCalendar *resource, const TQString &err ); void slotSaveError( ResourceCalendar *resource, const TQString &err ); + /** + All addIncidence( Incidence * ), addTodo( Todo * ) addEvent( Event * ) + and addJournal( Journal * ) calls made between beginAddingIncidences() + and endAddingIncidences() will only ask the user to choose a resource once. + @since 4.4 + */ + void beginAddingIncidences(); + + /** + @see beginAddingIncidences() + @since 4.4 + */ + void endAddingIncidences(); + private: + /** Initialize the Resource object with starting values. */ @@ -698,6 +808,7 @@ class LIBKCAL_EXPORT CalendarResources : DestinationPolicy *mDestinationPolicy; StandardDestinationPolicy *mStandardPolicy; AskDestinationPolicy *mAskPolicy; + bool mPendingDeleteFromResourceMap; TQMap<ResourceCalendar *, Ticket *> mTickets; TQMap<ResourceCalendar *, int> mChangeCounts; diff --git a/libkcal/calformat.cpp b/libkcal/calformat.cpp index 6ec0260cd..e506b8dfe 100644 --- a/libkcal/calformat.cpp +++ b/libkcal/calformat.cpp @@ -47,7 +47,7 @@ void CalFormat::clearException() mException = 0; } -void CalFormat::setException(ErrorFormat *exception) +void CalFormat::setException( ErrorFormat *exception ) { delete mException; mException = exception; diff --git a/libkcal/calformat.h b/libkcal/calformat.h index 59c585538..b3b7160db 100644 --- a/libkcal/calformat.h +++ b/libkcal/calformat.h @@ -95,7 +95,7 @@ class LIBKCAL_EXPORT CalFormat Set exception for this object. This is used by the functions of this class to report errors. */ - void setException(ErrorFormat *error); + void setException( ErrorFormat *error ); protected: TQString mLoadedProductId; // PRODID string loaded from calendar file diff --git a/libkcal/calhelper.cpp b/libkcal/calhelper.cpp new file mode 100644 index 000000000..cae5c102f --- /dev/null +++ b/libkcal/calhelper.cpp @@ -0,0 +1,167 @@ +/* + This file is part of the kcal library. + + Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> + + 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 convenience functions for making decisions about calendar data. + + @brief + Provides methods for making decisions about calendar data. + + @author Allen Winter \<allen@kdab.net\> +*/ + +#include "calhelper.h" +#include "calendarresources.h" + +using namespace KCal; + +bool CalHelper::isMyKolabIncidence( Calendar *calendar, Incidence *incidence ) +{ + CalendarResources *cal = dynamic_cast<CalendarResources*>( calendar ); + if ( !cal || !incidence ) { + return true; + } + + CalendarResourceManager *manager = cal->resourceManager(); + CalendarResourceManager::Iterator it; + for ( it = manager->begin(); it != manager->end(); ++it ) { + TQString subRes = (*it)->subresourceIdentifier( incidence ); + if ( !subRes.isEmpty() && !subRes.contains( "/.INBOX.directory/" ) ) { + return false; + } + } + return true; +} + +bool CalHelper::isMyCalendarIncidence( Calendar *calendar, Incidence *incidence ) +{ + return isMyKolabIncidence( calendar, incidence ); +} + +Incidence *CalHelper::findMyCalendarIncidenceByUid( Calendar *calendar, const TQString &uid ) +{ + // Determine if this incidence is in my calendar (and owned by me) + Incidence *existingIncidence = 0; + if ( calendar ) { + existingIncidence = calendar->incidence( uid ); + if ( !isMyCalendarIncidence( calendar, existingIncidence ) ) { + existingIncidence = 0; + } + if ( !existingIncidence ) { + const Incidence::List list = calendar->incidences(); + for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) { + if ( (*it)->schedulingID() == uid && isMyCalendarIncidence( calendar, *it ) ) { + existingIncidence = *it; + break; + } + } + } + } + return existingIncidence; +} + +bool CalHelper::usingGroupware( Calendar *calendar ) +{ + CalendarResources *cal = dynamic_cast<CalendarResources*>( calendar ); + if ( !cal ) { + return true; + } + + CalendarResourceManager *manager = cal->resourceManager(); + CalendarResourceManager::Iterator it; + for ( it = manager->begin(); it != manager->end(); ++it ) { + TQString res = (*it)->type(); + if ( res == "imap" ) { + return true; + } + } + return false; +} + +bool CalHelper::hasMyWritableEventsFolders( const TQString &family ) +{ + TQString myfamily = family; + if ( family.isEmpty() ) { + myfamily = "calendar"; + } + + CalendarResourceManager manager( myfamily ); + manager.readConfig(); + + CalendarResourceManager::ActiveIterator it; + for ( it=manager.activeBegin(); it != manager.activeEnd(); ++it ) { + if ( (*it)->readOnly() ) { + continue; + } + + const TQStringList subResources = (*it)->subresources(); + if ( subResources.isEmpty() ) { + return true; + } + + TQStringList::ConstIterator subIt; + for ( subIt=subResources.begin(); subIt != subResources.end(); ++subIt ) { + if ( !(*it)->subresourceActive( (*subIt) ) ) { + continue; + } + if ( (*it)->type() == "imap" || (*it)->type() == "kolab" ) { + if ( (*it)->subresourceType( ( *subIt ) ) == "todo" || + (*it)->subresourceType( ( *subIt ) ) == "journal" || + !(*subIt).contains( "/.INBOX.directory/" ) ) { + continue; + } + } + return true; + } + } + return false; +} + +ResourceCalendar *CalHelper::incResourceCalendar( Calendar *calendar, Incidence *incidence ) +{ + CalendarResources *cal = dynamic_cast<CalendarResources*>( calendar ); + if ( !cal || !incidence ) { + return 0; + } + + return cal->resource( incidence ); +} + +QPair<ResourceCalendar *, TQString> CalHelper::incSubResourceCalendar( Calendar *calendar, + Incidence *incidence ) +{ + QPair<ResourceCalendar *, TQString> p( 0, TQString() ); + + CalendarResources *cal = dynamic_cast<CalendarResources*>( calendar ); + if ( !cal || !incidence ) { + return p; + } + + ResourceCalendar *res = cal->resource( incidence ); + + TQString subRes; + if ( res && res->canHaveSubresources() ) { + subRes = res->subresourceIdentifier( incidence ); + } + p = qMakePair( res, subRes ); + return p; +} diff --git a/libkcal/calhelper.h b/libkcal/calhelper.h new file mode 100644 index 000000000..edebecf92 --- /dev/null +++ b/libkcal/calhelper.h @@ -0,0 +1,135 @@ +/* + This file is part of libkcal. + + Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> + + 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 convenience functions for making decisions about calendar data. + + @author Allen Winter \<allen@kdab.net\> +*/ + +#ifndef KCAL_CALHELPER_H +#define KCAL_CALHELPER_H + +class TQString; +#include <tqpair.h> + +namespace KCal { +class Calendar; +class Incidence; +class ResourceCalendar; + +/** + @brief + Provides methods for making decisions about calendar data. +*/ +namespace CalHelper { + + /** + Determine if the specified incidence is likely a Kolab incidence + owned by the the user. + + @param calendar is a pointer to a valid Calendar object. + @param incidence is a pointer to an Incidence object. + + @return true if it is likely that the specified incidence belongs + to the user in their Kolab resource; false otherwise. + */ + bool isMyKolabIncidence( Calendar *calendar, Incidence *incidence ); + + /** + Determine if the specified incidence is likely owned by the the user, + independent of the Resource type. + + @param calendar is a pointer to a valid Calendar object. + @param incidence is a pointer to an Incidence object. + + @return true if it is likely that the specified incidence belongs + to the user; false otherwise. + */ + bool isMyCalendarIncidence( Calendar *calendar, Incidence *incidence ); + + /** + Searches for the specified Incidence by UID, returning an Incidence pointer + if and only if the found Incidence is owned by the user. + + @param calendar is a pointer to a valid Calendar object. + @param Uid is a TQString containing an Incidence UID. + + @return a pointer to the Incidence found; 0 if the Incidence is not found + or the Incidence is found but is not owned by the user. + */ + Incidence *findMyCalendarIncidenceByUid( Calendar *calendar, const TQString &uid ); + + /** + Determines if the Calendar is using a Groupware resource type. + @param calendar is a pointer to a valid Calendar object. + + @return true if the Calendar is using a known Groupware resource type; + false otherwise. + @since 4.4 + */ + bool usingGroupware( Calendar *calendar ); + + /** + Determines if the Calendar has any writable folders with Events content + that are owned by me. + @param family is the resource family name or "calendar" if empty. + + @return true if the any such writable folders are found; false otherwise. + @since 4.5 + */ + bool hasMyWritableEventsFolders( const TQString &family ); + + /** + Returns the ResourceCalendar where the Incidence is stored, if any. + @param calendar is a pointer to a valid Calendar object. + @param incidence is a pointer to an Incidence object. + + @return a pointer to the ResourceCalendar where the Incidence is stored; + else 0 if none can be found. + @since 4.5 + */ + ResourceCalendar *incResourceCalendar( Calendar *calendar, Incidence *incidence ); + + /** + Returns the (ResourceCalendar, SubResourceCalendar) pair where the + Incidence is stored, if any. + @param calendar is a pointer to a valid Calendar object. + @param incidence is a pointer to an Incidence object. + + @return a QPair containing a pointer to the Incidence's ResourceCalendar + in the 'first' element of the QPair and the SubResourceCalendar in the + 'second' element. + + @note many resource types do not support subresources, so the 'second' + element will be an empty TQString in those situations. + @since 4.5 + */ + QPair<ResourceCalendar *, TQString> incSubResourceCalendar( Calendar *calendar, + Incidence *incidence ); + +} + +} + +#endif + diff --git a/libkcal/calselectdialog.cpp b/libkcal/calselectdialog.cpp new file mode 100644 index 000000000..9c346f483 --- /dev/null +++ b/libkcal/calselectdialog.cpp @@ -0,0 +1,101 @@ +/* + This file is part of libkcal. + + Copyright (c) 2008 Kevin Ottens <ervin@kde.org> + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ +/** + @file + This file is part of the API for handling calendar data and provides a + dialog for asking the user to select from a list of available calendars. + + @brief + Calendar selector dialog. + + @author Kevin Ottens \<ervin@kde.org\> + @author Allen Winter \<allen@kdab.net\> +*/ + +#include "calselectdialog.h" + +#include <tqlabel.h> +#include <tqlayout.h> + +using namespace KCal; + +CalSelectDialog::CalSelectDialog( const TQString &caption, const TQString &label, + const TQStringList &list ) + : KDialogBase( 0, 0, true, caption, Ok|Cancel, Ok, true ) +{ + TQFrame *frame = makeMainWidget(); + TQVBoxLayout *layout = new TQVBoxLayout( frame, 0, spacingHint() ); + + TQLabel *labelWidget = new TQLabel( label, frame ); + layout->addWidget( labelWidget ); + + mListBox = new KListBox( frame ); + mListBox->insertStringList( list ); + mListBox->setSelected( 0, true ); + mListBox->ensureCurrentVisible(); + layout->addWidget( mListBox, 10 ); + + connect( mListBox, TQT_SIGNAL(doubleClicked(TQListBoxItem *)), + TQT_SLOT(slotOk()) ); + connect( mListBox, TQT_SIGNAL(returnPressed(TQListBoxItem *)), + TQT_SLOT(slotOk()) ); + + mListBox->setFocus(); + + layout->addStretch(); + + setMinimumWidth( 320 ); +} + +TQString CalSelectDialog::getItem( const TQString &caption, const TQString &label, + const TQStringList &list ) +{ + CalSelectDialog dlg( caption, label, list ); + + TQString result; + if ( dlg.exec() == Accepted ) { + result = dlg.mListBox->currentText(); + } + + return result; +} + +void CalSelectDialog::closeEvent( TQCloseEvent *event ) +{ + event->ignore(); +} + +void CalSelectDialog::reject() +{ +} + diff --git a/libkcal/calselectdialog.h b/libkcal/calselectdialog.h new file mode 100644 index 000000000..40b2811b4 --- /dev/null +++ b/libkcal/calselectdialog.h @@ -0,0 +1,69 @@ +/* + This file is part of libkcal. + + Copyright (c) 2008 Kevin Ottens <ervin@kde.org> + Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> + + 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. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + TQt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ +/** + @file + This file is part of the API for handling calendar data and provides a + dialog for asking the user to select from a list of available calendars. + + @author Kevin Ottens \<ervin@kde.org\> + @author Allen Winter \<allen@kdab.net\> +*/ + +#ifndef CALSELECTDIALOG_H +#define CALSELECTDIALOG_H + +#include <kdialogbase.h> + +namespace KCal { + +class CalSelectDialog : public KDialogBase +{ + private: + CalSelectDialog( const TQString &caption, const TQString &label, + const TQStringList &list ); + + public: + static TQString getItem( const TQString &caption, const TQString &label, + const TQStringList &list ); + + protected: + virtual void closeEvent( TQCloseEvent *event ); + void reject(); + + private: + KListBox *mListBox; +}; + +} + +#endif diff --git a/libkcal/comparisonvisitor.cpp b/libkcal/comparisonvisitor.cpp new file mode 100644 index 000000000..79a61e2fc --- /dev/null +++ b/libkcal/comparisonvisitor.cpp @@ -0,0 +1,107 @@ +/* + Copyright 2009 Ingo Klöcker <kloecker@kde.org> + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "comparisonvisitor.h" +#include "event.h" +#include "freebusy.h" +#include "journal.h" +#include "todo.h" + +using namespace KCal; + +class ComparisonVisitor::Private +{ + public: + Private() : mReference( 0 ) {} + + public: + const IncidenceBase *mReference; +}; + +ComparisonVisitor::ComparisonVisitor() : d( new Private() ) +{ +} + +ComparisonVisitor::~ComparisonVisitor() +{ + delete d; +} + +bool ComparisonVisitor::compare( IncidenceBase *incidence, const IncidenceBase *reference ) +{ + d->mReference = reference; + + const bool result = incidence ? incidence->accept( *this ) : reference == 0; + + d->mReference = 0; + + return result; +} + +bool ComparisonVisitor::visit( Event *event ) +{ + Q_ASSERT( event != 0 ); + + const Event *refEvent = dynamic_cast<const Event*>( d->mReference ); + if ( refEvent ) { + return *event == *refEvent; + } else { + // refEvent is no Event and thus cannot be equal to event + return false; + } +} + +bool ComparisonVisitor::visit( Todo *todo ) +{ + Q_ASSERT( todo != 0 ); + + const Todo *refTodo = dynamic_cast<const Todo*>( d->mReference ); + if ( refTodo ) { + return *todo == *refTodo; + } else { + // refTodo is no Todo and thus cannot be equal to todo + return false; + } +} + +bool ComparisonVisitor::visit( Journal *journal ) +{ + Q_ASSERT( journal != 0 ); + + const Journal *refJournal = dynamic_cast<const Journal*>( d->mReference ); + if ( refJournal ) { + return *journal == *refJournal; + } else { + // refJournal is no Journal and thus cannot be equal to journal + return false; + } +} + +bool ComparisonVisitor::visit( FreeBusy *freebusy ) +{ + Q_ASSERT( freebusy != 0 ); + + const FreeBusy *refFreeBusy = dynamic_cast<const FreeBusy*>( d->mReference ); + if ( refFreeBusy ) { + return *freebusy == *refFreeBusy; + } else { + // refFreeBusy is no FreeBusy and thus cannot be equal to freebusy + return false; + } +} diff --git a/libkcal/comparisonvisitor.h b/libkcal/comparisonvisitor.h new file mode 100644 index 000000000..0712f9c04 --- /dev/null +++ b/libkcal/comparisonvisitor.h @@ -0,0 +1,122 @@ +/* + Copyright 2009 Ingo Klöcker <kloecker@kde.org> + + 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. +*/ + +#ifndef KCAL_COMPARISONVISITOR_H +#define KCAL_COMPARISONVISITOR_H + +#include "incidencebase.h" + +namespace KCal { + +/** + Helper for type correct comparison of incidences via pointers. + + This class provides a way of correctly comparing one incidence to another, + given two IncidenceBase derived pointers. It effectively provides a virtual + comparison method which first type checks the two pointers to ensure they + reference the same incidence type, before performing the comparison. + + Usage example: + @code + KCal::Incidence *incidence; // assume this is set somewhere else + KCal::Incidence *referenceIncidence; // assume this is set somewhere else + + KCal::ComparisonVisitor visitor; + + // compare + if ( visitor.compare( incidence, referenceIncidence ) ) { + // incidence and referenceIncidence point to identical incidences + } + @endcode + + @author Ingo Klöcker <kloecker@kde.org> + */ +class ComparisonVisitor : public IncidenceBase::Visitor +{ + public: + /** + Creates a visitor instance. + */ + ComparisonVisitor(); + + /** + Destroys the instance. + */ + virtual ~ComparisonVisitor(); + + /** + Compares the incidence referenced by @p incidence to the incidence + referenced by @p reference. Returns true, if the incidence referenced + by @p incidence is identical to the incidence referenced by @p reference. + Also returns true, if @p incidence and @p reference are both @c 0. + + Basically it is a virtual equivalent of + @code + *incidence == *reference + @endcode + + @param incidence pointer to the incidence to compare with the reference incidence + @param reference pointer to the reference incidence + + @return @c true if the two incidences are identical or both @c 0 + */ + bool compare( IncidenceBase *incidence, const IncidenceBase *reference ); + + /** + Compares the event referenced by @p event to the incidence passed to + compare(). + + @return @c true if the event is identical to the reference incidence + */ + virtual bool visit( Event *event ); + + /** + Compares the todo referenced by @p todo to the incidence passed to + compare(). + + @return @c true if the todo is identical to the reference incidence + */ + virtual bool visit( Todo *todo ); + + /** + Compares the journal referenced by @p journal to the incidence passed to + compare(). + + @return @c true if the journal is identical to the reference incidence + */ + virtual bool visit( Journal *journal ); + + /** + Compares the freebusy object referenced by @p freebusy to the incidence passed to + compare(). + + @return @c true if the freebusy object is identical to the reference incidence + */ + virtual bool visit( FreeBusy *freebusy ); + + private: + //@cond PRIVATE + class Private; + Private *const d; + //@endcond +}; + +} + +#endif // KCAL_COMPARISONVISITOR_H diff --git a/libkcal/dndfactory.cpp b/libkcal/dndfactory.cpp index 9f5292d1d..b94c1707b 100644 --- a/libkcal/dndfactory.cpp +++ b/libkcal/dndfactory.cpp @@ -23,6 +23,7 @@ #include <tqapplication.h> #include <tqclipboard.h> +#include <tqmap.h> #include <kiconloader.h> #include <kdebug.h> @@ -40,11 +41,68 @@ using namespace KCal; +class DndFactory::Private +{ + public: + Incidence * pasteIncidence( Incidence *inc, + const TQDate &newDate, + const TQTime *newTime = 0 ) + { + if ( inc ) { + inc = inc->clone(); + inc->recreate(); + } + + if ( inc && newDate.isValid() ) { + if ( inc->type() == "Event" ) { + Event *anEvent = static_cast<Event*>( inc ); + // Calculate length of event + int daysOffset = anEvent->dtStart().date().daysTo( + anEvent->dtEnd().date() ); + // new end date if event starts at the same time on the new day + TQDateTime endDate( newDate.addDays(daysOffset), anEvent->dtEnd().time() ); + + if ( newTime ) { + // additional offset for new time of day + int addSecsOffset( anEvent->dtStart().time().secsTo( *newTime )); + endDate=endDate.addSecs( addSecsOffset ); + anEvent->setDtStart( TQDateTime( newDate, *newTime ) ); + } else { + anEvent->setDtStart( TQDateTime( newDate, anEvent->dtStart().time() ) ); + } + anEvent->setDtEnd( endDate ); + } else if ( inc->type() == "Todo" ) { + Todo *anTodo = static_cast<Todo*>( inc ); + if ( newTime ) { + anTodo->setDtDue( TQDateTime( newDate, *newTime ) ); + } else { + anTodo->setDtDue( TQDateTime( newDate, anTodo->dtDue().time() ) ); + } + } else if ( inc->type() == "Journal" ) { + Journal *anJournal = static_cast<Journal*>( inc ); + if ( newTime ) { + anJournal->setDtStart( TQDateTime( newDate, *newTime ) ); + } else { + anJournal->setDtStart( TQDateTime( newDate ) ); + } + } else { + kdDebug(5850) << "Trying to paste unknown incidence of type " << inc->type() << endl; + } + } + return inc; + } +}; + DndFactory::DndFactory( Calendar *cal ) : - mCalendar( cal ) + mCalendar( cal ), d( new Private ) { } +DndFactory::~DndFactory() +{ + delete d; +} + ICalDrag *DndFactory::createDrag( Incidence *incidence, TQWidget *owner ) { CalendarLocal cal( mCalendar->timeZoneId() ); @@ -98,90 +156,111 @@ Todo *DndFactory::createDropTodo(TQDropEvent *de) return 0; } - void DndFactory::cutIncidence( Incidence *selectedInc ) { - if ( copyIncidence( selectedInc ) ) { - mCalendar->deleteIncidence( selectedInc ); - } + Incidence::List list; + list.append( selectedInc ); + cutIncidences( list ); } -bool DndFactory::copyIncidence( Incidence *selectedInc ) +bool DndFactory::cutIncidences( const Incidence::List &incidences ) { - if ( !selectedInc ) + if ( copyIncidences( incidences ) ) { + Incidence::List::ConstIterator it; + for ( it = incidences.constBegin(); it != incidences.constEnd(); ++it ) { + mCalendar->deleteIncidence( *it ); + } + return true; + } else { return false; - QClipboard *cb = TQApplication::clipboard(); + } +} +bool DndFactory::copyIncidences( const Incidence::List &incidences ) +{ + QClipboard *cb = TQApplication::clipboard(); CalendarLocal cal( mCalendar->timeZoneId() ); - Incidence *inc = selectedInc->clone(); - cal.addIncidence( inc ); - cb->setData( new ICalDrag( &cal ) ); + Incidence::List::ConstIterator it; - return true; + for ( it = incidences.constBegin(); it != incidences.constEnd(); ++it ) { + if ( *it ) { + cal.addIncidence( ( *it )->clone() ); + } + } + + if ( cal.incidences().isEmpty() ) { + return false; + } else { + cb->setData( new ICalDrag( &cal ) ); + return true; + } } -Incidence *DndFactory::pasteIncidence(const TQDate &newDate, const TQTime *newTime) +bool DndFactory::copyIncidence( Incidence *selectedInc ) { -// kdDebug(5800) << "DnDFactory::pasteEvent()" << endl; + Incidence::List list; + list.append( selectedInc ); + return copyIncidences( list ); +} +Incidence::List DndFactory::pasteIncidences( const TQDate &newDate, const TQTime *newTime ) +{ CalendarLocal cal( mCalendar->timeZoneId() ); - QClipboard *cb = TQApplication::clipboard(); + Incidence::List list; if ( !ICalDrag::decode( cb->data(), &cal ) && !VCalDrag::decode( cb->data(), &cal ) ) { kdDebug(5800) << "Can't parse clipboard" << endl; - return 0; + return list; } - Incidence::List incList = cal.incidences(); - Incidence *inc = incList.first(); - - if ( !incList.isEmpty() && inc ) { - inc = inc->clone(); - - inc->recreate(); - - if ( inc->type() == "Event" ) { - - Event *anEvent = static_cast<Event*>( inc ); - // Calculate length of event - int daysOffset = anEvent->dtStart().date().daysTo( - anEvent->dtEnd().date() ); - // new end date if event starts at the same time on the new day - TQDateTime endDate( newDate.addDays(daysOffset), anEvent->dtEnd().time() ); + // All pasted incidences get new uids, must keep track of old uids, + // so we can update child's parents + TQMap<TQString,Incidence*> oldUidToNewInc; + + Incidence::List::ConstIterator it; + const Incidence::List incs = cal.incidences(); + for ( it = incs.constBegin(); it != incs.constEnd(); ++it ) { + Incidence *inc = d->pasteIncidence( *it, newDate, newTime ); + if ( inc ) { + list.append( inc ); + oldUidToNewInc[( *it )->uid()] = inc; + } + } - if ( newTime ) { - // additional offset for new time of day - int addSecsOffset( anEvent->dtStart().time().secsTo( *newTime )); - endDate=endDate.addSecs( addSecsOffset ); - anEvent->setDtStart( TQDateTime( newDate, *newTime ) ); - } else { - anEvent->setDtStart( TQDateTime( newDate, anEvent->dtStart().time() ) ); - } - anEvent->setDtEnd( endDate ); - - } else if ( inc->type() == "Todo" ) { - Todo *anTodo = static_cast<Todo*>( inc ); - if ( newTime ) { - anTodo->setDtDue( TQDateTime( newDate, *newTime ) ); - } else { - anTodo->setDtDue( TQDateTime( newDate, anTodo->dtDue().time() ) ); - } - } else if ( inc->type() == "Journal" ) { - Journal *anJournal = static_cast<Journal*>( inc ); - if ( newTime ) { - anJournal->setDtStart( TQDateTime( newDate, *newTime ) ); - } else { - anJournal->setDtStart( TQDateTime( newDate ) ); - } + // update relations + for ( it = list.constBegin(); it != list.constEnd(); ++it ) { + Incidence *inc = *it; + if ( oldUidToNewInc.contains( inc->relatedToUid() ) ) { + Incidence *parentInc = oldUidToNewInc[inc->relatedToUid()]; + inc->setRelatedToUid( parentInc->uid() ); + inc->setRelatedTo( parentInc ); } else { - kdDebug(5850) << "Trying to paste unknown incidence of type " << inc->type() << endl; + // not related to anything in the clipboard + inc->setRelatedToUid( TQString() ); + inc->setRelatedTo( 0 ); } + } - return inc; + return list; +} + +Incidence *DndFactory::pasteIncidence( const TQDate &newDate, const TQTime *newTime ) +{ + CalendarLocal cal( mCalendar->timeZoneId() ); + QClipboard *cb = TQApplication::clipboard(); + if ( !ICalDrag::decode( cb->data(), &cal ) && + !VCalDrag::decode( cb->data(), &cal ) ) { + kdDebug(5800) << "Can't parse clipboard" << endl; + return 0; } - return 0; + Incidence::List incList = cal.incidences(); + Incidence *inc = incList.isEmpty() ? 0 : incList.first(); + + Incidence *newInc = d->pasteIncidence( inc, newDate, newTime ); + newInc->setRelatedTo( 0 ); + return newInc; } diff --git a/libkcal/dndfactory.h b/libkcal/dndfactory.h index 441a0a405..a8712f2b8 100644 --- a/libkcal/dndfactory.h +++ b/libkcal/dndfactory.h @@ -43,6 +43,7 @@ class LIBKCAL_EXPORT DndFactory { public: DndFactory( Calendar * ); + ~DndFactory(); /** Create a drag object. @@ -58,9 +59,20 @@ class LIBKCAL_EXPORT DndFactory void cutIncidence( Incidence * ); /** copy the incidence to clipboard */ bool copyIncidence( Incidence * ); + + /** cuts a list of incidences to the clipboard */ + bool cutIncidences( const Incidence::List &incidences ); + + /** copies a list of incidences to the clipboard */ + bool copyIncidences( const Incidence::List &incidences ); + /** pastes the event or todo and returns a pointer to the new incidence pasted. */ Incidence *pasteIncidence( const TQDate &, const TQTime *newTime = 0 ); + /** pastes and returns the incidences from the clipboard + If no date and time are given, the incidences will be pasted at their original time */ + Incidence::List pasteIncidences( const TQDate &newDate = TQDate(), const TQTime *newTime = 0 ); + private: Calendar *mCalendar; diff --git a/libkcal/duration.cpp b/libkcal/duration.cpp index 0dfd8781c..fdb5a3701 100644 --- a/libkcal/duration.cpp +++ b/libkcal/duration.cpp @@ -19,42 +19,160 @@ Boston, MA 02110-1301, USA. */ -#include <kdebug.h> -#include <klocale.h> - #include "duration.h" using namespace KCal; Duration::Duration() { - mSeconds = 0; + mDuration = 0; } Duration::Duration( const TQDateTime &start, const TQDateTime &end ) { - mSeconds = start.secsTo( end ); + if ( start.time() == end.time() ) { + mDuration = start.daysTo( end ); + mDaily = true; + } else { + mDuration = start.secsTo( end ); + mDaily = false; + } +} + +Duration::Duration( const TQDateTime &start, const TQDateTime &end, Type type ) +{ + if ( type == Days ) { + mDuration = start.daysTo( end ); + if ( mDuration ) { + // Round down to whole number of days if necessary + if ( start < end ) { + if ( end.time() < start.time() ) { + --mDuration; + } + } else { + if ( end.time() > start.time() ) { + ++mDuration; + } + } + } + mDaily = true; + } else { + mDuration = start.secsTo( end ); + mDaily = false; + } +} + +Duration::Duration( int duration, Type type ) +{ + mDuration = duration; + mDaily = ( type == Days ); +} + +Duration::Duration( const Duration &duration ) +{ + mDuration = duration.mDuration; + mDaily = duration.mDaily; +} + +Duration &Duration::operator=( const Duration &duration ) +{ + // check for self assignment + if ( &duration == this ) { + return *this; + } + + mDuration = duration.mDuration; + mDaily = duration.mDaily; + + return *this; +} + +Duration::operator bool() const +{ + return mDuration; +} + +bool Duration::operator<( const Duration &other ) const +{ + if ( mDaily == other.mDaily ) { + // guard against integer overflow for two daily durations + return mDuration < other.mDuration; + } + return seconds() < other.seconds(); } -Duration::Duration( int seconds ) +bool Duration::operator==( const Duration &other ) const { - mSeconds = seconds; + // Note: daily and non-daily durations are always unequal, since a day's + // duration may differ from 24 hours if it happens to span a daylight saving + // time change. + return + mDuration == other.mDuration && + mDaily == other.mDaily; } -bool KCal::operator==( const Duration& d1, const Duration& d2 ) +Duration &Duration::operator+=( const Duration &other ) { - return ( d1.asSeconds() == d2.asSeconds() ); + if ( mDaily == other.mDaily ) { + mDuration += other.mDuration; + } else if ( mDaily ) { + mDuration = mDuration * 86400 + other.mDuration; + mDaily = false; + } else { + mDuration += other.mDuration + 86400; + } + return *this; } +Duration Duration::operator-() const +{ + return Duration( -mDuration, ( mDaily ? Days : Seconds ) ); +} +Duration &Duration::operator-=( const Duration &duration ) +{ + return operator+=( -duration ); +} + +Duration &Duration::operator*=( int value ) +{ + mDuration *= value; + return *this; +} + +Duration &Duration::operator/=( int value ) +{ + mDuration /= value; + return *this; +} TQDateTime Duration::end( const TQDateTime &start ) const { - return start.addSecs( mSeconds ); + return mDaily ? start.addDays( mDuration ) + : start.addSecs( mDuration ); +} +Duration::Type Duration::type() const +{ + return mDaily ? Days : Seconds; +} + +bool Duration::isDaily() const +{ + return mDaily; } int Duration::asSeconds() const { - return mSeconds; + return seconds(); +} + +int Duration::asDays() const +{ + return mDaily ? mDuration : mDuration / 86400; +} + +int Duration::value() const +{ + return mDuration; } diff --git a/libkcal/duration.h b/libkcal/duration.h index b4655d6c0..9cddf6958 100644 --- a/libkcal/duration.h +++ b/libkcal/duration.h @@ -33,27 +33,246 @@ namespace KCal { class LIBKCAL_EXPORT Duration { public: + /** + The unit of time used to define the duration. + */ + enum Type { + Seconds, /**< duration is a number of seconds */ + Days /**< duration is a number of days */ + }; + + /** + Constructs a duration of 0 seconds. + */ Duration(); + + /** + Constructs a duration from @p start to @p end. + + If the time of day in @p start and @p end is equal, and their time + specifications (i.e. time zone etc.) are the same, the duration will be + set in terms of days. Otherwise, the duration will be set in terms of + seconds. + + @param start is the time the duration begins. + @param end is the time the duration ends. + */ Duration( const TQDateTime &start, const TQDateTime &end ); - Duration( int seconds ); + /** + Constructs a duration from @p start to @p end. + + If @p type is Days, and the time of day in @p start's time zone differs + between @p start and @p end, the duration will be rounded down to the + nearest whole number of days. + + @param start is the time the duration begins. + @param end is the time the duration ends. + @param type the unit of time to use (seconds or days) + */ + Duration( const TQDateTime &start, const TQDateTime &end, Type type ); + + /** + Constructs a duration with a number of seconds or days. + + @param duration the number of seconds or days in the duration + @param type the unit of time to use (seconds or days) + */ + Duration( int duration, Type type = Seconds ); //krazy:exclude=explicit + + /** + Constructs a duration by copying another duration object. + + @param duration is the duration to copy. + */ + Duration( const Duration &duration ); + + /** + Sets this duration equal to @p duration. + + @param duration is the duration to copy. + */ + Duration &operator=( const Duration &duration ); + + /** + Returns true if this duration is non-zero. + */ + operator bool() const; + + /** + Returns true if this duration is zero. + */ + bool operator!() const { return !operator bool(); } + + /** + Returns true if this duration is smaller than the @p other. + @param other is the other duration to compare. + */ + bool operator<( const Duration &other ) const; + + /** + Returns true if this duration is smaller than or equal to the @p other. + @param other is the other duration to compare. + */ + bool operator<=( const Duration &other ) const + { return !other.operator<( *this ); } + + + /** + Returns true if this duration is greater than the @p other. + @param other is the other duration to compare. + */ + bool operator>( const Duration &other ) const + { return other.operator<( *this ); } + + /** + Returns true if this duration is greater than or equal to the @p other. + @param other is the other duration to compare. + */ + bool operator>=( const Duration &other ) const + { return !operator<( other ); } + + /** + Returns true if this duration is equal to the @p other. + Daily and non-daily durations are always considered unequal, since a + day's duration may differ from 24 hours if it happens to span a daylight + saving time change. + @param other the other duration to compare + */ + bool operator==( const Duration &other ) const; + + /** + Returns true if this duration is not equal to the @p other. + Daily and non-daily durations are always considered unequal, since a + day's duration may differ from 24 hours if it happens to span a daylight + saving time change. + @param other is the other duration to compare. + */ + bool operator!=( const Duration &other ) const + { return !operator==( other ); } + + /** + Adds another duration to this one. + If one is in terms of days and the other in terms of seconds, + the result is in terms of seconds. + @param other the other duration to add + */ + Duration &operator+=( const Duration &other ); + + /** + Adds two durations. + If one is in terms of days and the other in terms of seconds, + the result is in terms of seconds. + + @param other the other duration to add + @return combined duration + */ + Duration operator+( const Duration &other ) const + { return Duration( *this ) += other; } + + /** + Returns the negative of this duration. + */ + Duration operator-() const; + + /** + Subtracts another duration from this one. + If one is in terms of days and the other in terms of seconds, + the result is in terms of seconds. + + @param other the other duration to subtract + */ + Duration &operator-=( const Duration &other ); + + /** + Returns the difference between another duration and this. + If one is in terms of days and the other in terms of seconds, + the result is in terms of seconds. + + @param other the other duration to subtract + @return difference in durations + */ + Duration operator-( const Duration &other ) const + { return Duration( *this ) += other; } + + /** + Multiplies this duration by a value. + @param value value to multiply by + */ + Duration &operator*=( int value ); + + /** + Multiplies a duration by a value. + + @param value value to multiply by + @return resultant duration + */ + Duration operator*( int value ) const + { return Duration( *this ) *= value; } + + /** + Divides this duration by a value. + @param value value to divide by + */ + Duration &operator/=( int value ); + + /** + Divides a duration by a value. + + @param value value to divide by + @return resultant duration + */ + Duration operator/( int value ) const + { return Duration( *this ) /= value; } + + /** + Computes a duration end time by adding the number of seconds or + days in the duration to the specified @p start time. + + @param start is the start time. + @return end time. + */ TQDateTime end( const TQDateTime &start ) const; + /** + Returns the time units (seconds or days) used to specify the duration. + */ + Type type() const; + + /** + Returns whether the duration is specified in terms of days rather + than seconds. + */ + bool isDaily() const; + + /** + Returns the length of the duration in seconds. + */ int asSeconds() const; + /** + Returns the length of the duration in days. If the duration is + not an exact number of days, it is rounded down to return the + number of whole days. + */ + int asDays() const; + + /** + Returns the length of the duration in seconds or days. + + @return if isDaily(), duration in days, else duration in seconds + */ + int value() const; + private: - int mSeconds; + int seconds() const { return mDaily ? mDuration * 86400 : mDuration; } + int mDuration; + bool mDaily; class Private; Private *d; }; -bool operator==( const Duration&, const Duration& ); -inline bool operator!=( const Duration &d1, const Duration &d2 ) -{ - return !operator==( d1, d2 ); -} - } #endif diff --git a/libkcal/event.cpp b/libkcal/event.cpp index 0f286e7f6..5bd3a8188 100644 --- a/libkcal/event.cpp +++ b/libkcal/event.cpp @@ -87,8 +87,8 @@ TQDateTime Event::dtEnd() const if (hasEndDate()) return mDtEnd; if (hasDuration()) return dtStart().addSecs(duration()); - kdDebug(5800) << "Warning! Event '" << summary() - << "' has neither end date nor duration." << endl; + // It is valid for a VEVENT to be without a DTEND. See RFC2445, Sect4.6.1. + // Be careful to use Event::dateEnd() as appropriate due to this possibility. return dtStart(); } diff --git a/libkcal/event.h b/libkcal/event.h index 39980556c..fed0e41a0 100644 --- a/libkcal/event.h +++ b/libkcal/event.h @@ -72,21 +72,24 @@ class LIBKCAL_EXPORT Event : public Incidence /** Return end time as string formatted according to the users locale settings. + @deprecated use IncidenceFormatter::timeToString() */ - TQString dtEndTimeStr() const; + TQString KDE_DEPRECATED dtEndTimeStr() const; /** Return end date as string formatted according to the users locale settings. @param shortfmt if true return string in short format, if false return long format + @deprecated use IncidenceFormatter::dateToString() */ - TQString dtEndDateStr( bool shortfmt = true ) const; + TQString KDE_DEPRECATED dtEndDateStr( bool shortfmt = true ) const; /** Return end date and time as string formatted according to the users locale settings. + @deprecated use IncidenceFormatter::dateTimeToString() */ - TQString dtEndStr() const; + TQString KDE_DEPRECATED dtEndStr() const; /** Set whether the event has an end date/time. diff --git a/libkcal/exceptions.cpp b/libkcal/exceptions.cpp index 98d7153eb..99c804ddf 100644 --- a/libkcal/exceptions.cpp +++ b/libkcal/exceptions.cpp @@ -26,7 +26,7 @@ using namespace KCal; -Exception::Exception(const TQString &message) +Exception::Exception( const TQString &message ) { mMessage = message; } @@ -37,13 +37,16 @@ Exception::~Exception() TQString Exception::message() { - if (mMessage.isEmpty()) return i18n("%1 Error").arg(CalFormat::application()); - else return mMessage; + if ( mMessage.isEmpty() ) { + return i18n( "%1 Error" ).arg( CalFormat::application() ); + } else { + return mMessage; + } } -ErrorFormat::ErrorFormat(ErrorCodeFormat code,const TQString &message) : - Exception(message) +ErrorFormat::ErrorFormat( ErrorCodeFormat code, const TQString &message ) + : Exception( message ) { mCode = code; } @@ -52,35 +55,45 @@ TQString ErrorFormat::message() { TQString message = ""; - switch (mCode) { - case LoadError: - message = i18n("Load Error"); - break; - case SaveError: - message = i18n("Save Error"); - break; - case ParseErrorIcal: - message = i18n("Parse Error in libical"); - break; - case ParseErrorKcal: - message = i18n("Parse Error in libkcal"); - break; - case NoCalendar: - message = i18n("No calendar component found."); - break; - case CalVersion1: - message = i18n("vCalendar Version 1.0 detected."); - break; - case CalVersion2: - message = i18n("iCalendar Version 2.0 detected."); - break; - case Restriction: - message = i18n("Restriction violation"); - default: - break; + switch ( mCode ) { + case LoadError: + message = i18n( "Load Error" ); + break; + case SaveError: + message = i18n( "Save Error" ); + break; + case ParseErrorIcal: + message = i18n( "Parse Error in libical" ); + break; + case ParseErrorKcal: + message = i18n( "Parse Error in libkcal" ); + break; + case NoCalendar: + message = i18n( "No calendar component found." ); + break; + case CalVersion1: + message = i18n( "vCalendar Version 1.0 detected." ); + break; + case CalVersion2: + message = i18n( "iCalendar Version 2.0 detected." ); + break; + case CalVersionUnknown: + message = i18n( "Unknown calendar format detected." ); + break; + case Restriction: + message = i18n( "Restriction violation" ); + break; + case NoWritableFound: + message = i18n( "No writable resource found" ); + break; + case UserCancel: + // no real error; the user canceled the operation + break; } - if (!mMessage.isEmpty()) message += ": " + mMessage; + if ( !mMessage.isEmpty() ) { + message += ": " + mMessage; + } return message; } diff --git a/libkcal/exceptions.h b/libkcal/exceptions.h index 1ae90d516..8bdf7fa0f 100644 --- a/libkcal/exceptions.h +++ b/libkcal/exceptions.h @@ -47,9 +47,9 @@ class Exception /** Return descriptive message of exception. - */ + */ virtual TQString message(); - + protected: TQString mMessage; @@ -64,18 +64,28 @@ class Exception class ErrorFormat : public Exception { public: - enum ErrorCodeFormat { LoadError, SaveError, - ParseErrorIcal, ParseErrorKcal, - NoCalendar, - CalVersion1,CalVersion2, - CalVersionUnknown, - Restriction }; - + /** + The different types of Calendar format errors. + */ + enum ErrorCodeFormat { + LoadError, /**< Load error */ + SaveError, /**< Save error */ + ParseErrorIcal, /**< Parse error in libical */ + ParseErrorKcal, /**< Parse error in libkcal */ + NoCalendar, /**< No calendar component found */ + CalVersion1, /**< vCalendar v1.0 detected */ + CalVersion2, /**< iCalendar v2.0 detected */ + CalVersionUnknown, /**< Unknown calendar format detected */ + Restriction, /**< Restriction violation */ + NoWritableFound, /**< No writable resource is available */ + UserCancel /**< User canceled the operation */ + }; + /** Create format error exception. */ ErrorFormat( ErrorCodeFormat code, const TQString &message = TQString::null ); - + /** Return format error message. */ @@ -84,7 +94,7 @@ class ErrorFormat : public Exception Return format error code. */ ErrorCodeFormat errorCode(); - + private: ErrorCodeFormat mCode; diff --git a/libkcal/freebusy.cpp b/libkcal/freebusy.cpp index 75d0e7db4..2935f4baf 100644 --- a/libkcal/freebusy.cpp +++ b/libkcal/freebusy.cpp @@ -222,3 +222,12 @@ void FreeBusy::merge( FreeBusy *freeBusy ) for ( it = periods.begin(); it != periods.end(); ++it ) addPeriod( (*it).start(), (*it).end() ); } + +bool FreeBusy::operator==( const FreeBusy &freebusy ) const +{ + return + static_cast<const IncidenceBase &>( *this ) == static_cast<const IncidenceBase &>( freebusy ) && + dtEnd() == freebusy.dtEnd() && + mCalendar == freebusy.mCalendar && + mBusyPeriods == freebusy.mBusyPeriods; +} diff --git a/libkcal/freebusy.h b/libkcal/freebusy.h index d87234f30..f736092df 100644 --- a/libkcal/freebusy.h +++ b/libkcal/freebusy.h @@ -47,7 +47,7 @@ class LIBKCAL_EXPORT FreeBusy : public IncidenceBase FreeBusy( PeriodList busyPeriods ); ~FreeBusy(); - + TQCString type() const { return "FreeBusy"; } virtual TQDateTime dtEnd() const; @@ -65,7 +65,14 @@ class LIBKCAL_EXPORT FreeBusy : public IncidenceBase void sortList(); void merge( FreeBusy *freebusy ); - + + /** + Compare this with @p freebusy for equality. + + @param freebusy is the FreeBusy to compare. + */ + bool operator==( const FreeBusy &freebusy ) const; + private: bool accept( Visitor &v ) { return v.visit( this ); } //This is used for creating a freebusy object for the current user diff --git a/libkcal/htmlexport.cpp b/libkcal/htmlexport.cpp index fd994fca2..c945b88f0 100644 --- a/libkcal/htmlexport.cpp +++ b/libkcal/htmlexport.cpp @@ -34,6 +34,7 @@ #include <libkcal/calendar.h> #include <libkcal/event.h> +#include <libkcal/incidenceformatter.h> #include <libkcal/todo.h> #ifndef KORG_NOKABC @@ -184,20 +185,23 @@ void HtmlExport::createMonthView(TQTextStream *ts) *ts << "</td></tr><tr><td valign=\"top\">"; - Event::List events = mCalendar->events( start, - EventSortStartDate, - SortDirectionAscending ); - if (events.count()) { - *ts << "<table>"; - Event::List::ConstIterator it; - for( it = events.begin(); it != events.end(); ++it ) { + // Only print events within the from-to range + if ( start >= fromDate() && start <= toDate() ) { + Event::List events = mCalendar->events( start, + EventSortStartDate, + SortDirectionAscending ); + if (events.count()) { + *ts << "<table>"; + Event::List::ConstIterator it; + for( it = events.begin(); it != events.end(); ++it ) { if ( checkSecrecy( *it ) ) { createEvent( ts, *it, start, false ); } + } + *ts << "</table>"; + } else { + *ts << " "; } - *ts << "</table>"; - } else { - *ts << " "; } *ts << "</td></tr></table></td>\n"; @@ -275,12 +279,16 @@ void HtmlExport::createEvent (TQTextStream *ts, Event *event, if (event->isMultiDay() && (event->dtStart().date() != date)) { *ts << " <td> </td>\n"; } else { - *ts << " <td valign=\"top\">" << event->dtStartTimeStr() << "</td>\n"; + *ts << " <td valign=\"top\">" + << IncidenceFormatter::timeToString( event->dtStart(), true ) + << "</td>\n"; } if (event->isMultiDay() && (event->dtEnd().date() != date)) { *ts << " <td> </td>\n"; } else { - *ts << " <td valign=\"top\">" << event->dtEndTimeStr() << "</td>\n"; + *ts << " <td valign=\"top\">" + << IncidenceFormatter::timeToString( event->dtEnd(), true ) + << "</td>\n"; } } else { *ts << " <td> </td><td> </td>\n"; @@ -459,7 +467,7 @@ void HtmlExport::createTodo (TQTextStream *ts,Todo *todo) if (completed) *ts << " class=\"done\""; *ts << ">\n"; if (todo->hasDueDate()) { - *ts << " " << todo->dtDueDateStr() << "\n"; + *ts << " " << IncidenceFormatter::dateToString( todo->dtDue( true ) ) << "\n"; } else { *ts << " \n"; } diff --git a/libkcal/icalformatimpl.cpp b/libkcal/icalformatimpl.cpp index a1655409f..613d492e9 100644 --- a/libkcal/icalformatimpl.cpp +++ b/libkcal/icalformatimpl.cpp @@ -55,6 +55,7 @@ static TQDateTime ICalDate2TQDate(const icaltimetype& t) return TQDateTime(TQDate(year,t.month,t.day), TQTime(t.hour,t.minute,t.second)); } +/* static void _dumpIcaltime( const icaltimetype& t) { kdDebug(5800) << "--- Y: " << t.year << " M: " << t.month << " D: " << t.day @@ -64,6 +65,16 @@ static void _dumpIcaltime( const icaltimetype& t) kdDebug(5800) << "--- isUtc: " << icaltime_is_utc( t )<< endl; kdDebug(5800) << "--- zoneId: " << icaltimezone_get_tzid( const_cast<icaltimezone*>( t.zone ) )<< endl; } +*/ + +static TQString quoteForParam( const TQString &text ) +{ + TQString tmp = text; + tmp.remove( '"' ); + if ( tmp.contains( ';' ) || tmp.contains( ':' ) || tmp.contains( ',' ) ) + return tmp; // libical quotes in this case already, see icalparameter_as_ical_string() + return TQString::fromLatin1( "\"" ) + tmp + TQString::fromLatin1( "\"" ); +} const int gSecondsPerMinute = 60; const int gSecondsPerHour = gSecondsPerMinute * 60; @@ -565,7 +576,7 @@ icalproperty *ICalFormatImpl::writeOrganizer( const Person &organizer ) icalproperty *p = icalproperty_new_organizer("MAILTO:" + organizer.email().utf8()); if (!organizer.name().isEmpty()) { - icalproperty_add_parameter( p, icalparameter_new_cn(organizer.name().utf8()) ); + icalproperty_add_parameter( p, icalparameter_new_cn(quoteForParam(organizer.name()).utf8()) ); } // TODO: Write dir, sent-by and language @@ -578,7 +589,7 @@ icalproperty *ICalFormatImpl::writeAttendee(Attendee *attendee) icalproperty *p = icalproperty_new_attendee("mailto:" + attendee->email().utf8()); if (!attendee->name().isEmpty()) { - icalproperty_add_parameter(p,icalparameter_new_cn(attendee->name().utf8())); + icalproperty_add_parameter(p,icalparameter_new_cn(quoteForParam(attendee->name()).utf8())); } @@ -649,14 +660,15 @@ icalproperty *ICalFormatImpl::writeAttendee(Attendee *attendee) return p; } -icalproperty *ICalFormatImpl::writeAttachment(Attachment *att) +icalproperty *ICalFormatImpl::writeAttachment( Attachment *att ) { icalattach *attach; - if (att->isUri()) - attach = icalattach_new_from_url( att->uri().utf8().data()); - else - attach = icalattach_new_from_data ( (unsigned char *)att->data(), 0, 0); - icalproperty *p = icalproperty_new_attach(attach); + if ( att->isUri() ) { + attach = icalattach_new_from_url( att->uri().utf8().data() ); + } else { + attach = icalattach_new_from_data ( (unsigned char *)att->data(), 0, 0 ); + } + icalproperty *p = icalproperty_new_attach( attach ); if ( !att->mimeType().isEmpty() ) { icalproperty_add_parameter( p, @@ -853,7 +865,7 @@ icalcomponent *ICalFormatImpl::writeAlarm(Alarm *alarm) for (TQValueList<Person>::Iterator ad = addresses.begin(); ad != addresses.end(); ++ad) { icalproperty *p = icalproperty_new_attendee("MAILTO:" + (*ad).email().utf8()); if (!(*ad).name().isEmpty()) { - icalproperty_add_parameter(p,icalparameter_new_cn((*ad).name().utf8())); + icalproperty_add_parameter(p,icalparameter_new_cn(quoteForParam((*ad).name()).utf8())); } icalcomponent_add_property(a,p); } @@ -892,7 +904,7 @@ icalcomponent *ICalFormatImpl::writeAlarm(Alarm *alarm) offset = alarm->startOffset(); else offset = alarm->endOffset(); - trigger.duration = icaldurationtype_from_int( offset.asSeconds() ); + trigger.duration = writeICalDuration( offset.asSeconds() ); } icalproperty *p = icalproperty_new_trigger(trigger); if ( alarm->hasEndOffset() ) @@ -903,7 +915,7 @@ icalcomponent *ICalFormatImpl::writeAlarm(Alarm *alarm) if (alarm->repeatCount()) { icalcomponent_add_property(a,icalproperty_new_repeat(alarm->repeatCount())); icalcomponent_add_property(a,icalproperty_new_duration( - icaldurationtype_from_int(alarm->snoozeTime()*60))); + writeICalDuration(alarm->snoozeTime().value()))); } // Custom properties @@ -1000,9 +1012,9 @@ Event *ICalFormatImpl::readEvent( icalcomponent *vevent, icalcomponent *vtimezon readIncidence( vevent, tz, event); - icalproperty *p = icalcomponent_get_first_property(vevent,ICAL_ANY_PROPERTY); + icalproperty *p = icalcomponent_get_first_property( vevent, ICAL_ANY_PROPERTY ); -// int intvalue; + // int intvalue; icaltimetype icaltime; TQStringList categories; @@ -1010,16 +1022,19 @@ Event *ICalFormatImpl::readEvent( icalcomponent *vevent, icalcomponent *vtimezon bool dtEndProcessed = false; - while (p) { - icalproperty_kind kind = icalproperty_isa(p); - switch (kind) { + while ( p ) { + icalproperty_kind kind = icalproperty_isa( p ); + switch ( kind ) { case ICAL_DTEND_PROPERTY: // start date and time - icaltime = icalproperty_get_dtend(p); - if (icaltime.is_date) { + icaltime = icalproperty_get_dtend( p ); + if ( icaltime.is_date ) { // End date is non-inclusive TQDate endDate = readICalDate( icaltime ).addDays( -1 ); - if ( mCompat ) mCompat->fixFloatingEnd( endDate ); + if ( mCompat ) { + mCompat->fixFloatingEnd( endDate ); + } + if ( endDate < event->dtStart().date() ) { endDate = event->dtStart().date(); } @@ -1032,26 +1047,26 @@ Event *ICalFormatImpl::readEvent( icalcomponent *vevent, icalcomponent *vtimezon break; case ICAL_RELATEDTO_PROPERTY: // related event (parent) - event->setRelatedToUid(TQString::fromUtf8(icalproperty_get_relatedto(p))); - mEventsRelate.append(event); + event->setRelatedToUid( TQString::fromUtf8( icalproperty_get_relatedto( p ) ) ); + mEventsRelate.append( event ); break; - case ICAL_TRANSP_PROPERTY: // Transparency - transparency = icalproperty_get_transp(p); - if( transparency == ICAL_TRANSP_TRANSPARENT ) + transparency = icalproperty_get_transp( p ); + if ( transparency == ICAL_TRANSP_TRANSPARENT ) { event->setTransparency( Event::Transparent ); - else + } else { event->setTransparency( Event::Opaque ); + } break; default: -// kdDebug(5800) << "ICALFormat::readEvent(): Unknown property: " << kind -// << endl; + // kdDebug(5800) << "ICALFormat::readEvent(): Unknown property: " << kind + // << endl; break; } - p = icalcomponent_get_next_property(vevent,ICAL_ANY_PROPERTY); + p = icalcomponent_get_next_property( vevent, ICAL_ANY_PROPERTY ); } // according to rfc2445 the dtend shouldn't be written when it equals @@ -1060,13 +1075,15 @@ Event *ICalFormatImpl::readEvent( icalcomponent *vevent, icalcomponent *vtimezon event->setDtEnd( event->dtStart() ); } - TQString msade = event->nonKDECustomProperty("X-MICROSOFT-CDO-ALLDAYEVENT"); - if (!msade.isEmpty()) { - bool floats = (msade == TQString::fromLatin1("TRUE")); + const TQString msade = event->nonKDECustomProperty("X-MICROSOFT-CDO-ALLDAYEVENT"); + if ( !msade.isEmpty() ) { + const bool floats = ( msade == TQString::fromLatin1("TRUE") ); event->setFloats(floats); } - if ( mCompat ) mCompat->fixEmptySummary( event ); + if ( mCompat ) { + mCompat->fixEmptySummary( event ); + } return event; } @@ -1096,7 +1113,8 @@ FreeBusy *ICalFormatImpl::readFreeBusy(icalcomponent *vfreebusy) freebusy->setDtEnd(readICalDateTime(p, icaltime)); break; - case ICAL_FREEBUSY_PROPERTY: { //Any FreeBusy Times + case ICAL_FREEBUSY_PROPERTY: //Any FreeBusy Times + { icalperiodtype icalperiod = icalproperty_get_freebusy(p); TQDateTime period_start = readICalDateTime(p, icalperiod.start); Period period; @@ -1107,12 +1125,21 @@ FreeBusy *ICalFormatImpl::readFreeBusy(icalcomponent *vfreebusy) Duration duration = readICalDuration( icalperiod.duration ); period = Period(period_start, duration); } - TQCString param = icalproperty_get_parameter_as_string( p, "X-SUMMARY" ); - period.setSummary( TQString::fromUtf8( KCodecs::base64Decode( param ) ) ); - param = icalproperty_get_parameter_as_string( p, "X-LOCATION" ); - period.setLocation( TQString::fromUtf8( KCodecs::base64Decode( param ) ) ); + icalparameter *param = icalproperty_get_first_parameter( p, ICAL_X_PARAMETER ); + while ( param ) { + if ( strncmp( icalparameter_get_xname( param ), "X-SUMMARY", 9 ) == 0 ) { + period.setSummary( TQString::fromUtf8( + KCodecs::base64Decode( icalparameter_get_xvalue( param ) ) ) ); + } + if ( strncmp( icalparameter_get_xname( param ), "X-LOCATION", 10 ) == 0 ) { + period.setLocation( TQString::fromUtf8( + KCodecs::base64Decode( icalparameter_get_xvalue( param ) ) ) ); + } + param = icalproperty_get_next_parameter( p, ICAL_X_PARAMETER ); + } periods.append( period ); - break;} + break; + } default: // kdDebug(5800) << "ICalFormatImpl::readFreeBusy(): Unknown property: " @@ -1256,31 +1283,70 @@ Attachment *ICalFormatImpl::readAttachment(icalproperty *attach) { Attachment *attachment = 0; - icalvalue_kind value_kind = icalvalue_isa(icalproperty_get_value(attach)); + const char *p; + icalvalue *value = icalproperty_get_value( attach ); - if ( value_kind == ICAL_ATTACH_VALUE || value_kind == ICAL_BINARY_VALUE ) { - icalattach *a = icalproperty_get_attach(attach); - - int isurl = icalattach_get_is_url (a); - if (isurl == 0) - attachment = new Attachment((const char*)icalattach_get_data(a)); - else { - attachment = new Attachment(TQString::fromUtf8(icalattach_get_url(a))); + switch( icalvalue_isa( value ) ) { + case ICAL_ATTACH_VALUE: + { + icalattach *a = icalproperty_get_attach( attach ); + if ( !icalattach_get_is_url( a ) ) { + p = (const char *)icalattach_get_data( a ); + if ( p ) { + attachment = new Attachment( p ); + } + } else { + p = icalattach_get_url( a ); + if ( p ) { + attachment = new Attachment( TQString::fromUtf8( p ) ); + } + } + break; + } + case ICAL_BINARY_VALUE: + { + icalattach *a = icalproperty_get_attach( attach ); + p = (const char *)icalattach_get_data( a ); + if ( p ) { + attachment = new Attachment( p ); } + break; } - else if ( value_kind == ICAL_URI_VALUE ) { - attachment = new Attachment(TQString::fromUtf8(icalvalue_get_uri(icalproperty_get_value(attach)))); + case ICAL_URI_VALUE: + p = icalvalue_get_uri( value ); + attachment = new Attachment( TQString::fromUtf8( p ) ); + break; + default: + break; } - icalparameter *p = icalproperty_get_first_parameter(attach, ICAL_FMTTYPE_PARAMETER); - if (p && attachment) - attachment->setMimeType(TQString(icalparameter_get_fmttype(p))); + if ( attachment ) { + icalparameter *p = + icalproperty_get_first_parameter( attach, ICAL_FMTTYPE_PARAMETER ); + if ( p ) { + attachment->setMimeType( TQString( icalparameter_get_fmttype( p ) ) ); + } - p = icalproperty_get_first_parameter(attach,ICAL_X_PARAMETER); - while (p) { - if ( strncmp (icalparameter_get_xname(p), "X-LABEL", 7) == 0 ) - attachment->setLabel( icalparameter_get_xvalue(p) ); - p = icalproperty_get_next_parameter(attach, ICAL_X_PARAMETER); + p = icalproperty_get_first_parameter( attach, ICAL_X_PARAMETER ); + while ( p ) { + TQString xname = TQString( icalparameter_get_xname( p ) ).upper(); + TQString xvalue = TQString::fromUtf8( icalparameter_get_xvalue( p ) ); + if ( xname == "X-CONTENT-DISPOSITION" ) { + attachment->setShowInline( xvalue.lower() == "inline" ); + } + if ( xname == "X-LABEL" ) { + attachment->setLabel( xvalue ); + } + p = icalproperty_get_next_parameter( attach, ICAL_X_PARAMETER ); + } + + p = icalproperty_get_first_parameter( attach, ICAL_X_PARAMETER ); + while ( p ) { + if ( strncmp( icalparameter_get_xname( p ), "X-LABEL", 7 ) == 0 ) { + attachment->setLabel( TQString::fromUtf8( icalparameter_get_xvalue( p ) ) ); + } + p = icalproperty_get_next_parameter( attach, ICAL_X_PARAMETER ); + } } return attachment; @@ -1482,32 +1548,48 @@ void ICalFormatImpl::readIncidenceBase(icalcomponent *parent,IncidenceBase *inci { icalproperty *p = icalcomponent_get_first_property(parent,ICAL_ANY_PROPERTY); - while (p) { - icalproperty_kind kind = icalproperty_isa(p); + bool uidProcessed = false; + + while ( p ) { + icalproperty_kind kind = icalproperty_isa( p ); switch (kind) { case ICAL_UID_PROPERTY: // unique id - incidenceBase->setUid(TQString::fromUtf8(icalproperty_get_uid(p))); + uidProcessed = true; + incidenceBase->setUid( TQString::fromUtf8(icalproperty_get_uid( p ) ) ); break; case ICAL_ORGANIZER_PROPERTY: // organizer - incidenceBase->setOrganizer( readOrganizer(p)); + incidenceBase->setOrganizer( readOrganizer( p ) ); break; case ICAL_ATTENDEE_PROPERTY: // attendee - incidenceBase->addAttendee(readAttendee(p)); + incidenceBase->addAttendee( readAttendee( p ) ); break; case ICAL_COMMENT_PROPERTY: incidenceBase->addComment( - TQString::fromUtf8(icalproperty_get_comment(p))); + TQString::fromUtf8( icalproperty_get_comment( p ) ) ); break; default: break; } - p = icalcomponent_get_next_property(parent,ICAL_ANY_PROPERTY); + p = icalcomponent_get_next_property( parent, ICAL_ANY_PROPERTY ); + } + + if ( !uidProcessed ) { + kdWarning() << "The incidence didn't have any UID! Report a bug " + << "to the application that generated this file." + << endl; + + // Our in-memory incidence has a random uid generated in Event's ctor. + // Make it empty so it matches what's in the file: + incidenceBase->setUid( TQString() ); + + // Otherwise, next time we read the file, this function will return + // an event with another random uid and we will have two events in the calendar. } // kpilot stuff @@ -1736,7 +1818,7 @@ void ICalFormatImpl::readAlarm(icalcomponent *alarm,Incidence *incidence) } case ICAL_DURATION_PROPERTY: { icaldurationtype duration = icalproperty_get_duration(p); - ialarm->setSnoozeTime(icaldurationtype_as_int(duration)/60); + ialarm->setSnoozeTime( readICalDuration( duration ) ); break; } case ICAL_REPEAT_PROPERTY: @@ -1937,13 +2019,16 @@ TQDate ICalFormatImpl::readICalDate(icaltimetype t) icaldurationtype ICalFormatImpl::writeICalDuration(int seconds) { + // should be able to use icaldurationtype_from_int(), except we know + // that some older tools do not properly support weeks. So we never + // set a week duration, only days + icaldurationtype d; d.is_neg = (seconds<0)?1:0; if (seconds<0) seconds = -seconds; - d.weeks = seconds / gSecondsPerWeek; - seconds %= gSecondsPerWeek; + d.weeks = 0; d.days = seconds / gSecondsPerDay; seconds %= gSecondsPerDay; d.hours = seconds / gSecondsPerHour; @@ -2001,7 +2086,7 @@ icalcomponent *ICalFormatImpl::createCalendarComponent(Calendar *cal) // take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc. // and break it down from its tree-like format into the dictionary format // that is used internally in the ICalFormatImpl. -bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar) +bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar ) { // this function will populate the caldict dictionary and other event // lists. It turns vevents into Events and then inserts them. @@ -2031,6 +2116,14 @@ bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar) return false; } else { const char *version = icalproperty_get_version(p); + if ( !version ) { + kdDebug(5800) << "No VERSION property found" << endl; + mParent->setException( new ErrorFormat( + ErrorFormat::CalVersionUnknown, + i18n( "No VERSION property found" ) ) ); + return false; + } + // kdDebug(5800) << "VCALENDAR version: '" << version << "'" << endl; if (strcmp(version,"1.0") == 0) { @@ -2071,7 +2164,11 @@ bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar) TQString originalUid = todo->uid(); todo->setUid(originalUid + QString("-recur-%1").arg(todo->recurrenceID().toTime_t())); if (!cal->todo(todo->uid())) { - cal->addTodo(todo); + if ( !cal->addTodo( todo ) ) { + cal->endBatchAdding(); + // If the user pressed cancel, return true, it's not an error. + return cal->exception() && cal->exception()->errorCode() == ErrorFormat::UserCancel; + } if (!cal->event(originalUid)) { printf("FIXME! [WARNING] Parent for child event does not yet exist!\n\r"); } @@ -2085,7 +2182,11 @@ bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar) } else { if (!cal->todo(todo->uid())) { - cal->addTodo(todo); + if ( !cal->addTodo( todo ) ) { + cal->endBatchAdding(); + // If the user pressed cancel, return true, it's not an error. + return cal->exception() && cal->exception()->errorCode() == ErrorFormat::UserCancel; + } } else { delete todo; mTodosRelate.remove( todo ); @@ -2119,7 +2220,11 @@ bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar) } else { if (!cal->event(event->uid())) { - cal->addEvent(event); + if ( !cal->addEvent( event ) ) { + cal->endBatchAdding(); + // If the user pressed cancel, return true, it's not an error. + return cal->exception() && cal->exception()->errorCode() == ErrorFormat::UserCancel; + } } else { delete event; mEventsRelate.remove( event ); @@ -2153,7 +2258,11 @@ bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar) } else { if (!cal->journal(journal->uid())) { - cal->addJournal(journal); + if ( !cal->addJournal(journal) ) { + cal->endBatchAdding(); + // If the user pressed cancel, return true, it's not an error. + return cal->exception() && cal->exception()->errorCode() == ErrorFormat::UserCancel; + } } else { delete journal; } @@ -2162,6 +2271,8 @@ bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar) c = icalcomponent_get_next_component(calendar,ICAL_VJOURNAL_COMPONENT); } + cal->endBatchAdding(); + // Post-Process list of events with relations, put Event objects in relation Event::List::ConstIterator eIt; for ( eIt = mEventsRelate.begin(); eIt != mEventsRelate.end(); ++eIt ) { diff --git a/libkcal/incidence.h b/libkcal/incidence.h index 477b758c6..89d45b085 100644 --- a/libkcal/incidence.h +++ b/libkcal/incidence.h @@ -66,6 +66,36 @@ class LIBKCAL_EXPORT Incidence : public IncidenceBase, public Recurrence::Observ }; /** + This class implements a visitor for adding an Incidence to a resource + plus subresource supporting addEvent(), addTodo() and addJournal() calls. + */ + template<class T> + class AddSubResourceVisitor : public IncidenceBase::Visitor + { + public: + AddSubResourceVisitor( T *r, const TQString &subResource ) + : mResource( r ), mSubResource( subResource ) {} + + protected: + bool visit( Event *e ) + { + return mResource->addEvent( e, mSubResource ); + } + bool visit( Todo *t ) + { + return mResource->addTodo( t, mSubResource ); + } + bool visit( Journal *j ) + { + return mResource->addJournal( j, mSubResource ); + } + + private: + T *mResource; + TQString mSubResource; + }; + + /** This class implements a visitor for deleting an Incidence from a resource supporting deleteEvent(), deleteTodo() and deleteJournal() calls. */ diff --git a/libkcal/incidencebase.cpp b/libkcal/incidencebase.cpp index 2aeba830f..a8a6bdb05 100644 --- a/libkcal/incidencebase.cpp +++ b/libkcal/incidencebase.cpp @@ -59,6 +59,10 @@ IncidenceBase::IncidenceBase(const IncidenceBase &i) : mSyncStatus = i.mSyncStatus; mComments = i.mComments; + // The copied object is a new one, so it isn't observed by the observer + // of the original object. + mObservers.clear(); + mAttendees.setAutoDelete( true ); } @@ -404,7 +408,9 @@ void IncidenceBase::updated() while( it.current() ) { Observer *o = it.current(); ++it; - o->incidenceUpdated( this ); + if ( o ) { + o->incidenceUpdated( this ); + } } } diff --git a/libkcal/incidencebase.h b/libkcal/incidencebase.h index 72455b432..fa3a1a619 100644 --- a/libkcal/incidencebase.h +++ b/libkcal/incidencebase.h @@ -131,16 +131,28 @@ class LIBKCAL_EXPORT IncidenceBase : public CustomProperties /** for setting the event's starting date/time with a TQDateTime. */ virtual void setDtStart( const TQDateTime &dtStart ); /** returns an event's starting date/time as a TQDateTime. */ + virtual TQDateTime dtStart() const; - /** returns an event's starting time as a string formatted according to the - users locale settings */ - virtual TQString dtStartTimeStr() const; - /** returns an event's starting date as a string formatted according to the - users locale settings */ - virtual TQString dtStartDateStr( bool shortfmt = true ) const; - /** returns an event's starting date and time as a string formatted according - to the users locale settings */ - virtual TQString dtStartStr() const; + + /** + returns an event's starting time as a string formatted according to the + users locale settings. + @deprecated use IncidenceFormatter::timeToString() + */ + virtual KDE_DEPRECATED TQString dtStartTimeStr() const; + + /** + returns an event's starting date as a string formatted according to the + users locale settings + @deprecated use IncidenceFormatter::dateToString() + */ + virtual KDE_DEPRECATED TQString dtStartDateStr( bool shortfmt = true ) const; + /** + returns an event's starting date and time as a string formatted according + to the users locale settings + @deprecated use IncidenceFormatter::dateTimeToString() + */ + virtual KDE_DEPRECATED TQString dtStartStr() const; virtual void setDuration( int seconds ); int duration() const; diff --git a/libkcal/incidenceformatter.cpp b/libkcal/incidenceformatter.cpp index 618611712..cebdd0b16 100644 --- a/libkcal/incidenceformatter.cpp +++ b/libkcal/incidenceformatter.cpp @@ -3,6 +3,7 @@ Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com> + Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public @@ -42,27 +43,28 @@ #include <kabc/stdaddressbook.h> #include <kapplication.h> -// #include <kdebug.h> +#include <kemailsettings.h> #include <klocale.h> #include <kglobal.h> #include <kiconloader.h> +#include <kcalendarsystem.h> +#include <kmimetype.h> #include <tqbuffer.h> #include <tqstylesheet.h> #include <tqdatetime.h> +#include <tqregexp.h> #include <time.h> - using namespace KCal; +/******************* + * General helpers + *******************/ -/******************************************************************* - * Helper functions for the extensive display (event viewer) - *******************************************************************/ - -static TQString eventViewerAddLink( const TQString &ref, const TQString &text, +static TQString htmlAddLink( const TQString &ref, const TQString &text, bool newline = true ) { TQString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" ); @@ -70,7 +72,7 @@ static TQString eventViewerAddLink( const TQString &ref, const TQString &text, return tmpStr; } -static TQString eventViewerAddTag( const TQString & tag, const TQString & text ) +static TQString htmlAddTag( const TQString & tag, const TQString & text ) { int numLineBreaks = text.contains( "\n" ); TQString str = "<" + tag + ">"; @@ -94,134 +96,299 @@ static TQString eventViewerAddTag( const TQString & tag, const TQString & text ) return tmpStr; } -static TQString linkPerson( const TQString& email, TQString name, TQString uid ) +static bool iamAttendee( Attendee *attendee ) +{ + // Check if I'm this attendee + + bool iam = false; + KEMailSettings settings; + TQStringList profiles = settings.profiles(); + for( TQStringList::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; + TQStringList profiles = settings.profiles(); + for( TQStringList::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 TQString &sender ) +{ + // Check if the specified sender is the organizer + + if ( !incidence || sender.isEmpty() ) { + return true; + } + bool isorg = true; + TQString senderName, senderEmail; + if ( KPIM::getNameAndMail( sender, senderName, senderEmail ) ) { + // 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 TQString firstAttendeeName( Incidence *incidence, const TQString &defName ) +{ + TQString 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; +} + +/******************************************************************* + * Helper functions for the extensive display (display viewer) + *******************************************************************/ + +static TQString displayViewLinkPerson( const TQString& email, TQString name, TQString uid ) { // Make the search, if there is an email address to search on, // and either name or uid is missing if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) { KABC::AddressBook *add_book = KABC::StdAddressBook::self( true ); KABC::Addressee::List addressList = add_book->findByEmail( email ); - KABC::Addressee o = addressList.first(); - if ( !o.isEmpty() && addressList.size() < 2 ) { - if ( name.isEmpty() ) - // No name set, so use the one from the addressbook - name = o.formattedName(); - uid = o.uid(); - } else - // Email not found in the addressbook. Don't make a link - uid = TQString::null; + 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 = TQString::null; + } + } } - kdDebug(5850) << "formatAttendees: uid = " << uid << endl; // Show the attendee - TQString tmpString = "<li>"; + TQString tmpString; if ( !uid.isEmpty() ) { // There is a UID, so make a link to the addressbook - if ( name.isEmpty() ) + if ( name.isEmpty() ) { // Use the email address for text - tmpString += eventViewerAddLink( "uid:" + uid, email ); - else - tmpString += eventViewerAddLink( "uid:" + uid, name ); + 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() ); - tmpString += eventViewerAddLink( mailto.url(), TQString::null ); + mailto.setPath( email ); + const TQString iconPath = + KGlobal::iconLoader()->iconPath( "mail_new", KIcon::Small ); + tmpString += " " + + htmlAddLink( mailto.url(), + "<img valign=\"top\" src=\"" + iconPath + "\">" ); } - tmpString += "</li>\n"; return tmpString; } -static TQString eventViewerFormatAttendees( Incidence *event ) +static TQString displayViewFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role ) { TQString tmpStr; - Attendee::List attendees = event->attendees(); - if ( attendees.count() ) { - - // Add organizer link - tmpStr += eventViewerAddTag( "i", i18n("Organizer") ); - tmpStr += "<ul>"; - tmpStr += linkPerson( event->organizer().email(), - event->organizer().name(), TQString::null ); - tmpStr += "</ul>"; - - // Add attendees links - tmpStr += eventViewerAddTag( "i", i18n("Attendees") ); - tmpStr += "<ul>"; - Attendee::List::ConstIterator it; - for( it = attendees.begin(); it != attendees.end(); ++it ) { - Attendee *a = *it; - tmpStr += linkPerson( a->email(), a->name(), a->uid() ); - if ( !a->delegator().isEmpty() ) { - tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() ); - } - if ( !a->delegate().isEmpty() ) { - tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() ); - } + Attendee::List::ConstIterator it; + Attendee::List attendees = incidence->attendees(); + + for ( it = attendees.begin(); it != attendees.end(); ++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 += "</ul>"; + tmpStr += displayViewLinkPerson( a->email(), a->name(), a->uid() ); + if ( !a->delegator().isEmpty() ) { + tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() ); + } + if ( !a->delegate().isEmpty() ) { + tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() ); + } + tmpStr += "<br>"; + } + if ( tmpStr.endsWith( "<br>" ) ) { + tmpStr.truncate( tmpStr.length() - 4 ); } return tmpStr; } -static TQString eventViewerFormatAttachments( Incidence *i ) +static TQString displayViewFormatAttendees( Incidence *incidence ) +{ + TQString tmpStr, str; + + // Add organizer link + int attendeeCount = incidence->attendees().count(); + if ( attendeeCount > 1 || + ( attendeeCount == 1 && + incidence->organizer().email() != incidence->attendees().first()->email() ) ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Organizer:" ) + "</b></td>"; + tmpStr += "<td>" + + displayViewLinkPerson( incidence->organizer().email(), + incidence->organizer().name(), + TQString::null ) + + "</td>"; + tmpStr += "</tr>"; + } + + // Add "chair" + str = displayViewFormatAttendeeRoleList( incidence, Attendee::Chair ); + if ( !str.isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Chair:" ) + "</b></td>"; + tmpStr += "<td>" + str + "</td>"; + tmpStr += "</tr>"; + } + + // Add required participants + str = displayViewFormatAttendeeRoleList( incidence, Attendee::ReqParticipant ); + if ( !str.isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Required Participants:" ) + "</b></td>"; + tmpStr += "<td>" + str + "</td>"; + tmpStr += "</tr>"; + } + + // Add optional participants + str = displayViewFormatAttendeeRoleList( incidence, Attendee::OptParticipant ); + if ( !str.isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Optional Participants:" ) + "</b></td>"; + tmpStr += "<td>" + str + "</td>"; + tmpStr += "</tr>"; + } + + // Add observers + str = displayViewFormatAttendeeRoleList( incidence, Attendee::NonParticipant ); + if ( !str.isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Observers:" ) + "</b></td>"; + tmpStr += "<td>" + str + "</td>"; + tmpStr += "</tr>"; + } + + return tmpStr; +} + +static TQString displayViewFormatAttachments( Incidence *incidence ) { TQString tmpStr; - Attachment::List as = i->attachments(); - if ( as.count() > 0 ) { - Attachment::List::ConstIterator it; - for( it = as.begin(); it != as.end(); ++it ) { - if ( (*it)->isUri() ) { - TQString name; - if ( (*it)->uri().startsWith( "kmail:" ) ) - name = i18n( "Show mail" ); - else + Attachment::List as = incidence->attachments(); + Attachment::List::ConstIterator it; + uint count = 0; + for( it = as.begin(); it != as.end(); ++it ) { + count++; + if ( (*it)->isUri() ) { + TQString name; + if ( (*it)->uri().startsWith( "kmail:" ) ) { + name = i18n( "Show mail" ); + } else { + if ( (*it)->label().isEmpty() ) { name = (*it)->uri(); - tmpStr += eventViewerAddLink( (*it)->uri(), name ); - tmpStr += "<br>"; + } else { + name = (*it)->label(); + } } + tmpStr += htmlAddLink( (*it)->uri(), name ); + } else { + tmpStr += htmlAddLink( "ATTACH:" + incidence->uid() + ':' + (*it)->label(), + (*it)->label(), false ); + } + if ( count < as.count() ) { + tmpStr += "<br>"; } } return tmpStr; } -/* - FIXME:This function depends of kaddressbook. Is necessary a new - type of event? -*/ -static TQString eventViewerFormatBirthday( Event *event ) +static TQString displayViewFormatCategories( Incidence *incidence ) { - if ( !event) return TQString::null; - if ( event->customProperty("KABC","BIRTHDAY") != "YES" ) return TQString::null; + // We do not use Incidence::categoriesStr() since it does not have whitespace + return incidence->categories().join( ", " ); +} + +static TQString displayViewFormatCreationDate( Incidence *incidence ) +{ + return i18n( "Creation date: %1" ). + arg( IncidenceFormatter::dateTimeToString( incidence->created(), false, true ) ); +} + +static TQString displayViewFormatBirthday( Event *event ) +{ + if ( !event ) { + return TQString::null; + } + if ( event->customProperty("KABC","BIRTHDAY") != "YES" ) { + return TQString::null; + } TQString uid = event->customProperty("KABC","UID-1"); TQString name = event->customProperty("KABC","NAME-1"); TQString email= event->customProperty("KABC","EMAIL-1"); - TQString tmpString = "<ul>"; - tmpString += linkPerson( email, name, uid ); + TQString tmpStr = displayViewLinkPerson( email, name, uid ); if ( event->customProperty( "KABC", "ANNIVERSARY") == "YES" ) { uid = event->customProperty("KABC","UID-2"); name = event->customProperty("KABC","NAME-2"); email= event->customProperty("KABC","EMAIL-2"); - tmpString += linkPerson( email, name, uid ); + tmpStr += "<br>"; + tmpStr += displayViewLinkPerson( email, name, uid ); } - tmpString += "</ul>"; - return tmpString; + return tmpStr; } -static TQString eventViewerFormatHeader( Incidence *incidence ) +static TQString displayViewFormatHeader( Incidence *incidence ) { TQString tmpStr = "<table><tr>"; @@ -230,32 +397,41 @@ static TQString eventViewerFormatHeader( Incidence *incidence ) tmpStr += "<td>"; if ( incidence->type() == "Event" ) { - tmpStr += "<img src=\"" + - KGlobal::iconLoader()->iconPath( "appointment", KIcon::Small ) + - "\">"; + TQString iconPath; + if ( incidence->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) { + if ( incidence->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) { + iconPath = + KGlobal::iconLoader()->iconPath( "calendaranniversary", KIcon::Small ); + } else { + iconPath = KGlobal::iconLoader()->iconPath( "calendarbirthday", KIcon::Small ); + } + } else { + iconPath = KGlobal::iconLoader()->iconPath( "appointment", KIcon::Small ); + } + tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">"; } if ( incidence->type() == "Todo" ) { - tmpStr += "<img src=\"" + + tmpStr += "<img valign=\"top\" src=\"" + KGlobal::iconLoader()->iconPath( "todo", KIcon::Small ) + "\">"; } if ( incidence->type() == "Journal" ) { - tmpStr += "<img src=\"" + + tmpStr += "<img valign=\"top\" src=\"" + KGlobal::iconLoader()->iconPath( "journal", KIcon::Small ) + "\">"; } if ( incidence->isAlarmEnabled() ) { - tmpStr += "<img src=\"" + + tmpStr += "<img valign=\"top\" src=\"" + KGlobal::iconLoader()->iconPath( "bell", KIcon::Small ) + "\">"; } if ( incidence->doesRecur() ) { - tmpStr += "<img src=\"" + + tmpStr += "<img valign=\"top\" src=\"" + KGlobal::iconLoader()->iconPath( "recur", KIcon::Small ) + "\">"; } if ( incidence->isReadOnly() ) { - tmpStr += "<img src=\"" + + tmpStr += "<img valign=\"top\" src=\"" + KGlobal::iconLoader()->iconPath( "readonlyevent", KIcon::Small ) + "\">"; } @@ -263,245 +439,409 @@ static TQString eventViewerFormatHeader( Incidence *incidence ) tmpStr += "</td>"; } - tmpStr += "<td>" - + eventViewerAddTag( "u", - eventViewerAddTag( "b", incidence->summary() ) ) - + "</td>"; - tmpStr += "</tr></table><br>"; + tmpStr += "<td>"; + tmpStr += "<b><u>" + incidence->summary() + "</u></b>"; + tmpStr += "</td>"; + + tmpStr += "</tr></table>"; return tmpStr; } -static TQString eventViewerFormatEvent( Event *event ) +static TQString displayViewFormatEvent( Calendar *calendar, Event *event, + const TQDate &date ) { - if ( !event ) return TQString::null; - TQString tmpStr = eventViewerFormatHeader( event ); + if ( !event ) { + return TQString::null; + } + + TQString tmpStr = displayViewFormatHeader( event ); tmpStr += "<table>"; + tmpStr += "<col width=\"25%\"/>"; + tmpStr += "<col width=\"75%\"/>"; + + if ( calendar ) { + TQString calStr = IncidenceFormatter::resourceString( calendar, event ); + if ( !calStr.isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>"; + tmpStr += "<td>" + calStr + "</td>"; + tmpStr += "</tr>"; + } + } + + if ( !event->location().isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>"; + tmpStr += "<td>" + event->location() + "</td>"; + tmpStr += "</tr>"; + } + + TQDateTime startDt = event->dtStart(); + TQDateTime endDt = event->dtEnd(); + if ( event->doesRecur() ) { + if ( date.isValid() ) { + TQDateTime dt( date, TQTime( 0, 0, 0 ) ); + int diffDays = startDt.daysTo( dt ); + dt = dt.addSecs( -1 ); + startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() ); + if ( event->hasEndDate() ) { + endDt = endDt.addDays( diffDays ); + if ( startDt > endDt ) { + startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() ); + endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) ); + } + } + } + } tmpStr += "<tr>"; if ( event->doesFloat() ) { if ( event->isMultiDay() ) { - tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>"; - tmpStr += "<td>" + i18n("<beginTime> - <endTime>","%1 - %2") - .arg( event->dtStartDateStr() ) - .arg( event->dtEndDateStr() ) + "</td>"; + tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>"; + tmpStr += "<td>" + + i18n("<beginDate> - <endDate>","%1 - %2"). + arg( IncidenceFormatter::dateToString( startDt, false ) ). + arg( IncidenceFormatter::dateToString( endDt, false ) ) + + "</td>"; } else { - tmpStr += "<td align=\"right\"><b>" + i18n( "Date" ) + "</b></td>"; - tmpStr += "<td>" + i18n("date as string","%1").arg( event->dtStartDateStr() ) + "</td>"; + tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>"; + tmpStr += "<td>" + + i18n("date as string","%1"). + arg( IncidenceFormatter::dateToString( startDt, false ) ) + + "</td>"; } } else { if ( event->isMultiDay() ) { - tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>"; - tmpStr += "<td>" + i18n("<beginTime> - <endTime>","%1 - %2") - .arg( event->dtStartStr() ) - .arg( event->dtEndStr() ) + "</td>"; + tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>"; + tmpStr += "<td>" + + i18n("<beginDate> - <endDate>","%1 - %2"). + arg( IncidenceFormatter::dateToString( startDt, false ) ). + arg( IncidenceFormatter::dateToString( endDt, false ) ) + + "</td>"; } else { - tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>"; - if ( event->hasEndDate() && event->dtStart() != event->dtEnd()) { - tmpStr += "<td>" + i18n("<beginTime> - <endTime>","%1 - %2") - .arg( event->dtStartTimeStr() ) - .arg( event->dtEndTimeStr() ) + "</td>"; + tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>"; + tmpStr += "<td>" + + i18n("date as string","%1"). + arg( IncidenceFormatter::dateToString( startDt, false ) ) + + "</td>"; + + tmpStr += "</tr><tr>"; + tmpStr += "<td><b>" + i18n( "Time:" ) + "</b></td>"; + if ( event->hasEndDate() && startDt != endDt ) { + tmpStr += "<td>" + + i18n("<beginTime> - <endTime>","%1 - %2"). + arg( IncidenceFormatter::timeToString( startDt, true ) ). + arg( IncidenceFormatter::timeToString( endDt, true ) ) + + "</td>"; } else { - tmpStr += "<td>" + event->dtStartTimeStr() + "</td>"; + tmpStr += "<td>" + + IncidenceFormatter::timeToString( startDt, true ) + + "</td>"; } - tmpStr += "</tr><tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "Date" ) + "</b></td>"; - tmpStr += "<td>" + i18n("date as string","%1") - .arg( event->dtStartDateStr() ) + "</td>"; } } tmpStr += "</tr>"; - if ( event->customProperty("KABC","BIRTHDAY")== "YES" ) { + TQString durStr = IncidenceFormatter::durationString( event ); + if ( !durStr.isEmpty() ) { tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "Birthday" ) + "</b></td>"; - tmpStr += "<td>" + eventViewerFormatBirthday( event ) + "</td>"; + tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>"; + tmpStr += "<td>" + durStr + "</td>"; tmpStr += "</tr>"; - tmpStr += "</table>"; - return tmpStr; } - if ( !event->description().isEmpty() ) { + if ( event->doesRecur() ) { tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "Description" ) + "</b></td>"; - tmpStr += "<td>" + eventViewerAddTag( "p", event->description() ) + "</td>"; + tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>"; + tmpStr += "<td>" + + IncidenceFormatter::recurrenceString( event ) + + "</td>"; tmpStr += "</tr>"; } - if ( !event->location().isEmpty() ) { + if ( event->customProperty("KABC","BIRTHDAY")== "YES" ) { tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "Location" ) + "</b></td>"; - tmpStr += "<td>" + event->location() + "</td>"; + if ( event->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) { + tmpStr += "<td><b>" + i18n( "Anniversary:" ) + "</b></td>"; + } else { + tmpStr += "<td><b>" + i18n( "Birthday:" ) + "</b></td>"; + } + tmpStr += "<td>" + displayViewFormatBirthday( event ) + "</td>"; tmpStr += "</tr>"; + tmpStr += "</table>"; + return tmpStr; } - if ( event->categories().count() > 0 ) { + if ( !event->description().isEmpty() ) { tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "1 Category", "%n Categories", event->categories().count() )+ "</b></td>"; - tmpStr += "<td>" + event->categoriesStr() + "</td>"; + tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>"; + tmpStr += "<td>" + event->description() + "</td>"; tmpStr += "</tr>"; } - if ( event->doesRecur() ) { - TQDateTime dt = - event->recurrence()->getNextDateTime( TQDateTime::currentDateTime() ); + // TODO: print comments? + + int reminderCount = event->alarms().count(); + if ( reminderCount > 0 && event->isAlarmEnabled() ) { tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "Next on" ) + "</b></td>"; - if ( !event->doesFloat() ) { - tmpStr += "<td>" + - KGlobal::locale()->formatDateTime( dt, true ) + "</td>"; - } else { - tmpStr += "<td>" + - KGlobal::locale()->formatDate( dt.date(), true ) + "</td>"; - } + tmpStr += "<td><b>" + + i18n( "Reminder:", "%n Reminders:", reminderCount ) + + "</b></td>"; + tmpStr += "<td>" + IncidenceFormatter::reminderStringList( event ).join( "<br>" ) + "</td>"; tmpStr += "</tr>"; } - int attendeeCount = event->attendees().count(); - if ( attendeeCount > 0 ) { - tmpStr += "<tr><td colspan=\"2\">"; - tmpStr += eventViewerFormatAttendees( event ); - tmpStr += "</td></tr>"; + tmpStr += displayViewFormatAttendees( event ); + + int categoryCount = event->categories().count(); + if ( categoryCount > 0 ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + + i18n( "Category:", "%n Categories:", categoryCount ) + + "</b></td>"; + tmpStr += "<td>" + displayViewFormatCategories( event ) + "</td>"; + tmpStr += "</tr>"; } int attachmentCount = event->attachments().count(); if ( attachmentCount > 0 ) { tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "1 attachment", "%n attachments", attachmentCount )+ "</b></td>"; - tmpStr += "<td>" + eventViewerFormatAttachments( event ) + "</td>"; + tmpStr += "<td><b>" + + i18n( "Attachment:", "%n Attachments:", attachmentCount ) + + "</b></td>"; + tmpStr += "<td>" + displayViewFormatAttachments( event ) + "</td>"; tmpStr += "</tr>"; } - tmpStr += "</table>"; - tmpStr += "<em>" + i18n( "Creation date: %1.").arg( - KGlobal::locale()->formatDateTime( event->created() , true ) ) + "</em>"; + + tmpStr += "<em>" + displayViewFormatCreationDate( event ) + "</em>"; + return tmpStr; } -static TQString eventViewerFormatTodo( Todo *todo ) +static TQString displayViewFormatTodo( Calendar *calendar, Todo *todo, + const TQDate &date ) { - if ( !todo ) return TQString::null; - TQString tmpStr = eventViewerFormatHeader( todo ); + if ( !todo ) { + return TQString::null; + } + + TQString tmpStr = displayViewFormatHeader( todo ); tmpStr += "<table>"; + tmpStr += "<col width=\"25%\"/>"; + tmpStr += "<col width=\"75%\"/>"; + + if ( calendar ) { + TQString calStr = IncidenceFormatter::resourceString( calendar, todo ); + if ( !calStr.isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>"; + tmpStr += "<td>" + calStr + "</td>"; + tmpStr += "</tr>"; + } + } - if ( todo->hasDueDate() ) { + if ( !todo->location().isEmpty() ) { tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "Due on" ) + "</b></td>"; - if ( !todo->doesFloat() ) { - tmpStr += "<td>" + - KGlobal::locale()->formatDateTime( todo->dtDue(), true ) + - "</td>"; - } else { - tmpStr += "<td>" + - KGlobal::locale()->formatDate( todo->dtDue().date(), true ) + - "</td>"; + tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>"; + tmpStr += "<td>" + todo->location() + "</td>"; + tmpStr += "</tr>"; + } + + if ( todo->hasStartDate() && todo->dtStart().isValid() ) { + TQDateTime startDt = todo->dtStart(); + if ( todo->doesRecur() ) { + if ( date.isValid() ) { + startDt.setDate( date ); + } } + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Start:" ) + "</b></td>"; + tmpStr += "<td>" + + IncidenceFormatter::dateTimeToString( startDt, + todo->doesFloat(), false ) + + "</td>"; tmpStr += "</tr>"; } - if ( !todo->description().isEmpty() ) { + if ( todo->hasDueDate() && todo->dtDue().isValid() ) { + TQDateTime dueDt = todo->dtDue(); + if ( todo->doesRecur() ) { + if ( date.isValid() ) { + TQDateTime dt( date, TQTime( 0, 0, 0 ) ); + dt = dt.addSecs( -1 ); + dueDt.setDate( todo->recurrence()->getNextDateTime( dt ).date() ); + } + } tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "Description" ) + "</b></td>"; - tmpStr += "<td>" + todo->description() + "</td>"; + tmpStr += "<td><b>" + i18n( "Due:" ) + "</b></td>"; + tmpStr += "<td>" + + IncidenceFormatter::dateTimeToString( dueDt, + todo->doesFloat(), false ) + + "</td>"; tmpStr += "</tr>"; } - if ( !todo->location().isEmpty() ) { + TQString durStr = IncidenceFormatter::durationString( todo ); + if ( !durStr.isEmpty() ) { tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "Location" ) + "</b></td>"; - tmpStr += "<td>" + todo->location() + "</td>"; + tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>"; + tmpStr += "<td>" + durStr + "</td>"; tmpStr += "</tr>"; } - if ( todo->categories().count() > 0 ) { + if ( todo->doesRecur() ) { tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "1 Category", "%n Categories", todo->categories().count() )+ "</b></td>"; - tmpStr += "<td>" + todo->categoriesStr() + "</td>"; + tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>"; + tmpStr += "<td>" + + IncidenceFormatter::recurrenceString( todo ) + + "</td>"; tmpStr += "</tr>"; } - tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "Priority" ) + "</b></td>"; - if ( todo->priority() > 0 ) { - tmpStr += "<td>" + TQString::number( todo->priority() ) + "</td>"; - } else { - tmpStr += "<td>" + i18n( "Unspecified" ) + "</td>"; + if ( !todo->description().isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>"; + tmpStr += "<td>" + todo->description() + "</td>"; + tmpStr += "</tr>"; } - tmpStr += "</tr>"; - tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "Completed" ) + "</b></td>"; - tmpStr += "<td>" + i18n( "%1 %" ).arg( todo->percentComplete() ) + "</td>"; - tmpStr += "</tr>"; + // TODO: print comments? - if ( todo->doesRecur() ) { - TQDateTime dt = - todo->recurrence()->getNextDateTime( TQDateTime::currentDateTime() ); + int reminderCount = todo->alarms().count(); + if ( reminderCount > 0 && todo->isAlarmEnabled() ) { tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "Next on" ) + "</b></td>"; - if ( !todo->doesFloat() ) { - tmpStr += "<td>" + - KGlobal::locale()->formatDateTime( dt, true ) + "</td>"; - } else { - tmpStr += "<td>" + - KGlobal::locale()->formatDate( dt.date(), true ) + "</td>"; - } + tmpStr += "<td><b>" + + i18n( "Reminder:", "%n Reminders:", reminderCount ) + + "</b></td>"; + tmpStr += "<td>" + IncidenceFormatter::reminderStringList( todo ).join( "<br>" ) + "</td>"; + tmpStr += "</tr>"; + } + + tmpStr += displayViewFormatAttendees( todo ); + + int categoryCount = todo->categories().count(); + if ( categoryCount > 0 ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + + i18n( "Category:", "%n Categories:", categoryCount ) + + "</b></td>"; + tmpStr += "<td>" + displayViewFormatCategories( todo ) + "</td>"; + tmpStr += "</tr>"; + } + + if ( todo->priority() > 0 ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Priority:" ) + "</b></td>"; + tmpStr += "<td>"; + tmpStr += TQString::number( todo->priority() ); + tmpStr += "</td>"; tmpStr += "</tr>"; } - int attendeeCount = todo->attendees().count(); - if ( attendeeCount > 0 ) { - tmpStr += "<tr><td colspan=\"2\">"; - tmpStr += eventViewerFormatAttendees( todo ); - tmpStr += "</td></tr>"; + tmpStr += "<tr>"; + if ( todo->isCompleted() ) { + tmpStr += "<td><b>" + i18n( "Completed:" ) + "</b></td>"; + tmpStr += "<td>"; + tmpStr += todo->completedStr(); + } else { + tmpStr += "<td><b>" + i18n( "Percent Done:" ) + "</b></td>"; + tmpStr += "<td>"; + tmpStr += i18n( "%1%" ).arg( todo->percentComplete() ); } + tmpStr += "</td>"; + tmpStr += "</tr>"; int attachmentCount = todo->attachments().count(); if ( attachmentCount > 0 ) { tmpStr += "<tr>"; - tmpStr += "<td align=\"right\"><b>" + i18n( "1 attachment", "%n attachments", attachmentCount )+ "</b></td>"; - tmpStr += "<td>" + eventViewerFormatAttachments( todo ) + "</td>"; + tmpStr += "<td><b>" + + i18n( "Attachment:", "Attachments:", attachmentCount ) + + "</b></td>"; + tmpStr += "<td>" + displayViewFormatAttachments( todo ) + "</td>"; tmpStr += "</tr>"; } tmpStr += "</table>"; - tmpStr += "<em>" + i18n( "Creation date: %1.").arg( - KGlobal::locale()->formatDateTime( todo->created(), true ) ) + "</em>"; + + tmpStr += "<em>" + displayViewFormatCreationDate( todo ) + "</em>"; + return tmpStr; } -static TQString eventViewerFormatJournal( Journal *journal ) +static TQString displayViewFormatJournal( Calendar *calendar, Journal *journal ) { - if ( !journal ) return TQString::null; + if ( !journal ) { + return TQString::null; + } - TQString tmpStr; - if ( !journal->summary().isEmpty() ) { - tmpStr += eventViewerAddTag( "u", - eventViewerAddTag( "b", journal->summary() ) ); + TQString tmpStr = displayViewFormatHeader( journal ); + + tmpStr += "<table>"; + tmpStr += "<col width=\"25%\"/>"; + tmpStr += "<col width=\"75%\"/>"; + + if ( calendar ) { + TQString calStr = IncidenceFormatter::resourceString( calendar, journal ); + if ( !calStr.isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>"; + tmpStr += "<td>" + calStr + "</td>"; + tmpStr += "</tr>"; + } } - tmpStr += eventViewerAddTag( "b", i18n("Journal for %1").arg( journal->dtStartDateStr( false ) ) ); - if ( !journal->description().isEmpty() ) - tmpStr += eventViewerAddTag( "p", journal->description() ); + + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>"; + tmpStr += "<td>" + + IncidenceFormatter::dateToString( journal->dtStart(), false ) + + "</td>"; + tmpStr += "</tr>"; + + if ( !journal->description().isEmpty() ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>"; + tmpStr += "<td>" + journal->description() + "</td>"; + tmpStr += "</tr>"; + } + + int categoryCount = journal->categories().count(); + if ( categoryCount > 0 ) { + tmpStr += "<tr>"; + tmpStr += "<td><b>" + + i18n( "Category:", "%n Categories:", categoryCount ) + + "</b></td>"; + tmpStr += "<td>" + displayViewFormatCategories( journal ) + "</td>"; + tmpStr += "</tr>"; + } + tmpStr += "</table>"; + + tmpStr += "<em>" + displayViewFormatCreationDate( journal ) + "</em>"; + return tmpStr; } -static TQString eventViewerFormatFreeBusy( FreeBusy *fb ) +static TQString displayViewFormatFreeBusy( Calendar * /*calendar*/, FreeBusy *fb ) { - if ( !fb ) return TQString::null; + if ( !fb ) { + return TQString::null; + } + + TQString tmpStr = htmlAddTag( "h2", + htmlAddTag( "b", + i18n("Free/Busy information for %1"). + arg( fb->organizer().fullName() ) ) ); - TQString tmpStr = - eventViewerAddTag( "u", - eventViewerAddTag( "b", i18n("Free/Busy information for %1") - .arg( fb->organizer().fullName() ) ) ); - tmpStr += eventViewerAddTag( "i", i18n("Busy times in date range %1 - %2:") - .arg( KGlobal::locale()->formatDate( fb->dtStart().date(), true ) ) - .arg( KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) ) ); + tmpStr += htmlAddTag( "h4", i18n("Busy times in date range %1 - %2:"). + arg( IncidenceFormatter::dateToString( fb->dtStart(), true ) ). + arg( IncidenceFormatter::dateToString( fb->dtEnd(), true ) ) ); TQValueList<Period> periods = fb->busyPeriods(); - TQString text = eventViewerAddTag( "em", eventViewerAddTag( "b", i18n("Busy:") ) ); + TQString text = htmlAddTag( "em", htmlAddTag( "b", i18n("Busy:") ) ); TQValueList<Period>::iterator it; for ( it = periods.begin(); it != periods.end(); ++it ) { Period per = *it; @@ -519,91 +859,121 @@ static TQString eventViewerFormatFreeBusy( FreeBusy *fb ) if ( dur > 0 ) { cont += i18n("1 second", "%n seconds", dur); } - text += i18n("startDate for duration", "%1 for %2") - .arg( KGlobal::locale()->formatDateTime( per.start(), false ) ) - .arg( cont ); + text += i18n("startDate for duration", "%1 for %2"). + arg( IncidenceFormatter::dateTimeToString( per.start(), false, true ) ). + arg( cont ); text += "<br>"; } else { if ( per.start().date() == per.end().date() ) { - text += i18n("date, fromTime - toTime ", "%1, %2 - %3") - .arg( KGlobal::locale()->formatDate( per.start().date() ) ) - .arg( KGlobal::locale()->formatTime( per.start().time() ) ) - .arg( KGlobal::locale()->formatTime( per.end().time() ) ); + text += i18n("date, fromTime - toTime ", "%1, %2 - %3"). + arg( IncidenceFormatter::dateToString( per.start().date(), true ) ). + arg( IncidenceFormatter::timeToString( per.start(), true ) ). + arg( IncidenceFormatter::timeToString( per.end(), true ) ); } else { - text += i18n("fromDateTime - toDateTime", "%1 - %2") - .arg( KGlobal::locale()->formatDateTime( per.start(), false ) ) - .arg( KGlobal::locale()->formatDateTime( per.end(), false ) ); + text += i18n("fromDateTime - toDateTime", "%1 - %2"). + arg( IncidenceFormatter::dateTimeToString( per.start(), false, true ) ). + arg( IncidenceFormatter::dateTimeToString( per.end(), false, true ) ); } text += "<br>"; } } - tmpStr += eventViewerAddTag( "p", text ); + tmpStr += htmlAddTag( "p", text ); return tmpStr; } class IncidenceFormatter::EventViewerVisitor : public IncidenceBase::Visitor { public: - EventViewerVisitor() { mResult = ""; } - bool act( IncidenceBase *incidence ) { return incidence->accept( *this ); } + EventViewerVisitor() + : mCalendar( 0 ), mResult( "" ) {} + + bool act( Calendar *calendar, IncidenceBase *incidence, const TQDate &date ) + { + mCalendar = calendar; + mDate = date; + mResult = ""; + return incidence->accept( *this ); + } TQString result() const { return mResult; } + protected: bool visit( Event *event ) { - mResult = eventViewerFormatEvent( event ); + mResult = displayViewFormatEvent( mCalendar, event, mDate ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { - mResult = eventViewerFormatTodo( todo ); + mResult = displayViewFormatTodo( mCalendar, todo, mDate ); return !mResult.isEmpty(); } bool visit( Journal *journal ) { - mResult = eventViewerFormatJournal( journal ); + mResult = displayViewFormatJournal( mCalendar, journal ); return !mResult.isEmpty(); } bool visit( FreeBusy *fb ) { - mResult = eventViewerFormatFreeBusy( fb ); + mResult = displayViewFormatFreeBusy( mCalendar, fb ); return !mResult.isEmpty(); } protected: + Calendar *mCalendar; + TQDate mDate; TQString mResult; }; TQString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence ) { - if ( !incidence ) return TQString::null; + return extensiveDisplayStr( 0, incidence, TQDate() ); +} + +TQString IncidenceFormatter::extensiveDisplayStr( Calendar *calendar, + IncidenceBase *incidence, + const TQDate &date ) +{ + if ( !incidence ) { + return TQString::null; + } + EventViewerVisitor v; - if ( v.act( incidence ) ) { + if ( v.act( calendar, incidence, date ) ) { return v.result(); - } else + } else { return TQString::null; + } } - - - -/******************************************************************* - * Helper functions for the body part formatter of kmail - *******************************************************************/ +/*********************************************************************** + * Helper functions for the body part formatter of kmail (Invitations) + ***********************************************************************/ static TQString string2HTML( const TQString& str ) { return TQStyleSheet::convertFromPlainText(str, TQStyleSheetItem::WhiteSpaceNormal); } +static TQString cleanHtml( const TQString &html ) +{ + TQRegExp rx( "<body[^>]*>(.*)</body>" ); + rx.setCaseSensitive( false ); + rx.search( html ); + TQString body = rx.cap( 1 ); + + return TQStyleSheet::escape( body.remove( TQRegExp( "<[^>]*>" ) ).stripWhiteSpace() ); +} + static TQString eventStartTimeStr( Event *event ) { TQString tmp; - if ( ! event->doesFloat() ) { - tmp = i18n("%1: Start Date, %2: Start Time", "%1 %2") - .arg( event->dtStartDateStr(), event->dtStartTimeStr() ); + if ( !event->doesFloat() ) { + tmp = i18n( "%1: Start Date, %2: Start Time", "%1 %2" ). + arg( IncidenceFormatter::dateToString( event->dtStart(), true ), + IncidenceFormatter::timeToString( event->dtStart(), true ) ); } else { - tmp = i18n("%1: Start Date", "%1 (time unspecified)") - .arg( event->dtStartDateStr() ); + tmp = i18n( "%1: Start Date", "%1 (all day)" ). + arg( IncidenceFormatter::dateToString( event->dtStart(), true ) ); } return tmp; } @@ -611,16 +981,15 @@ static TQString eventStartTimeStr( Event *event ) static TQString eventEndTimeStr( Event *event ) { TQString tmp; - if ( event->hasEndDate() ) { - if ( ! event->doesFloat() ) { - tmp = i18n("%1: End Date, %2: End Time", "%1 %2") - .arg( event->dtEndDateStr(), event->dtEndTimeStr() ); + if ( event->hasEndDate() && event->dtEnd().isValid() ) { + if ( !event->doesFloat() ) { + tmp = i18n( "%1: End Date, %2: End Time", "%1 %2" ). + arg( IncidenceFormatter::dateToString( event->dtEnd(), true ), + IncidenceFormatter::timeToString( event->dtEnd(), true ) ); } else { - tmp = i18n("%1: End Date", "%1 (time unspecified)") - .arg( event->dtEndDateStr() ); + tmp = i18n( "%1: End Date", "%1 (all day)" ). + arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) ); } - } else { - tmp = i18n( "Unspecified" ); } return tmp; } @@ -630,198 +999,495 @@ static TQString invitationRow( const TQString &cell1, const TQString &cell2 ) return "<tr><td>" + cell1 + "</td><td>" + cell2 + "</td></tr>\n"; } -static TQString invitationsDetailsIncidence( Incidence *incidence ) +static Attendee *findDelegatedFromMyAttendee( Incidence *incidence ) +{ + // Return the first attendee that was delegated-from me + + Attendee *attendee = 0; + if ( !incidence ) { + return attendee; + } + + KEMailSettings settings; + TQStringList profiles = settings.profiles(); + for( TQStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) { + settings.setProfile( *it ); + + TQString delegatorName, delegatorEmail; + Attendee::List attendees = incidence->attendees(); + Attendee::List::ConstIterator it2; + for ( it2 = attendees.begin(); it2 != attendees.end(); ++it2 ) { + Attendee *a = *it2; + KPIM::getNameAndMail( a->delegator(), delegatorName, delegatorEmail ); + 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; + TQStringList profiles = settings.profiles(); + for( TQStringList::Iterator it=profiles.begin(); it!=profiles.end(); ++it ) { + settings.setProfile( *it ); + + Attendee::List attendees = incidence->attendees(); + Attendee::List::ConstIterator it2; + for ( it2 = attendees.begin(); it2 != attendees.end(); ++it2 ) { + Attendee *a = *it2; + if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) { + attendee = a; + break; + } + } + } + return attendee; +} + +static Attendee *findAttendee( Incidence *incidence, const TQString &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.begin(); it != attendees.end(); ++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.begin(); it != attendees.end(); ++it ) { + if ( it == attendees.begin() ) { + rsvp = (*it)->RSVP(); // use what the first one has + } else { + if ( (*it)->RSVP() != rsvp ) { + rsvp = true; // they differ, default + break; + } + } + } + return rsvp; +} + +static TQString rsvpRequestedStr( bool rsvpRequested, const TQString &role ) +{ + if ( rsvpRequested ) { + if ( role.isEmpty() ) { + return i18n( "Your response is requested" ); + } else { + return i18n( "Your response as <b>%1</b> is requested" ).arg( role ); + } + } else { + if ( role.isEmpty() ) { + return i18n( "No response is necessary" ); + } else { + return i18n( "No response as <b>%1</b> is necessary" ).arg( role ); + } + } +} + +static TQString myStatusStr( Incidence *incidence ) +{ + TQString ret; + Attendee *a = findMyAttendee( incidence ); + if ( a && + a->status() != Attendee::NeedsAction && a->status() != Attendee::Delegated ) { + ret = i18n( "(<b>Note</b>: the Organizer preset your response to <b>%1</b>)" ). + arg( Attendee::statusName( a->status() ) ); + } + return ret; +} + +static TQString invitationPerson( const TQString& email, TQString name, TQString uid ) { + // Make the search, if there is an email address to search on, + // and either name or uid is missing + if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) { + KABC::AddressBook *add_book = KABC::StdAddressBook::self( true ); + KABC::Addressee::List addressList = add_book->findByEmail( email ); + 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 = TQString::null; + } + } + } + + // Show the attendee + TQString 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 TQString iconPath = + KGlobal::iconLoader()->iconPath( "mail_new", KIcon::Small ); + tmpString += " " + + htmlAddLink( mailto.url(), "<img src=\"" + iconPath + "\">" ) +; + } + tmpString += "\n"; + + return tmpString; +} + +static TQString 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 + TQString html; - TQString descr = incidence->description(); + TQString descr; + TQStringList comments; + + if ( incidence->comments().isEmpty() ) { + if ( !incidence->description().isEmpty() ) { + // use description as comments + if ( !TQStyleSheet::mightBeRichText( incidence->description() ) ) { + comments << string2HTML( incidence->description() ); + } else { + comments << incidence->description(); + if ( noHtmlMode ) { + comments[0] = cleanHtml( comments[0] ); + } + comments[0] = htmlAddTag( "p", comments[0] ); + } + } + //else desc and comments are empty + } else { + // non-empty comments + TQStringList cl = incidence->comments(); + uint i = 0; + for( TQStringList::Iterator it=cl.begin(); it!=cl.end(); ++it ) { + if ( !TQStyleSheet::mightBeRichText( *it ) ) { + comments.append( string2HTML( *it ) ); + } else { + if ( noHtmlMode ) { + comments.append( cleanHtml( "<body>" + (*it) + "</body>" ) ); + } else { + comments.append( *it ); + } + } + i++; + } + if ( !incidence->description().isEmpty() ) { + // use description too + if ( !TQStyleSheet::mightBeRichText( incidence->description() ) ) { + descr = string2HTML( incidence->description() ); + } else { + descr = incidence->description(); + if ( noHtmlMode ) { + descr = cleanHtml( descr ); + } + descr = htmlAddTag( "p", descr ); + } + } + } + if( !descr.isEmpty() ) { - html += "<br/><u>" + i18n("Description:") - + "</u><table border=\"0\"><tr><td> </td><td>"; - html += string2HTML(descr) + "</td></tr></table>"; + html += "<p>"; + html += "<table border=\"0\" style=\"margin-top:4px;\">"; + html += "<tr><td><center>" + + htmlAddTag( "u", i18n( "Description:" ) ) + + "</center></td></tr>"; + html += "<tr><td>" + descr + "</td></tr>"; + html += "</table>"; } - TQStringList comments = incidence->comments(); + if ( !comments.isEmpty() ) { - html += "<br><u>" + i18n("Comments:") - + "</u><table border=\"0\"><tr><td> </td><td><ul>"; - for ( uint i = 0; i < comments.count(); ++i ) - html += "<li>" + string2HTML( comments[i] ) + "</li>"; - html += "</ul></td></tr></table>"; + html += "<p>"; + html += "<table border=\"0\" style=\"margin-top:4px;\">"; + html += "<tr><td><center>" + + htmlAddTag( "u", i18n( "Comments:" ) ) + + "</center></td></tr>"; + html += "<tr><td>"; + if ( comments.count() > 1 ) { + html += "<ul>"; + for ( uint i=0; i < comments.count(); ++i ) { + html += "<li>" + comments[i] + "</li>"; + } + html += "</ul>"; + } else { + html += comments[0]; + } + html += "</td></tr>"; + html += "</table>"; } return html; } -static TQString invitationDetailsEvent( Event* event ) +static TQString invitationDetailsEvent( Event* event, bool noHtmlMode ) { - // Meeting details are formatted into an HTML table - if ( !event ) + // Invitation details are formatted into an HTML table + if ( !event ) { return TQString::null; - - TQString html; - TQString tmp; + } TQString sSummary = i18n( "Summary unspecified" ); - if ( ! event->summary().isEmpty() ) { - sSummary = string2HTML( event->summary() ); + if ( !event->summary().isEmpty() ) { + if ( !TQStyleSheet::mightBeRichText( event->summary() ) ) { + sSummary = TQStyleSheet::escape( event->summary() ); + } else { + sSummary = event->summary(); + if ( noHtmlMode ) { + sSummary = cleanHtml( sSummary ); + } + } } TQString sLocation = i18n( "Location unspecified" ); - if ( ! event->location().isEmpty() ) { - sLocation = string2HTML( event->location() ); + if ( !event->location().isEmpty() ) { + if ( !TQStyleSheet::mightBeRichText( event->location() ) ) { + sLocation = TQStyleSheet::escape( event->location() ); + } else { + sLocation = event->location(); + if ( noHtmlMode ) { + sLocation = cleanHtml( sLocation ); + } + } } TQString dir = ( TQApplication::reverseLayout() ? "rtl" : "ltr" ); - html = TQString("<div dir=\"%1\">\n").arg(dir); + TQString html = TQString("<div dir=\"%1\">\n").arg(dir); html += "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n"; - // Meeting summary & location rows + // Invitation summary & location rows html += invitationRow( i18n( "What:" ), sSummary ); html += invitationRow( i18n( "Where:" ), sLocation ); - // Meeting Start Time Row if (event->doesRecur() == true) { html += invitationRow( i18n( "First Start Time:" ), eventStartTimeStr( event ) ); - } - else { - html += invitationRow( i18n( "Start Time:" ), eventStartTimeStr( event ) ); - } - - // Meeting End Time Row - if (event->doesRecur() == true) { html += invitationRow( i18n( "First End Time:" ), eventEndTimeStr( event ) ); } - else { - html += invitationRow( i18n( "End Time:" ), eventEndTimeStr( event ) ); - } - - // Meeting Duration Row - if ( !event->doesFloat() && event->hasEndDate() ) { - tmp = TQString::null; - TQTime sDuration(0,0,0), t; - int secs = event->dtStart().secsTo( event->dtEnd() ); - t = sDuration.addSecs( secs ); - if ( t.hour() > 0 ) { - tmp += i18n( "1 hour ", "%n hours ", t.hour() ); - } - if ( t.minute() > 0 ) { - tmp += i18n( "1 minute ", "%n minutes ", t.minute() ); +// else { + // If a 1 day event + if ( event->dtStart().date() == event->dtEnd().date() ) { + html += invitationRow( i18n( "Date:" ), + IncidenceFormatter::dateToString( event->dtStart(), false ) ); + if ( !event->doesFloat() ) { + html += invitationRow( i18n( "Time:" ), + IncidenceFormatter::timeToString( event->dtStart(), true ) + + " - " + + IncidenceFormatter::timeToString( event->dtEnd(), true ) ); + } + } else { + html += invitationRow( i18n( "Starting date of an event", "From:" ), + IncidenceFormatter::dateToString( event->dtStart(), false ) ); + if ( !event->doesFloat() ) { + html += invitationRow( i18n( "Starting time of an event", "At:" ), + IncidenceFormatter::timeToString( event->dtStart(), true ) ); + } + if ( event->hasEndDate() ) { + html += invitationRow( i18n( "Ending date of an event", "To:" ), + IncidenceFormatter::dateToString( event->dtEnd(), false ) ); + if ( !event->doesFloat() ) { + html += invitationRow( i18n( "Starting time of an event", "At:" ), + IncidenceFormatter::timeToString( event->dtEnd(), true ) ); + } + } else { + html += invitationRow( i18n( "Ending date of an event", "To:" ), + i18n( "no end date specified" ) ); + } } +// } - html += invitationRow( i18n( "Duration:" ), tmp ); - - if ( event->doesRecur() ) { - TQString recurrence[]= {i18n("no recurrence", "None"), - i18n("Minutely"), i18n("Hourly"), i18n("Daily"), - i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"), - i18n("Yearly"), i18n("Yearly"), i18n("Yearly")}; + // Invitation Duration Row + QString durStr = IncidenceFormatter::durationString( event ); + if ( !durStr.isEmpty() ) { + html += invitationRow( i18n( "Duration:" ), durStr ); + } - Recurrence *recur = event->recurrence(); - if (event->doesRecur() == true) { - html += invitationRow( " ", " " ); - html += invitationRow( i18n( "Recurs:" ), recurrence[ recur->recurrenceType() ] ); - html += invitationRow( i18n("Frequency:"), i18n("%1").arg(event->recurrence()->frequency()) ); + // Recurrence Information Rows + if ( event->doesRecur() ) { + Recurrence *recur = event->recurrence(); + html += invitationRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( event ) ); - if ( recur->duration() > 0 ) { - if ( recur->duration() == 1 ) - html += invitationRow( i18n("Repeats:"), i18n("Once") ); - else - html += invitationRow( i18n("Repeats:"), i18n("%1 times").arg(recur->duration())); - } else { - if ( recur->duration() != -1 ) { - TQString endstr; - if ( event->doesFloat() ) { - endstr = KGlobal::locale()->formatDate( recur->endDate() ); - } else { - endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() ); - } - html += invitationRow( i18n("Repeats until:"), endstr ); - } else { - html += invitationRow( i18n("Repeats:"), i18n("Forever") ); - } + DateList exceptions = recur->exDates(); + if (exceptions.isEmpty() == false) { + bool isFirstExRow; + isFirstExRow = true; + DateList::ConstIterator ex_iter; + for ( ex_iter = exceptions.begin(); ex_iter != exceptions.end(); ++ex_iter ) { + if (isFirstExRow == true) { + isFirstExRow = false; + html += invitationRow( i18n("Cancelled on:"), KGlobal::locale()->formatDate(* ex_iter ) ); } - - DateList exceptions = recur->exDates(); - if (exceptions.isEmpty() == false) { - bool isFirstExRow; - isFirstExRow = true; - DateList::ConstIterator ex_iter; - for ( ex_iter = exceptions.begin(); ex_iter != exceptions.end(); ++ex_iter ) { - if (isFirstExRow == true) { - isFirstExRow = false; - html += invitationRow( i18n("Cancelled on:"), KGlobal::locale()->formatDate(* ex_iter ) ); - } - else { - html += invitationRow(" ", KGlobal::locale()->formatDate(* ex_iter ) ); - } - } + else { + html += invitationRow(" ", KGlobal::locale()->formatDate(* ex_iter ) ); } } } } html += "</table>\n"; - html += invitationsDetailsIncidence( event ); + html += invitationsDetailsIncidence( event, noHtmlMode ); html += "</div>\n"; return html; } -static TQString invitationDetailsTodo( Todo *todo ) +static TQString invitationDetailsTodo( Todo *todo, bool noHtmlMode ) { // Task details are formatted into an HTML table - if ( !todo ) + if ( !todo ) { return TQString::null; + } TQString sSummary = i18n( "Summary unspecified" ); - TQString sDescr = i18n( "Description unspecified" ); - if ( ! todo->summary().isEmpty() ) { - sSummary = todo->summary(); + if ( !todo->summary().isEmpty() ) { + if ( !TQStyleSheet::mightBeRichText( todo->summary() ) ) { + sSummary = TQStyleSheet::escape( todo->summary() ); + } else { + sSummary = todo->summary(); + if ( noHtmlMode ) { + sSummary = cleanHtml( sSummary ); + } + } + } + + TQString sLocation = i18n( "Location unspecified" ); + if ( !todo->location().isEmpty() ) { + if ( !TQStyleSheet::mightBeRichText( todo->location() ) ) { + sLocation = TQStyleSheet::escape( todo->location() ); + } else { + sLocation = todo->location(); + if ( noHtmlMode ) { + sLocation = cleanHtml( sLocation ); + } + } } - if ( ! todo->description().isEmpty() ) { - sDescr = todo->description(); + + TQString dir = ( TQApplication::reverseLayout() ? "rtl" : "ltr" ); + TQString html = TQString("<div dir=\"%1\">\n").arg(dir); + html += "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n"; + + // 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:" ), + IncidenceFormatter::dateToString( todo->dtStart(), false ) ); + if ( !todo->doesFloat() ) { + html += invitationRow( i18n( "Start Time:" ), + IncidenceFormatter::timeToString( todo->dtStart(), false ) ); + } + } + if ( todo->hasDueDate() && todo->dtDue().isValid() ) { + html += invitationRow( i18n( "Due Date:" ), + IncidenceFormatter::dateToString( todo->dtDue(), false ) ); + if ( !todo->doesFloat() ) { + html += invitationRow( i18n( "Due Time:" ), + IncidenceFormatter::timeToString( todo->dtDue(), false ) ); + } + + } else { + html += invitationRow( i18n( "Due Date:" ), i18n( "Due Date: None", "None" ) ); } - TQString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" ); - html += invitationRow( i18n( "Summary:" ), sSummary ); - html += invitationRow( i18n( "Description:" ), sDescr ); - html += "</table>\n"; - html += invitationsDetailsIncidence( todo ); + + html += "</table></div>\n"; + html += invitationsDetailsIncidence( todo, noHtmlMode ); return html; } -static TQString invitationDetailsJournal( Journal *journal ) +static TQString invitationDetailsJournal( Journal *journal, bool noHtmlMode ) { - if ( !journal ) + if ( !journal ) { return TQString::null; + } TQString sSummary = i18n( "Summary unspecified" ); TQString sDescr = i18n( "Description unspecified" ); if ( ! journal->summary().isEmpty() ) { sSummary = journal->summary(); + if ( noHtmlMode ) { + sSummary = cleanHtml( sSummary ); + } } if ( ! journal->description().isEmpty() ) { sDescr = journal->description(); + if ( noHtmlMode ) { + sDescr = cleanHtml( sDescr ); + } } TQString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" ); html += invitationRow( i18n( "Summary:" ), sSummary ); - html += invitationRow( i18n( "Date:" ), journal->dtStartDateStr( false ) ); + html += invitationRow( i18n( "Date:" ), + IncidenceFormatter::dateToString( journal->dtStart(), false ) ); html += invitationRow( i18n( "Description:" ), sDescr ); html += "</table>\n"; - html += invitationsDetailsIncidence( journal ); + html += invitationsDetailsIncidence( journal, noHtmlMode ); return html; } -static TQString invitationDetailsFreeBusy( FreeBusy *fb ) +static TQString invitationDetailsFreeBusy( FreeBusy *fb, bool /*noHtmlMode*/ ) { if ( !fb ) return TQString::null; TQString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" ); html += invitationRow( i18n("Person:"), fb->organizer().fullName() ); - html += invitationRow( i18n("Start date:"), fb->dtStartDateStr() ); + html += invitationRow( i18n("Start date:"), + IncidenceFormatter::dateToString( fb->dtStart(), true ) ); html += invitationRow( i18n("End date:"), - KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) ); + KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) ); html += "<tr><td colspan=2><hr></td></tr>\n"; html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n"; @@ -868,259 +1534,524 @@ static TQString invitationDetailsFreeBusy( FreeBusy *fb ) return html; } -static TQString invitationHeaderEvent( Event *event, ScheduleMessage *msg ) +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 TQString invitationHeaderEvent( Event *event, Incidence *existingIncidence, + ScheduleMessage *msg, const TQString &sender ) { if ( !msg || !event ) return TQString::null; + switch ( msg->method() ) { - case Scheduler::Publish: - return i18n("This event has been published"); - case Scheduler::Request: - if ( event->revision() > 0 ) - return i18n( "This meeting has been updated" ); - return i18n( "You have been invited to this meeting" ); - case Scheduler::Refresh: - return i18n( "This invitation was refreshed" ); - case Scheduler::Cancel: - return i18n( "This meeting has been canceled" ); - case Scheduler::Add: - return i18n( "Addition to the meeting invitation" ); - case Scheduler::Reply: { - Attendee::List attendees = event->attendees(); - if( attendees.count() == 0 ) { - kdDebug(5850) << "No attendees in the iCal reply!\n"; - return TQString::null; + case Scheduler::Publish: + return i18n( "This invitation has been published" ); + case Scheduler::Request: + if ( existingIncidence && event->revision() > 0 ) { + return i18n( "This invitation has been updated by the organizer %1" ). + arg( event->organizer().fullName() ); + } + if ( iamOrganizer( event ) ) { + return i18n( "I created this invitation" ); + } else { + TQString orgStr; + if ( !event->organizer().fullName().isEmpty() ) { + orgStr = event->organizer().fullName(); + } else if ( !event->organizer().email().isEmpty() ) { + orgStr = event->organizer().email(); + } + if ( senderIsOrganizer( event, sender ) ) { + if ( !orgStr.isEmpty() ) { + return i18n( "You received an invitation from %1" ).arg( orgStr ); + } else { + return i18n( "You received an invitation" ); } - if( attendees.count() != 1 ) - kdDebug(5850) << "Warning: attendeecount in the reply should be 1 " - << "but is " << attendees.count() << endl; - Attendee* attendee = *attendees.begin(); - TQString attendeeName = attendee->name(); - if ( attendeeName.isEmpty() ) - attendeeName = attendee->email(); - if ( attendeeName.isEmpty() ) - attendeeName = i18n( "Sender" ); - - TQString delegatorName, dummy; - KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy ); - if ( delegatorName.isEmpty() ) - delegatorName = attendee->delegator(); - - switch( attendee->status() ) { - case Attendee::NeedsAction: - return i18n( "%1 indicates this invitation still needs some action" ).arg( attendeeName ); - case Attendee::Accepted: - if ( delegatorName.isEmpty() ) - return i18n( "%1 accepts this meeting invitation" ).arg( attendeeName ); - return i18n( "%1 accepts this meeting invitation on behalf of %2" ) - .arg( attendeeName ).arg( delegatorName ); - case Attendee::Tentative: - if ( delegatorName.isEmpty() ) - return i18n( "%1 tentatively accepts this meeting invitation" ).arg( attendeeName ); - return i18n( "%1 tentatively accepts this meeting invitation on behalf of %2" ) - .arg( attendeeName ).arg( delegatorName ); - case Attendee::Declined: - if ( delegatorName.isEmpty() ) - return i18n( "%1 declines this meeting invitation" ).arg( attendeeName ); - return i18n( "%1 declines this meeting invitation on behalf of %2" ) - .arg( attendeeName ).arg( delegatorName ); - case Attendee::Delegated: { - TQString delegate, dummy; - KPIM::getNameAndMail( attendee->delegate(), delegate, dummy ); - if ( delegate.isEmpty() ) - delegate = attendee->delegate(); - if ( !delegate.isEmpty() ) - return i18n( "%1 has delegated this meeting invitation to %2" ) - .arg( attendeeName ) .arg( delegate ); - return i18n( "%1 has delegated this meeting invitation" ).arg( attendeeName ); - } - case Attendee::Completed: - return i18n( "This meeting invitation is now completed" ); - case Attendee::InProcess: - return i18n( "%1 is still processing the invitation" ).arg( attendeeName ); - default: - return i18n( "Unknown response to this meeting invitation" ); + } else { + if ( !orgStr.isEmpty() ) { + return i18n( "You received an invitation from %1 as a representative of %2" ). + arg( sender, orgStr ); + } else { + return i18n( "You received an invitation from %1 as the organizer's representative" ). + arg( sender ); } - break; } - case Scheduler::Counter: - return i18n( "Sender makes this counter proposal" ); - case Scheduler::Declinecounter: - return i18n( "Sender declines the counter proposal" ); - case Scheduler::NoMethod: - return i18n("Error: iMIP message with unknown method: '%1'") - .arg( msg->method() ); + } + } + case Scheduler::Refresh: + return i18n( "This invitation was refreshed" ); + case Scheduler::Cancel: + return i18n( "This invitation has been canceled" ); + case Scheduler::Add: + return i18n( "Addition to the invitation" ); + case Scheduler::Reply: + { + if ( replyMeansCounter( event ) ) { + return i18n( "%1 makes this counter proposal" ). + arg( firstAttendeeName( event, i18n( "Sender" ) ) ); + } + + Attendee::List attendees = event->attendees(); + if( attendees.count() == 0 ) { + kdDebug(5850) << "No attendees in the iCal reply!" << endl; + return TQString::null; + } + if( attendees.count() != 1 ) { + kdDebug(5850) << "Warning: attendeecount in the reply should be 1 " + << "but is " << attendees.count() << endl; + } + TQString attendeeName = firstAttendeeName( event, i18n( "Sender" ) ); + + TQString delegatorName, dummy; + Attendee* attendee = *attendees.begin(); + KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy ); + if ( delegatorName.isEmpty() ) { + delegatorName = attendee->delegator(); + } + + switch( attendee->status() ) { + case Attendee::NeedsAction: + return i18n( "%1 indicates this invitation still needs some action" ).arg( attendeeName ); + case Attendee::Accepted: + if ( event->revision() > 0 ) { + if ( !sender.isEmpty() ) { + return i18n( "This invitation has been updated by attendee %1" ).arg( sender ); + } else { + return i18n( "This invitation has been updated by an attendee" ); + } + } else { + if ( delegatorName.isEmpty() ) { + return i18n( "%1 accepts this invitation" ).arg( attendeeName ); + } else { + return i18n( "%1 accepts this invitation on behalf of %2" ). + arg( attendeeName ).arg( delegatorName ); + } + } + case Attendee::Tentative: + if ( delegatorName.isEmpty() ) { + return i18n( "%1 tentatively accepts this invitation" ). + arg( attendeeName ); + } else { + return i18n( "%1 tentatively accepts this invitation on behalf of %2" ). + arg( attendeeName ).arg( delegatorName ); + } + case Attendee::Declined: + if ( delegatorName.isEmpty() ) { + return i18n( "%1 declines this invitation" ).arg( attendeeName ); + } else { + return i18n( "%1 declines this invitation on behalf of %2" ). + arg( attendeeName ).arg( delegatorName ); + } + case Attendee::Delegated: { + TQString delegate, dummy; + KPIM::getNameAndMail( attendee->delegate(), delegate, dummy ); + if ( delegate.isEmpty() ) { + delegate = attendee->delegate(); + } + if ( !delegate.isEmpty() ) { + return i18n( "%1 has delegated this invitation to %2" ). + arg( attendeeName ) .arg( delegate ); + } else { + return i18n( "%1 has delegated this invitation" ).arg( attendeeName ); + } + } + case Attendee::Completed: + return i18n( "This invitation is now completed" ); + case Attendee::InProcess: + return i18n( "%1 is still processing the invitation" ). + arg( attendeeName ); + default: + return i18n( "Unknown response to this invitation" ); + } + break; + } + + case Scheduler::Counter: + return i18n( "%1 makes this counter proposal" ). + arg( firstAttendeeName( event, i18n( "Sender" ) ) ); + + case Scheduler::Declinecounter: + return i18n( "%1 declines the counter proposal" ). + arg( firstAttendeeName( event, i18n( "Sender" ) ) ); + + case Scheduler::NoMethod: + return i18n("Error: iMIP message with unknown method: '%1'"). + arg( msg->method() ); } return TQString::null; } -static TQString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg ) +static TQString invitationHeaderTodo( Todo *todo, Incidence *existingIncidence, + ScheduleMessage *msg, const TQString &sender ) { - if ( !msg || !todo ) + if ( !msg || !todo ) { return TQString::null; + } + switch ( msg->method() ) { - case Scheduler::Publish: - return i18n("This task has been published"); - case Scheduler::Request: - if ( todo->revision() > 0 ) - return i18n( "This task has been updated" ); - return i18n( "You have been assigned this task" ); - case Scheduler::Refresh: - return i18n( "This task was refreshed" ); - case Scheduler::Cancel: - return i18n( "This task was canceled" ); - case Scheduler::Add: - return i18n( "Addition to the task" ); - case Scheduler::Reply: { - Attendee::List attendees = todo->attendees(); - if( attendees.count() == 0 ) { - kdDebug(5850) << "No attendees in the iCal reply!\n"; - return TQString::null; + case Scheduler::Publish: + return i18n("This task has been published"); + case Scheduler::Request: + if ( existingIncidence && todo->revision() > 0 ) { + return i18n( "This task has been updated by the organizer %1" ). + arg( todo->organizer().fullName() ); + } else { + if ( iamOrganizer( todo ) ) { + return i18n( "I created this task" ); + } else { + TQString orgStr; + if ( !todo->organizer().fullName().isEmpty() ) { + orgStr = todo->organizer().fullName(); + } else if ( !todo->organizer().email().isEmpty() ) { + orgStr = todo->organizer().email(); } - if( attendees.count() != 1 ) - kdDebug(5850) << "Warning: attendeecount in the reply should be 1 " - << "but is " << attendees.count() << endl; - Attendee* attendee = *attendees.begin(); - - switch( attendee->status() ) { - case Attendee::NeedsAction: - return i18n( "Sender indicates this task assignment still needs some action" ); - case Attendee::Accepted: - return i18n( "Sender accepts this task" ); - case Attendee::Tentative: - return i18n( "Sender tentatively accepts this task" ); - case Attendee::Declined: - return i18n( "Sender declines this task" ); - case Attendee::Delegated: { - TQString delegate, dummy; - KPIM::getNameAndMail( attendee->delegate(), delegate, dummy ); - if ( delegate.isEmpty() ) - delegate = attendee->delegate(); - if ( !delegate.isEmpty() ) - return i18n( "Sender has delegated this request for the task to %1" ).arg( delegate ); - return i18n( "Sender has delegated this request for the task " ); + if ( senderIsOrganizer( todo, sender ) ) { + if ( !orgStr.isEmpty() ) { + return i18n( "You have been assigned this task by %1" ).arg( orgStr ); + } else { + return i18n( "You have been assigned this task" ); } - case Attendee::Completed: - return i18n( "The request for this task is now completed" ); - case Attendee::InProcess: - return i18n( "Sender is still processing the invitation" ); - default: - return i18n( "Unknown response to this task" ); + } else { + if ( !orgStr.isEmpty() ) { + return i18n( "You have been assigned this task by %1 as a representative of %2" ). + arg( sender, orgStr ); + } else { + return i18n( "You have been assigned this task by %1 as the organizer's representative" ). + arg( sender ); } - break; } - case Scheduler::Counter: - return i18n( "Sender makes this counter proposal" ); - case Scheduler::Declinecounter: - return i18n( "Sender declines the counter proposal" ); - case Scheduler::NoMethod: - return i18n("Error: iMIP message with unknown method: '%1'") - .arg( msg->method() ); + } + } + } + case Scheduler::Refresh: + return i18n( "This task was refreshed" ); + case Scheduler::Cancel: + return i18n( "This task was canceled" ); + case Scheduler::Add: + return i18n( "Addition to the task" ); + case Scheduler::Reply: + { + if ( replyMeansCounter( todo ) ) { + return i18n( "%1 makes this counter proposal" ). + arg( firstAttendeeName( todo, i18n( "Sender" ) ) ); + } + + Attendee::List attendees = todo->attendees(); + if( attendees.count() == 0 ) { + kdDebug(5850) << "No attendees in the iCal reply!" << endl; + return TQString::null; + } + if( attendees.count() != 1 ) { + kdDebug(5850) << "Warning: attendeecount in the reply should be 1 " + << "but is " << attendees.count() << endl; + } + TQString attendeeName = firstAttendeeName( todo, i18n( "Sender" ) ); + + TQString delegatorName, dummy; + Attendee* attendee = *attendees.begin(); + KPIM::getNameAndMail( attendee->delegator(), delegatorName, dummy ); + if ( delegatorName.isEmpty() ) { + delegatorName = attendee->delegator(); + } + + switch( attendee->status() ) { + case Attendee::NeedsAction: + return i18n( "%1 indicates this task assignment still needs some action" ).arg( attendeeName ); + case Attendee::Accepted: + if ( todo->revision() > 0 ) { + if ( !sender.isEmpty() ) { + if ( todo->isCompleted() ) { + return i18n( "This task has been completed by assignee %1" ).arg( sender ); + } else { + return i18n( "This task has been updated by assignee %1" ).arg( sender ); + } + } else { + if ( todo->isCompleted() ) { + return i18n( "This task has been completed by an assignee" ); + } else { + return i18n( "This task has been updated by an assignee" ); + } + } + } else { + if ( delegatorName.isEmpty() ) { + return i18n( "%1 accepts this task" ).arg( attendeeName ); + } else { + return i18n( "%1 accepts this task on behalf of %2" ). + arg( attendeeName ).arg( delegatorName ); + } + } + case Attendee::Tentative: + if ( delegatorName.isEmpty() ) { + return i18n( "%1 tentatively accepts this task" ). + arg( attendeeName ); + } else { + return i18n( "%1 tentatively accepts this task on behalf of %2" ). + arg( attendeeName ).arg( delegatorName ); + } + case Attendee::Declined: + if ( delegatorName.isEmpty() ) { + return i18n( "%1 declines this task" ).arg( attendeeName ); + } else { + return i18n( "%1 declines this task on behalf of %2" ). + arg( attendeeName ).arg( delegatorName ); + } + case Attendee::Delegated: { + TQString delegate, dummy; + KPIM::getNameAndMail( attendee->delegate(), delegate, dummy ); + if ( delegate.isEmpty() ) { + delegate = attendee->delegate(); + } + if ( !delegate.isEmpty() ) { + return i18n( "%1 has delegated this request for the task to %2" ). + arg( attendeeName ).arg( delegate ); + } else { + return i18n( "%1 has delegated this request for the task" ). + arg( attendeeName ); + } + } + case Attendee::Completed: + return i18n( "The request for this task is now completed" ); + case Attendee::InProcess: + return i18n( "%1 is still processing the task" ). + arg( attendeeName ); + default: + return i18n( "Unknown response to this task" ); + } + break; + } + + case Scheduler::Counter: + return i18n( "%1 makes this counter proposal" ). + arg( firstAttendeeName( todo, i18n( "Sender" ) ) ); + + case Scheduler::Declinecounter: + return i18n( "%1 declines the counter proposal" ). + arg( firstAttendeeName( todo, i18n( "Sender" ) ) ); + + case Scheduler::NoMethod: + return i18n( "Error: iMIP message with unknown method: '%1'" ). + arg( msg->method() ); } return TQString::null; } static TQString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg ) { - // TODO: Several of the methods are not allowed for journals, so remove them. - if ( !msg || !journal ) + if ( !msg || !journal ) { return TQString::null; + } + switch ( msg->method() ) { - case Scheduler::Publish: - return i18n("This journal has been published"); - case Scheduler::Request: - return i18n( "You have been assigned this journal" ); - case Scheduler::Refresh: - return i18n( "This journal was refreshed" ); - case Scheduler::Cancel: - return i18n( "This journal was canceled" ); - case Scheduler::Add: - return i18n( "Addition to the journal" ); - case Scheduler::Reply: { - Attendee::List attendees = journal->attendees(); - if( attendees.count() == 0 ) { - kdDebug(5850) << "No attendees in the iCal reply!\n"; - return TQString::null; - } - if( attendees.count() != 1 ) - kdDebug(5850) << "Warning: attendeecount in the reply should be 1 " - << "but is " << attendees.count() << endl; - Attendee* attendee = *attendees.begin(); - - switch( attendee->status() ) { - case Attendee::NeedsAction: - return i18n( "Sender indicates this journal assignment still needs some action" ); - case Attendee::Accepted: - return i18n( "Sender accepts this journal" ); - case Attendee::Tentative: - return i18n( "Sender tentatively accepts this journal" ); - case Attendee::Declined: - return i18n( "Sender declines this journal" ); - case Attendee::Delegated: - return i18n( "Sender has delegated this request for the journal" ); - case Attendee::Completed: - return i18n( "The request for this journal is now completed" ); - case Attendee::InProcess: - return i18n( "Sender is still processing the invitation" ); - default: - return i18n( "Unknown response to this journal" ); - } - break; } - case Scheduler::Counter: - return i18n( "Sender makes this counter proposal" ); - case Scheduler::Declinecounter: - return i18n( "Sender declines the counter proposal" ); - case Scheduler::NoMethod: - return i18n("Error: iMIP message with unknown method: '%1'") - .arg( msg->method() ); + case Scheduler::Publish: + return i18n("This journal has been published"); + case Scheduler::Request: + return i18n( "You have been assigned this journal" ); + case Scheduler::Refresh: + return i18n( "This journal was refreshed" ); + case Scheduler::Cancel: + return i18n( "This journal was canceled" ); + case Scheduler::Add: + return i18n( "Addition to the journal" ); + case Scheduler::Reply: + { + if ( replyMeansCounter( journal ) ) { + return i18n( "Sender makes this counter proposal" ); + } + + Attendee::List attendees = journal->attendees(); + if( attendees.count() == 0 ) { + kdDebug(5850) << "No attendees in the iCal reply!" << endl; + return TQString::null; + } + if( attendees.count() != 1 ) { + kdDebug(5850) << "Warning: attendeecount in the reply should be 1 " + << "but is " << attendees.count() << endl; + } + Attendee* attendee = *attendees.begin(); + + switch( attendee->status() ) { + case Attendee::NeedsAction: + return i18n( "Sender indicates this journal assignment still needs some action" ); + case Attendee::Accepted: + return i18n( "Sender accepts this journal" ); + case Attendee::Tentative: + return i18n( "Sender tentatively accepts this journal" ); + case Attendee::Declined: + return i18n( "Sender declines this journal" ); + case Attendee::Delegated: + return i18n( "Sender has delegated this request for the journal" ); + case Attendee::Completed: + return i18n( "The request for this journal is now completed" ); + case Attendee::InProcess: + return i18n( "Sender is still processing the invitation" ); + default: + return i18n( "Unknown response to this journal" ); + } + break; + } + case Scheduler::Counter: + return i18n( "Sender makes this counter proposal" ); + case Scheduler::Declinecounter: + return i18n( "Sender declines the counter proposal" ); + case Scheduler::NoMethod: + return i18n("Error: iMIP message with unknown method: '%1'"). + arg( msg->method() ); } return TQString::null; } static TQString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg ) { - if ( !msg || !fb ) + if ( !msg || !fb ) { return TQString::null; + } + switch ( msg->method() ) { - case Scheduler::Publish: - return i18n("This free/busy list has been published"); - case Scheduler::Request: - return i18n( "The free/busy list has been requested" ); - case Scheduler::Refresh: - return i18n( "This free/busy list was refreshed" ); - case Scheduler::Cancel: - return i18n( "This free/busy list was canceled" ); - case Scheduler::Add: - return i18n( "Addition to the free/busy list" ); - case Scheduler::NoMethod: - default: - return i18n("Error: Free/Busy iMIP message with unknown method: '%1'") - .arg( msg->method() ); + case Scheduler::Publish: + return i18n("This free/busy list has been published"); + case Scheduler::Request: + return i18n( "The free/busy list has been requested" ); + case Scheduler::Refresh: + return i18n( "This free/busy list was refreshed" ); + case Scheduler::Cancel: + return i18n( "This free/busy list was canceled" ); + case Scheduler::Add: + return i18n( "Addition to the free/busy list" ); + case Scheduler::NoMethod: + default: + return i18n("Error: Free/Busy iMIP message with unknown method: '%1'"). + arg( msg->method() ); + } +} + +static TQString invitationAttendees( Incidence *incidence ) +{ + TQString tmpStr; + if ( !incidence ) { + return tmpStr; + } + + if ( incidence->type() == "Todo" ) { + tmpStr += htmlAddTag( "u", i18n( "Assignees" ) ); + } else { + tmpStr += htmlAddTag( "u", i18n( "Attendees" ) ); + } + tmpStr += "<br/>"; + + int count=0; + Attendee::List attendees = incidence->attendees(); + if ( !attendees.isEmpty() ) { + + Attendee::List::ConstIterator it; + for( it = attendees.begin(); it != attendees.end(); ++it ) { + Attendee *a = *it; + if ( !iamAttendee( a ) ) { + count++; + if ( count == 1 ) { + tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\" columns=\"2\">"; + } + tmpStr += "<tr>"; + tmpStr += "<td>"; + tmpStr += invitationPerson( a->email(), a->name(), TQString::null ); + if ( !a->delegator().isEmpty() ) { + tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() ); + } + if ( !a->delegate().isEmpty() ) { + tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() ); + } + tmpStr += "</td>"; + tmpStr += "<td>" + a->statusStr() + "</td>"; + tmpStr += "</tr>"; + } + } + } + if ( count ) { + tmpStr += "</table>"; + } else { + tmpStr += "<i>" + i18n( "No attendee", "None" ) + "</i>"; + } + + return tmpStr; +} + +static TQString invitationAttachments( InvitationFormatterHelper *helper, Incidence *incidence ) +{ + TQString tmpStr; + if ( !incidence ) { + return tmpStr; + } + + Attachment::List attachments = incidence->attachments(); + if ( !attachments.isEmpty() ) { + tmpStr += i18n( "Attached Documents:" ) + "<ol>"; + + Attachment::List::ConstIterator it; + for( it = attachments.begin(); it != attachments.end(); ++it ) { + Attachment *a = *it; + tmpStr += "<li>"; + // Attachment icon + KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() ); + const TQString iconStr = mimeType ? mimeType->icon( a->uri(), false ) : TQString( "application-octet-stream" ); + const TQString iconPath = KGlobal::iconLoader()->iconPath( iconStr, KIcon::Small ); + if ( !iconPath.isEmpty() ) { + tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">"; + } + tmpStr += helper->makeLink( "ATTACH:" + a->label(), a->label() ); + tmpStr += "</li>"; + } + tmpStr += "</ol>"; } + + return tmpStr; } -class IncidenceFormatter::ScheduleMessageVisitor : public IncidenceBase::Visitor +class IncidenceFormatter::ScheduleMessageVisitor + : public IncidenceBase::Visitor { public: - ScheduleMessageVisitor() : mMessage(0) { mResult = ""; } - bool act( IncidenceBase *incidence, ScheduleMessage *msg ) { mMessage = msg; return incidence->accept( *this ); } + ScheduleMessageVisitor() : mExistingIncidence( 0 ), mMessage( 0 ) { mResult = ""; } + bool act( IncidenceBase *incidence, Incidence *existingIncidence, ScheduleMessage *msg, + const TQString &sender ) + { + mExistingIncidence = existingIncidence; + mMessage = msg; + mSender = sender; + return incidence->accept( *this ); + } TQString result() const { return mResult; } protected: TQString mResult; + Incidence *mExistingIncidence; ScheduleMessage *mMessage; + TQString mSender; }; -class IncidenceFormatter::InvitationHeaderVisitor : - public IncidenceFormatter::ScheduleMessageVisitor +class IncidenceFormatter::InvitationHeaderVisitor + : public IncidenceFormatter::ScheduleMessageVisitor { protected: bool visit( Event *event ) { - mResult = invitationHeaderEvent( event, mMessage ); + mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { - mResult = invitationHeaderTodo( todo, mMessage ); + mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender ); return !mResult.isEmpty(); } bool visit( Journal *journal ) @@ -1135,47 +2066,59 @@ class IncidenceFormatter::InvitationHeaderVisitor : } }; -class IncidenceFormatter::InvitationBodyVisitor : - public IncidenceFormatter::ScheduleMessageVisitor +class IncidenceFormatter::InvitationBodyVisitor + : public IncidenceFormatter::ScheduleMessageVisitor { + public: + InvitationBodyVisitor( bool noHtmlMode ) + : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ) {} + protected: bool visit( Event *event ) { - mResult = invitationDetailsEvent( event ); + mResult = invitationDetailsEvent( event, mNoHtmlMode ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { - mResult = invitationDetailsTodo( todo ); + mResult = invitationDetailsTodo( todo, mNoHtmlMode ); return !mResult.isEmpty(); } bool visit( Journal *journal ) { - mResult = invitationDetailsJournal( journal ); + mResult = invitationDetailsJournal( journal, mNoHtmlMode ); return !mResult.isEmpty(); } bool visit( FreeBusy *fb ) { - mResult = invitationDetailsFreeBusy( fb ); + mResult = invitationDetailsFreeBusy( fb, mNoHtmlMode ); return !mResult.isEmpty(); } + + private: + bool mNoHtmlMode; }; -class IncidenceFormatter::IncidenceCompareVisitor : - public IncidenceBase::Visitor +class IncidenceFormatter::IncidenceCompareVisitor + : public IncidenceBase::Visitor { public: IncidenceCompareVisitor() : mExistingIncidence(0) {} - bool act( IncidenceBase *incidence, Incidence* existingIncidence ) + bool act( IncidenceBase *incidence, Incidence *existingIncidence, int method ) { + Incidence *inc = dynamic_cast<Incidence*>( incidence ); + if ( !inc || !existingIncidence || inc->revision() <= existingIncidence->revision() ) + return false; mExistingIncidence = existingIncidence; + mMethod = method; return incidence->accept( *this ); } TQString result() const { - if ( mChanges.isEmpty() ) - return TQString(); + if ( mChanges.isEmpty() ) { + return TQString::null; + } TQString html = "<div align=\"left\"><ul><li>"; html += mChanges.join( "</li><li>" ); html += "</li><ul></div>"; @@ -1186,17 +2129,18 @@ class IncidenceFormatter::IncidenceCompareVisitor : bool visit( Event *event ) { compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) ); - compareIncidences( event, mExistingIncidence ); + compareIncidences( event, mExistingIncidence, mMethod ); return !mChanges.isEmpty(); } bool visit( Todo *todo ) { - compareIncidences( todo, mExistingIncidence ); + compareTodos( todo, dynamic_cast<Todo*>( mExistingIncidence ) ); + compareIncidences( todo, mExistingIncidence, mMethod ); return !mChanges.isEmpty(); } bool visit( Journal *journal ) { - compareIncidences( journal, mExistingIncidence ); + compareIncidences( journal, mExistingIncidence, mMethod ); return !mChanges.isEmpty(); } bool visit( FreeBusy *fb ) @@ -1211,60 +2155,60 @@ class IncidenceFormatter::IncidenceCompareVisitor : if ( !oldEvent || !newEvent ) return; if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->doesFloat() != newEvent->doesFloat() ) - mChanges += i18n( "The begin of the meeting has been changed from %1 to %2" ) - .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) ); + mChanges += i18n( "The invitation starting time has been changed from %1 to %2" ) + .arg( eventStartTimeStr( oldEvent ) ).arg( eventStartTimeStr( newEvent ) ); if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->doesFloat() != newEvent->doesFloat() ) - mChanges += i18n( "The end of the meeting has been changed from %1 to %2" ) - .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) ); - if ( newEvent->doesRecur() ) { - TQString recurrence[]= {i18n("no recurrence", "None"), - i18n("Minutely"), i18n("Hourly"), i18n("Daily"), - i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"), - i18n("Yearly"), i18n("Yearly"), i18n("Yearly")}; - - Recurrence *recur = newEvent->recurrence(); - if (oldEvent->doesRecur() == false) { - mChanges += i18n( "The meeting now recurs %1" ).arg( recurrence[ recur->recurrenceType() ] ); - DateList exceptions = recur->exDates(); - if (exceptions.isEmpty() == false) { - mChanges += i18n("This recurring meeting has been cancelled on the following days:<br>"); - DateList::ConstIterator ex_iter; - for ( ex_iter = exceptions.begin(); ex_iter != exceptions.end(); ++ex_iter ) { - mChanges += i18n("  %1<br>").arg( KGlobal::locale()->formatDate(* ex_iter ) ); - } - } - } - else { - Recurrence *oldRecur = oldEvent->recurrence(); - DateList exceptions = recur->exDates(); - DateList oldExceptions = oldRecur->exDates(); - bool existsInOldEvent; - bool atLeastOneModified; - if (exceptions.isEmpty() == false) { - atLeastOneModified = false; - DateList::ConstIterator ex_iter; - DateList::ConstIterator ex_iter_old; - for ( ex_iter = exceptions.begin(); ex_iter != exceptions.end(); ++ex_iter ) { - existsInOldEvent = false; - for ( ex_iter_old = oldExceptions.begin(); ex_iter_old != oldExceptions.end(); ++ex_iter_old ) { - if ( KGlobal::locale()->formatDate(* ex_iter ) == KGlobal::locale()->formatDate(* ex_iter_old ) ) { - existsInOldEvent = true; - if (atLeastOneModified == false) { - mChanges += i18n("This recurring meeting has been cancelled on the following days:<br>"); - } - atLeastOneModified = true; - } - } - if (existsInOldEvent == false ) { - mChanges += i18n("  %1<br>").arg( KGlobal::locale()->formatDate(* ex_iter ) ); - } - } - } - } + mChanges += i18n( "The invitation ending time has been changed from %1 to %2" ) + .arg( eventEndTimeStr( oldEvent ) ).arg( eventEndTimeStr( newEvent ) ); + } + + void compareTodos( Todo *newTodo, Todo *oldTodo ) + { + if ( !oldTodo || !newTodo ) { + return; + } + + if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) { + mChanges += i18n( "The task has been completed" ); + } + if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) { + mChanges += i18n( "The task is no longer completed" ); + } + if ( oldTodo->percentComplete() != newTodo->percentComplete() ) { + const TQString oldPer = i18n( "%1%" ).arg( oldTodo->percentComplete() ); + const TQString newPer = i18n( "%1%" ).arg( newTodo->percentComplete() ); + mChanges += i18n( "The task completed percentage has changed from %1 to %2" ). + arg( oldPer, newPer ); + } + + if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) { + mChanges += i18n( "A task starting time has been added" ); + } + if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) { + mChanges += i18n( "The task starting time has been removed" ); + } + if ( oldTodo->hasStartDate() && newTodo->hasStartDate() && + oldTodo->dtStart() != newTodo->dtStart() ) { + mChanges += i18n( "The task starting time has been changed from %1 to %2" ). + arg( dateTimeToString( oldTodo->dtStart(), oldTodo->doesFloat(), false ), + dateTimeToString( newTodo->dtStart(), newTodo->doesFloat(), false ) ); + } + + if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) { + mChanges += i18n( "A task due time has been added" ); + } + if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) { + mChanges += i18n( "The task due time has been removed" ); + } + if ( oldTodo->hasDueDate() && newTodo->hasDueDate() && + oldTodo->dtDue() != newTodo->dtDue() ) { + mChanges += i18n( "The task due time has been changed from %1 to %2" ). + arg( dateTimeToString( oldTodo->dtDue(), oldTodo->doesFloat(), false ), + dateTimeToString( newTodo->dtDue(), newTodo->doesFloat(), false ) ); } } - void compareIncidences( Incidence *newInc, Incidence *oldInc ) + void compareIncidences( Incidence *newInc, Incidence *oldInc, int method ) { if ( !oldInc || !newInc ) return; @@ -1276,56 +2220,176 @@ class IncidenceFormatter::IncidenceCompareVisitor : mChanges += i18n( "The description has been changed to: \"%1\"" ).arg( newInc->description() ); Attendee::List oldAttendees = oldInc->attendees(); Attendee::List newAttendees = newInc->attendees(); - for ( Attendee::List::ConstIterator it = newAttendees.constBegin(); it != newAttendees.constEnd(); ++it ) { + for ( Attendee::List::ConstIterator it = newAttendees.constBegin(); + it != newAttendees.constEnd(); ++it ) { Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() ); if ( !oldAtt ) { mChanges += i18n( "Attendee %1 has been added" ).arg( (*it)->fullName() ); } else { if ( oldAtt->status() != (*it)->status() ) - mChanges += i18n( "The status of attendee %1 has been changed to: %2" ).arg( (*it)->fullName() ) - .arg( (*it)->statusStr() ); + mChanges += i18n( "The status of attendee %1 has been changed to: %2" ). + arg( (*it)->fullName() ).arg( (*it)->statusStr() ); } } - for ( Attendee::List::ConstIterator it = oldAttendees.constBegin(); it != oldAttendees.constEnd(); ++it ) { - Attendee *newAtt = newInc->attendeeByMail( (*it)->email() ); - if ( !newAtt ) - mChanges += i18n( "Attendee %1 has been removed" ).arg( (*it)->fullName() ); + if ( method == Scheduler::Request ) { + 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" ).arg( (*it)->fullName() ); + } + } + } } } private: - Incidence* mExistingIncidence; + Incidence *mExistingIncidence; + int mMethod; TQStringList mChanges; }; TQString InvitationFormatterHelper::makeLink( const TQString &id, const TQString &text ) { - TQString res( "<a href=\"%1\"><b>%2</b></a>" ); - return res.arg( generateLinkURL( id ) ).arg( text ); - return res; + if ( !id.startsWith( "ATTACH:" ) ) { + TQString res = TQString( "<a href=\"%1\"><b>%2</b></a>" ). + arg( generateLinkURL( id ), text ); + return res; + } else { + // draw the attachment links in non-bold face + TQString res = TQString( "<a href=\"%1\">%2</a>" ). + 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 ) +static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence ) { - CalendarResources* cal = dynamic_cast<CalendarResources*>( calendar ); - if ( !cal || !incidence ) + CalendarResources *cal = dynamic_cast<CalendarResources*>( calendar ); + if ( !cal || !incidence ) { return true; - ResourceCalendar* res = cal->resource( incidence ); - if ( !res ) + } + ResourceCalendar *res = cal->resource( incidence ); + if ( !res ) { return true; + } const TQString subRes = res->subresourceIdentifier( incidence ); - if ( !subRes.contains( "/.INBOX.directory/" ) ) + if ( !subRes.contains( "/.INBOX.directory/" ) ) { return false; + } return true; } -TQString IncidenceFormatter::formatICalInvitation( TQString invitation, Calendar *mCalendar, - InvitationFormatterHelper *helper ) +// The spacer for the invitation buttons +static TQString spacer = "<td> </td>"; +// The open & close table cell tags for the invitation buttons +static TQString tdOpen = "<td>"; +static TQString tdClose = "</td>" + spacer; + +static TQString responseButtons( Incidence *inc, bool rsvpReq, bool rsvpRec, + InvitationFormatterHelper *helper ) +{ + TQString 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", i18n( "[Accept]" ) ); + html += tdClose; + + // Tentative + html += tdOpen; + html += helper->makeLink( "accept_conditionally", + i18n( "Accept conditionally", "[Accept cond.]" ) ); + html += tdClose; + + // Counter proposal + html += tdOpen; + html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) ); + html += tdClose; + + // Decline + html += tdOpen; + html += helper->makeLink( "decline", i18n( "[Decline]" ) ); + html += tdClose; + } + + if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) { + // Delegate + html += tdOpen; + html += helper->makeLink( "delegate", i18n( "[Delegate]" ) ); + html += tdClose; + + // Forward + html += tdOpen; + html += helper->makeLink( "forward", i18n( "[Forward]" ) ); + html += tdClose; + + // Check calendar + if ( inc && inc->type() == "Event" ) { + html += tdOpen; + html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) ); + html += tdClose; + } + } + return html; +} + +static TQString counterButtons( Incidence *incidence, + InvitationFormatterHelper *helper ) +{ + TQString 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; +} + +TQString IncidenceFormatter::formatICalInvitationHelper( TQString invitation, + Calendar *mCalendar, + InvitationFormatterHelper *helper, + bool noHtmlMode, + const TQString &sender ) { - if ( invitation.isEmpty() ) return TQString::null; + if ( invitation.isEmpty() ) { + return TQString::null; + } ICalFormat format; // parseScheduleMessage takes the tz from the calendar, no need to set it manually here for the format! @@ -1340,15 +2404,18 @@ TQString IncidenceFormatter::formatICalInvitation( TQString invitation, Calendar IncidenceBase *incBase = msg->event(); - Incidence* existingIncidence = 0; - if ( helper->calendar() ) { + // 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 ) ) + 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 ) ) { + if ( (*it)->schedulingID() == incBase->uid() && + incidenceOwnedByMe( helper->calendar(), *it ) ) { existingIncidence = *it; break; } @@ -1369,115 +2436,262 @@ TQString IncidenceFormatter::formatICalInvitation( TQString invitation, Calendar html += tableHead; InvitationHeaderVisitor headerVisitor; // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled - if ( !headerVisitor.act( incBase, msg ) ) + if ( !headerVisitor.act( incBase, existingIncidence, msg, sender ) ) return TQString::null; html += "<b>" + headerVisitor.result() + "</b>"; - InvitationBodyVisitor bodyVisitor; - if ( !bodyVisitor.act( incBase, msg ) ) + InvitationBodyVisitor bodyVisitor( noHtmlMode ); + if ( !bodyVisitor.act( incBase, existingIncidence, msg, sender ) ) return TQString::null; html += bodyVisitor.result(); - if ( msg->method() == Scheduler::Request ) { // ### Scheduler::Publish/Refresh/Add as well? + if ( msg->method() == Scheduler::Request ) { + IncidenceCompareVisitor compareVisitor; + if ( compareVisitor.act( incBase, existingIncidence, msg->method() ) ) { + html += "<p align=\"left\">"; + html += i18n( "The following changes have been made by the organizer:" ); + html += "</p>"; + html += compareVisitor.result(); + } + } + if ( msg->method() == Scheduler::Reply ) { IncidenceCompareVisitor compareVisitor; - if ( compareVisitor.act( incBase, existingIncidence ) ) { - html += i18n("<p align=\"left\">The following changes have been made by the organizer:</p>"); + if ( compareVisitor.act( incBase, existingIncidence, msg->method() ) ) { + html += "<p align=\"left\">"; + if ( !sender.isEmpty() ) { + html += i18n( "The following changes have been made by %1:" ).arg( sender ); + } else { + html += i18n( "The following changes have been made by an attendee:" ); + } + html += "</p>"; html += compareVisitor.result(); } } - html += "<br/>"; - html += "<table border=\"0\" cellspacing=\"0\"><tr><td> </td></tr><tr>"; + Incidence *inc = dynamic_cast<Incidence*>( incBase ); + + // determine if I am the organizer for this invitation + bool myInc = iamOrganizer( inc ); -#if 0 - html += helper->makeLinkURL( "accept", i18n("[Enter this into my calendar]") ); - html += "</td><td> </td><td>"; -#endif + // 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 + TQString 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() ); + } + + // determine if RSVP needed, not-needed, or response already recorded + bool rsvpReq = rsvpRequested( inc ); + if ( !myInc && a ) { + html += "<br/>"; + html += "<i><u>"; + if ( rsvpRec && inc ) { + if ( inc->revision() == 0 ) { + html += i18n( "Your <b>%1</b> response has already been recorded" ). + arg( ea->statusStr() ); + } else { + html += i18n( "Your status for this invitation is <b>%1</b>" ). + arg( ea->statusStr() ); + } + rsvpReq = false; + } else if ( msg->method() == Scheduler::Cancel ) { + html += i18n( "This invitation was declined" ); + } else if ( msg->method() == Scheduler::Add ) { + html += i18n( "This invitation was accepted" ); + } else { + if ( !isDelegated ) { + html += rsvpRequestedStr( rsvpReq, role ); + } else { + html += i18n( "Awaiting delegation response" ); + } + } + html += "</u></i>"; + } + + // Print if the organizer gave you a preset status + if ( !myInc ) { + if ( inc && inc->revision() == 0 ) { + TQString statStr = myStatusStr( inc ); + if ( !statStr.isEmpty() ) { + html += "<br/>"; + html += "<i>"; + html += statStr; + html += "</i>"; + } + } + } // Add groupware links + html += "<br><table border=\"0\" cellspacing=\"0\"><tr><td> </td></tr>"; + switch ( msg->method() ) { case Scheduler::Publish: case Scheduler::Request: case Scheduler::Refresh: case Scheduler::Add: { - Incidence *inc = dynamic_cast<Incidence*>( incBase ); - if ( inc && inc->revision() > 0 && (existingIncidence || !helper->calendar()) ) { - if ( incBase->type() == "Todo" ) { - html += "<td colspan=\"9\">"; - html += helper->makeLink( "reply", i18n( "[Enter this into my task list]" ) ); - } else { - html += "<td colspan=\"13\">"; - html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) ); - } - html += "</td></tr><tr>"; - } - html += "<td>"; - - if ( !existingIncidence ) { - // Accept - html += helper->makeLink( "accept", i18n( "[Accept]" ) ); - html += "</td><td> </td><td>"; - html += helper->makeLink( "accept_conditionally", - i18n( "Accept conditionally", "[Accept cond.]" ) ); - html += "</td><td> </td><td>"; - // counter proposal - html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) ); - html += "</td><td> </td><td>"; - // Decline - html += helper->makeLink( "decline", i18n( "[Decline]" ) ); - html += "</td><td> </td><td>"; - - // Delegate - html += helper->makeLink( "delegate", i18n( "[Delegate]" ) ); - html += "</td><td> </td><td>"; - - // Forward - html += helper->makeLink( "forward", i18n( "[Forward]" ) ); - - if ( incBase->type() == "Event" ) { - html += "</b></a></td><td> </td><td>"; - html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) ); - } + if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) { + html += "<tr>"; + if ( inc->type() == "Todo" ) { + html += "<td colspan=\"9\">"; + html += helper->makeLink( "reply", i18n( "[Record invitation in my task list]" ) ); + } else { + html += "<td colspan=\"13\">"; + html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) ); } - break; + html += "</td></tr>"; + } + + if ( !myInc && a ) { + html += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>"; + } + break; } case Scheduler::Cancel: - // Cancel event from my calendar - html += helper->makeLink( "cancel", i18n( "[Remove this from my calendar]" ) ); - break; + // Remove invitation + if ( inc ) { + html += "<tr>"; + if ( inc->type() == "Todo" ) { + html += "<td colspan=\"9\">"; + html += helper->makeLink( "cancel", i18n( "[Remove invitation from my task list]" ) ); + } else { + html += "<td colspan=\"13\">"; + html += helper->makeLink( "cancel", i18n( "[Remove invitation from my calendar]" ) ); + } + html += "</td></tr>"; + } + break; case Scheduler::Reply: - // Enter this into my calendar - if ( incBase->type() == "Todo" ) { - html += helper->makeLink( "reply", i18n( "[Enter this into my task list]" ) ); - } else { - html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) ); + { + // 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 += "<tr>" + counterButtons( inc, helper ) + "</tr>"; + break; } - 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 += "<tr>" + responseButtons( inc, rsvpReq, rsvpRec, helper ) + "</tr>"; + break; + } + } + + // Finally, simply allow a Record of the reply + if ( !inc->attendees().isEmpty() ) { + a = inc->attendees().first(); + } + if ( a ) { + ea = findAttendee( existingIncidence, a->email() ); + } + } + if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) { + if ( inc && inc->revision() > 0 ) { + html += "<br><u><i>"; + html += i18n( "The response has been recorded [%1]" ).arg( ea->statusStr() ); + html += "</i></u>"; + } + } else { + if ( inc ) { + html += "<tr><td>"; + if ( inc->type() == "Todo" ) { + html += helper->makeLink( "reply", i18n( "[Record response in my task list]" ) ); + } else { + html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) ); + } + html += "</td></tr>"; + } + } + break; + } case Scheduler::Counter: - html += helper->makeLink( "accept_counter", i18n("[Accept]") ); - html += " "; - html += helper->makeLink( "decline_counter", i18n("[Decline]") ); - html += " "; - html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) ); - break; + // Counter proposal + html += "<tr>" + counterButtons( inc, helper ) + "</tr>"; + break; + case Scheduler::Declinecounter: case Scheduler::NoMethod: - break; + break; } + // close the groupware table html += "</td></tr></table>"; + // Add the attendee list if I am the organizer + if ( myInc && helper->calendar() ) { + html += invitationAttendees( helper->calendar()->incidence( inc->uid() ) ); + } + + // close the top-level table html += "</td></tr></table><br></div>"; + // Add the attachment list + html += invitationAttachments( helper, inc ); + return html; } +TQString IncidenceFormatter::formatICalInvitation( TQString invitation, + Calendar *mCalendar, + InvitationFormatterHelper *helper ) +{ + return formatICalInvitationHelper( invitation, mCalendar, helper, false, TQString() ); +} + +TQString IncidenceFormatter::formatICalInvitationNoHtml( TQString invitation, + Calendar *mCalendar, + InvitationFormatterHelper *helper ) +{ + return formatICalInvitationHelper( invitation, mCalendar, helper, true, TQString() ); +} +TQString IncidenceFormatter::formatICalInvitationNoHtml( TQString invitation, + Calendar *mCalendar, + InvitationFormatterHelper *helper, + const TQString &sender ) +{ + return formatICalInvitationHelper( invitation, mCalendar, helper, true, sender ); +} /******************************************************************* @@ -1944,10 +3158,14 @@ TQString IncidenceFormatter::formatTNEFInvitation( const TQByteArray& tnef, class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor { public: - ToolTipVisitor() : mRichText( true ), mResult( "" ) {} + ToolTipVisitor() + : mCalendar( 0 ), mRichText( true ), mResult( "" ) {} - bool act( IncidenceBase *incidence, bool richText=true) + bool act( Calendar *calendar, IncidenceBase *incidence, + const TQDate &date=TQDate(), bool richText=true ) { + mCalendar = calendar; + mDate = date; mRichText = richText; mResult = ""; return incidence ? incidence->accept( *this ) : false; @@ -1960,43 +3178,65 @@ class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor bool visit( Journal *journal ); bool visit( FreeBusy *fb ); - TQString dateRangeText( Event*event ); - TQString dateRangeText( Todo *todo ); + TQString dateRangeText( Event *event, const TQDate &date ); + TQString dateRangeText( Todo *todo, const TQDate &date ); TQString dateRangeText( Journal *journal ); TQString dateRangeText( FreeBusy *fb ); TQString generateToolTip( Incidence* incidence, TQString dtRangeText ); protected: + Calendar *mCalendar; + TQDate mDate; bool mRichText; TQString mResult; }; -TQString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event*event ) +TQString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const TQDate &date ) { TQString ret; TQString tmp; + + TQDateTime startDt = event->dtStart(); + TQDateTime endDt = event->dtEnd(); + if ( event->doesRecur() ) { + if ( date.isValid() ) { + TQDateTime dt( date, TQTime( 0, 0, 0 ) ); + int diffDays = startDt.daysTo( dt ); + dt = dt.addSecs( -1 ); + startDt.setDate( event->recurrence()->getNextDateTime( dt ).date() ); + if ( event->hasEndDate() ) { + endDt = endDt.addDays( diffDays ); + if ( startDt > endDt ) { + startDt.setDate( event->recurrence()->getPreviousDateTime( dt ).date() ); + endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) ); + } + } + } + } if ( event->isMultiDay() ) { tmp = "<br>" + i18n("Event start", "<i>From:</i> %1"); if (event->doesFloat()) - ret += tmp.arg( event->dtStartDateStr().replace(" ", " ") ); + ret += tmp.arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", " ") ); else - ret += tmp.arg( event->dtStartStr().replace(" ", " ") ); + ret += tmp.arg( IncidenceFormatter::dateToString( startDt ).replace(" ", " ") ); tmp = "<br>" + i18n("Event end","<i>To:</i> %1"); if (event->doesFloat()) - ret += tmp.arg( event->dtEndDateStr().replace(" ", " ") ); + ret += tmp.arg( IncidenceFormatter::dateToString( endDt, false ).replace(" ", " ") ); else - ret += tmp.arg( event->dtEndStr().replace(" ", " ") ); + ret += tmp.arg( IncidenceFormatter::dateToString( endDt ).replace(" ", " ") ); } else { ret += "<br>"+i18n("<i>Date:</i> %1"). - arg( event->dtStartDateStr().replace(" ", " ") ); + arg( IncidenceFormatter::dateToString( startDt, false ).replace(" ", " ") ); if ( !event->doesFloat() ) { - const TQString dtStartTime = event->dtStartTimeStr().replace( " ", " " ); - const TQString dtEndTime = event->dtEndTimeStr().replace( " ", " " ); + const TQString dtStartTime = + IncidenceFormatter::timeToString( startDt, true ).replace( " ", " " ); + const TQString dtEndTime = + IncidenceFormatter::timeToString( endDt, true ).replace( " ", " " ); if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00' tmp = "<br>" + i18n("time for event, to prevent ugly line breaks", "<i>Time:</i> %1"). @@ -2013,27 +3253,55 @@ TQString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event*event ) return ret; } -TQString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo*todo ) +TQString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const TQDate &date ) { TQString ret; bool floats( todo->doesFloat() ); - if (todo->hasStartDate()) - // No need to add <i> here. This is separated issue and each line - // is very visible on its own. On the other hand... Yes, I like it - // italics here :) - ret += "<br>" + i18n("<i>Start:</i> %1").arg( - (floats) - ?(todo->dtStartDateStr().replace(" ", " ")) - :(todo->dtStartStr().replace(" ", " ")) ) ; - if (todo->hasDueDate()) - ret += "<br>" + i18n("<i>Due:</i> %1").arg( - (floats) - ?(todo->dtDueDateStr().replace(" ", " ")) - :(todo->dtDueStr().replace(" ", " ")) ); - if (todo->isCompleted()) - ret += "<br>" + i18n("<i>Completed:</i> %1").arg( todo->completedStr().replace(" ", " ") ); - else - ret += "<br>" + i18n("%1 % completed").arg(todo->percentComplete()); + + if ( todo->hasStartDate() && todo->dtStart().isValid() ) { + TQDateTime startDt = todo->dtStart(); + if ( todo->doesRecur() ) { + if ( date.isValid() ) { + startDt.setDate( date ); + } + } + ret += "<br>" + + i18n("<i>Start:</i> %1"). + arg( IncidenceFormatter::dateTimeToString( startDt, floats, false ). + replace( " ", " " ) ); + } + + if ( todo->hasDueDate() && todo->dtDue().isValid() ) { + TQDateTime dueDt = todo->dtDue(); + if ( todo->doesRecur() ) { + if ( date.isValid() ) { + TQDateTime dt( date, TQTime( 0, 0, 0 ) ); + dt = dt.addSecs( -1 ); + dueDt.setDate( todo->recurrence()->getNextDateTime( dt ).date() ); + } + } + ret += "<br>" + + i18n("<i>Due:</i> %1"). + arg( IncidenceFormatter::dateTimeToString( dueDt, floats, false ). + replace( " ", " " ) ); + } + + // Print priority and completed info here, for lack of a better place + + if ( todo->priority() > 0 ) { + ret += "<br>"; + ret += "<i>" + i18n( "Priority:" ) + "</i>" + " "; + ret += TQString::number( todo->priority() ); + } + + ret += "<br>"; + if ( todo->isCompleted() ) { + ret += "<i>" + i18n( "Completed:" ) + "</i>" + " "; + ret += todo->completedStr().replace( " ", " " ); + } else { + ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + " "; + ret += i18n( "%1%" ).arg( todo->percentComplete() ); + } return ret; } @@ -2042,7 +3310,9 @@ TQString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal ) { TQString ret; if (journal->dtStart().isValid() ) { - ret += "<br>" + i18n("<i>Date:</i> %1").arg( journal->dtStartDateStr( false ) ); + ret += "<br>" + + i18n("<i>Date:</i> %1"). + arg( IncidenceFormatter::dateToString( journal->dtStart(), false ) ); } return ret; } @@ -2060,13 +3330,13 @@ TQString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb ) bool IncidenceFormatter::ToolTipVisitor::visit( Event *event ) { - mResult = generateToolTip( event, dateRangeText( event ) ); + mResult = generateToolTip( event, dateRangeText( event, mDate ) ); return !mResult.isEmpty(); } bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo ) { - mResult = generateToolTip( todo, dateRangeText( todo ) ); + mResult = generateToolTip( todo, dateRangeText( todo, mDate ) ); return !mResult.isEmpty(); } @@ -2085,43 +3355,209 @@ bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb ) return !mResult.isEmpty(); } +static TQString tooltipPerson( const TQString& email, TQString name ) +{ + // Make the search, if there is an email address to search on, + // and name is missing + if ( name.isEmpty() && !email.isEmpty() ) { + 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(); + } + } + } + + // Show the attendee + TQString tmpString = ( name.isEmpty() ? email : name ); + + return tmpString; +} + +static TQString etc = i18n( "elipsis", "..." ); +static TQString tooltipFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role ) +{ + int maxNumAtts = 8; // maximum number of people to print per attendee role + TQString sep = i18n( "separator for lists of people names", ", " ); + int sepLen = sep.length(); + + int i = 0; + TQString tmpStr; + Attendee::List::ConstIterator it; + Attendee::List attendees = incidence->attendees(); + + for( it = attendees.begin(); it != attendees.end(); ++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 ) { + tmpStr += etc; + break; + } + tmpStr += tooltipPerson( a->email(), a->name() ); + if ( !a->delegator().isEmpty() ) { + tmpStr += i18n(" (delegated by %1)" ).arg( a->delegator() ); + } + if ( !a->delegate().isEmpty() ) { + tmpStr += i18n(" (delegated to %1)" ).arg( a->delegate() ); + } + tmpStr += sep; + i++; + } + if ( tmpStr.endsWith( sep ) ) { + tmpStr.truncate( tmpStr.length() - sepLen ); + } + return tmpStr; +} + +static TQString tooltipFormatAttendees( Incidence *incidence ) +{ + TQString tmpStr, str; + + // Add organizer link + int attendeeCount = incidence->attendees().count(); + if ( attendeeCount > 1 || + ( attendeeCount == 1 && + incidence->organizer().email() != incidence->attendees().first()->email() ) ) { + tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + " "; + tmpStr += tooltipPerson( incidence->organizer().email(), + incidence->organizer().name() ); + } + + // Add "chair" + str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair ); + if ( !str.isEmpty() ) { + tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + " "; + tmpStr += str; + } + + // Add required participants + str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant ); + if ( !str.isEmpty() ) { + tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + " "; + tmpStr += str; + } + + // Add optional participants + str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant ); + if ( !str.isEmpty() ) { + tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + " "; + tmpStr += str; + } + + // Add observers + str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant ); + if ( !str.isEmpty() ) { + tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + " "; + tmpStr += str; + } + + return tmpStr; +} + TQString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence* incidence, TQString dtRangeText ) { - if ( !incidence ) + uint maxDescLen = 120; // maximum description chars to print (before elipsis) + + if ( !incidence ) { return TQString::null; + } - TQString tmp = "<qt><b>"+ incidence->summary().replace("\n", "<br>")+"</b>"; + TQString tmp = "<qt>"; + + // header + tmp += "<b>" + incidence->summary().replace( "\n", "<br>" ) + "</b>"; + //NOTE: using <hr> seems to confuse TQt3 tooltips in some cases so use "-----" + tmp += "<br>----------<br>"; + + if ( mCalendar ) { + TQString calStr = IncidenceFormatter::resourceString( mCalendar, incidence ); + if ( !calStr.isEmpty() ) { + tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + " "; + tmp += calStr; + } + } tmp += dtRangeText; - if (!incidence->location().isEmpty()) { - // Put Location: in italics - tmp += "<br>"+i18n("<i>Location:</i> %1"). - arg( incidence->location().replace("\n", "<br>") ); + if ( !incidence->location().isEmpty() ) { + tmp += "<br>"; + tmp += "<i>" + i18n( "Location:" ) + "</i>" + " "; + tmp += incidence->location().replace( "\n", "<br>" ); + } + + TQString durStr = IncidenceFormatter::durationString( incidence ); + if ( !durStr.isEmpty() ) { + tmp += "<br>"; + tmp += "<i>" + i18n( "Duration:" ) + "</i>" + " "; + tmp += durStr; + } + + if ( incidence->doesRecur() ) { + tmp += "<br>"; + tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + " "; + tmp += IncidenceFormatter::recurrenceString( incidence ); } - if (!incidence->description().isEmpty()) { - TQString desc(incidence->description()); - if (desc.length()>120) { - desc = desc.left(120) + "..."; + + if ( !incidence->description().isEmpty() ) { + TQString desc( incidence->description() ); + if ( desc.length() > maxDescLen ) { + desc = desc.left( maxDescLen ) + etc; } - tmp += "<br>----------<br>" + i18n("<i>Description:</i><br>") + desc.replace("\n", "<br>"); + tmp += "<br>----------<br>"; + tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>"; + tmp += desc.replace( "\n", "<br>" ); + tmp += "<br>----------"; + } + + int reminderCount = incidence->alarms().count(); + if ( reminderCount > 0 && incidence->isAlarmEnabled() ) { + tmp += "<br>"; + tmp += "<i>" + i18n( "Reminder:", "%n Reminders:", reminderCount ) + "</i>" + " "; + tmp += IncidenceFormatter::reminderStringList( incidence ).join( ", " ); } + + tmp += "<br>"; + tmp += tooltipFormatAttendees( incidence ); + + int categoryCount = incidence->categories().count(); + if ( categoryCount > 0 ) { + tmp += "<br>"; + tmp += "<i>" + i18n( "Category:", "%n Categories:", categoryCount ) + "</i>" + " "; + tmp += incidence->categories().join( ", " ); + } + tmp += "</qt>"; return tmp; } TQString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText ) { + return toolTipStr( 0, incidence, TQDate(), richText ); +} + +TQString IncidenceFormatter::toolTipStr( Calendar *calendar, + IncidenceBase *incidence, + const TQDate &date, + bool richText ) +{ ToolTipVisitor v; - if ( v.act( incidence, richText ) ) { + if ( v.act( calendar, incidence, date, richText ) ) { return v.result(); - } else + } else { return TQString::null; + } } - - - /******************************************************************* * Helper functions for the Incidence tooltips *******************************************************************/ @@ -2171,15 +3607,19 @@ bool IncidenceFormatter::MailBodyVisitor::visit( Event *event ) i18n("Yearly"), i18n("Yearly"), i18n("Yearly")}; mResult = mailBodyIncidence( event ); - mResult += i18n("Start Date: %1\n").arg( event->dtStartDateStr() ); + mResult += i18n("Start Date: %1\n"). + arg( IncidenceFormatter::dateToString( event->dtStart(), true ) ); if ( !event->doesFloat() ) { - mResult += i18n("Start Time: %1\n").arg( event->dtStartTimeStr() ); + mResult += i18n("Start Time: %1\n"). + arg( IncidenceFormatter::timeToString( event->dtStart(), true ) ); } if ( event->dtStart() != event->dtEnd() ) { - mResult += i18n("End Date: %1\n").arg( event->dtEndDateStr() ); + mResult += i18n("End Date: %1\n"). + arg( IncidenceFormatter::dateToString( event->dtEnd(), true ) ); } if ( !event->doesFloat() ) { - mResult += i18n("End Time: %1\n").arg( event->dtEndTimeStr() ); + mResult += i18n("End Time: %1\n"). + arg( IncidenceFormatter::timeToString( event->dtEnd(), true ) ); } if ( event->doesRecur() ) { Recurrence *recur = event->recurrence(); @@ -2228,15 +3668,19 @@ bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo ) mResult = mailBodyIncidence( todo ); if ( todo->hasStartDate() ) { - mResult += i18n("Start Date: %1\n").arg( todo->dtStartDateStr() ); + mResult += i18n("Start Date: %1\n"). + arg( IncidenceFormatter::dateToString( todo->dtStart( false ), true ) ); if ( !todo->doesFloat() ) { - mResult += i18n("Start Time: %1\n").arg( todo->dtStartTimeStr() ); + mResult += i18n("Start Time: %1\n"). + arg( IncidenceFormatter::timeToString( todo->dtStart( false ),true ) ); } } if ( todo->hasDueDate() ) { - mResult += i18n("Due Date: %1\n").arg( todo->dtDueDateStr() ); + mResult += i18n("Due Date: %1\n"). + arg( IncidenceFormatter::dateToString( todo->dtDue(), true ) ); if ( !todo->doesFloat() ) { - mResult += i18n("Due Time: %1\n").arg( todo->dtDueTimeStr() ); + mResult += i18n("Due Time: %1\n"). + arg( IncidenceFormatter::timeToString( todo->dtDue(), true ) ); } } TQString details = todo->description(); @@ -2249,9 +3693,11 @@ bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo ) bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal ) { mResult = mailBodyIncidence( journal ); - mResult += i18n("Date: %1\n").arg( journal->dtStartDateStr() ); + mResult += i18n("Date: %1\n"). + arg( IncidenceFormatter::dateToString( journal->dtStart(), true ) ); if ( !journal->doesFloat() ) { - mResult += i18n("Time: %1\n").arg( journal->dtStartTimeStr() ); + mResult += i18n("Time: %1\n"). + arg( IncidenceFormatter::timeToString( journal->dtStart(), true ) ); } if ( !journal->description().isEmpty() ) mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() ); @@ -2283,47 +3729,479 @@ static TQString recurEnd( Incidence *incidence ) return endstr; } -TQString IncidenceFormatter::recurrenceString(Incidence * incidence) +/************************************ + * More static formatting functions + ************************************/ +TQString IncidenceFormatter::recurrenceString( Incidence *incidence ) { - if ( !incidence->doesRecur() ) + if ( !incidence->doesRecur() ) { return i18n( "No recurrence" ); - + } + TQStringList 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( i18n( "last day of the month", "Last" ) ); + dayList.append( i18n( "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(); + TQString dayNames; + TQString recurStr, 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 ) - return i18n( "Recurs every minute until %1", "Recurs every %n minutes until %1", recur->frequency() ) - .arg( recurEnd( incidence ) ); - return i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() ); - case Recurrence::rHourly: - if ( recur->duration() != -1 ) - return i18n( "Recurs hourly until %1", "Recurs every %n hours until %1", recur->frequency() ) - .arg( recurEnd( incidence ) ); - return i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() ); - case Recurrence::rDaily: - if ( recur->duration() != -1 ) - return i18n( "Recurs daily until %1", "Recurs every %n days until %1", recur->frequency() ) - .arg( recurEnd( incidence ) ); - return i18n( "Recurs daily", "Recurs every %n days", recur->frequency() ); - case Recurrence::rWeekly: - if ( recur->duration() != -1 ) - return i18n( "Recurs weekly until %1", "Recurs every %n weeks until %1", recur->frequency() ) - .arg( recurEnd( incidence ) ); - return i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() ); - case Recurrence::rMonthlyPos: - case Recurrence::rMonthlyDay: - if ( recur->duration() != -1 ) - return i18n( "Recurs monthly until %1" ).arg( recurEnd( incidence ) ); - return i18n( "Recurs monthly" ); - case Recurrence::rYearlyMonth: - case Recurrence::rYearlyDay: - case Recurrence::rYearlyPos: - if ( recur->duration() != -1 ) - return i18n( "Recurs yearly until %1" ).arg( recurEnd( incidence ) ); - return i18n( "Recurs yearly" ); - default: - return i18n( "Incidence recurs" ); + case Recurrence::rNone: + return i18n( "No recurrence" ); + + case Recurrence::rMinutely: + recurStr = i18n( "Recurs every minute", "Recurs every %n minutes", recur->frequency() ); + if ( recur->duration() != -1 ) { + txt = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) ); + if ( recur->duration() > 0 ) { + txt += i18n( " (%1 occurrences)" ).arg( recur->duration() ); + } + return txt; + } + return recurStr; + + case Recurrence::rHourly: + recurStr = i18n( "Recurs hourly", "Recurs every %n hours", recur->frequency() ); + if ( recur->duration() != -1 ) { + txt = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) ); + if ( recur->duration() > 0 ) { + txt += i18n( " (%1 occurrences)" ).arg( recur->duration() ); + } + return txt; + } + return recurStr; + + case Recurrence::rDaily: + recurStr = i18n( "Recurs daily", "Recurs every %n days", recur->frequency() ); + if ( recur->duration() != -1 ) { + + txt = i18n( "%1 until %2" ).arg( recurStr ).arg( recurEnd( incidence ) ); + if ( recur->duration() > 0 ) { + txt += i18n( " (%1 occurrences)" ).arg( recur->duration() ); + } + return txt; + } + return recurStr; + + case Recurrence::rWeekly: + { + recurStr = i18n( "Recurs weekly", "Recurs every %n weeks", recur->frequency() ); + + bool addSpace = false; + for ( int i = 0; i < 7; ++i ) { + if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) { + if ( addSpace ) { + dayNames.append( i18n( "separator for list of days", ", " ) ); + } + dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, true ) ); + addSpace = true; + } + } + if ( dayNames.isEmpty() ) { + dayNames = i18n( "Recurs weekly on no days", "no days" ); + } + if ( recur->duration() != -1 ) { + txt = i18n( "%1 on %2 until %3" ). + arg( recurStr ).arg( dayNames ).arg( recurEnd( incidence ) ); + if ( recur->duration() > 0 ) { + txt += i18n( " (%1 occurrences)" ).arg( recur->duration() ); + } + return txt; + } + txt = i18n( "%1 on %2" ).arg( recurStr ).arg( dayNames ); + return txt; + } + case Recurrence::rMonthlyPos: + { + recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() ); + + if ( !recur->monthPositions().isEmpty() ) { + KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0]; + if ( recur->duration() != -1 ) { + txt = i18n( "%1 on the %2 %3 until %4" ). + arg( recurStr ). + arg( dayList[rule.pos() + 31] ). + arg( calSys->weekDayName( rule.day(), false ) ). + arg( recurEnd( incidence ) ); + if ( recur->duration() > 0 ) { + txt += i18n( " (%1 occurrences)" ).arg( recur->duration() ); + } + return txt; + } + txt = i18n( "%1 on the %2 %3" ). + arg( recurStr ). + arg( dayList[rule.pos() + 31] ). + arg( calSys->weekDayName( rule.day(), false ) ); + return txt; + } else { + return recurStr; + } + break; + } + case Recurrence::rMonthlyDay: + { + recurStr = i18n( "Recurs monthly", "Recurs every %n months", recur->frequency() ); + + if ( !recur->monthDays().isEmpty() ) { + int days = recur->monthDays()[0]; + if ( recur->duration() != -1 ) { + txt = i18n( "%1 on the %2 day until %3" ). + arg( recurStr ). + arg( dayList[days + 31] ). + arg( recurEnd( incidence ) ); + if ( recur->duration() > 0 ) { + txt += i18n( " (%1 occurrences)" ).arg( recur->duration() ); + } + return txt; + } + txt = i18n( "%1 on the %2 day" ).arg( recurStr ).arg( dayList[days + 31] ); + return txt; + } else { + return recurStr; + } + break; + } + case Recurrence::rYearlyMonth: + { + recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() ); + + if ( recur->duration() != -1 ) { + if ( !recur->yearDates().isEmpty() ) { + txt = i18n( "%1 on %2 %3 until %4" ). + arg( recurStr ). + arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ). + arg( dayList[ recur->yearDates()[0] + 31 ] ). + arg( recurEnd( incidence ) ); + if ( recur->duration() > 0 ) { + txt += i18n( " (%1 occurrences)" ).arg( recur->duration() ); + } + return txt; + } + } + if ( !recur->yearDates().isEmpty() ) { + txt = i18n( "%1 on %2 %3" ). + arg( recurStr ). + arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ). + arg( dayList[ recur->yearDates()[0] + 31 ] ); + return txt; + } else { + if ( !recur->yearMonths().isEmpty() ) { + txt = i18n( "Recurs yearly on %1 %2" ). + arg( calSys->monthName( recur->yearMonths()[0], + recur->startDate().year() ) ). + arg( dayList[ recur->startDate().day() + 31 ] ); + } else { + txt = i18n( "Recurs yearly on %1 %2" ). + arg( calSys->monthName( recur->startDate().month(), + recur->startDate().year() ) ). + arg( dayList[ recur->startDate().day() + 31 ] ); + } + return txt; + } + break; + } + case Recurrence::rYearlyDay: + { + recurStr = i18n( "Recurs yearly", "Recurs every %n years", recur->frequency() ); + if ( !recur->yearDays().isEmpty() ) { + if ( recur->duration() != -1 ) { + txt = i18n( "%1 on day %2 until %3" ). + arg( recurStr ). + arg( recur->yearDays()[0] ). + arg( recurEnd( incidence ) ); + if ( recur->duration() > 0 ) { + txt += i18n( " (%1 occurrences)" ).arg( recur->duration() ); + } + return txt; + } + txt = i18n( "%1 on day %2" ).arg( recurStr ).arg( recur->yearDays()[0] ); + return txt; + } else { + return recurStr; + } + break; + } + case Recurrence::rYearlyPos: + { + recurStr = i18n( "Every year", "Every %n years", recur->frequency() ); + if ( !recur->yearPositions().isEmpty() && !recur->yearMonths().isEmpty() ) { + KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0]; + if ( recur->duration() != -1 ) { + txt = i18n( "%1 on the %2 %3 of %4 until %5" ). + arg( recurStr ). + arg( dayList[rule.pos() + 31] ). + arg( calSys->weekDayName( rule.day(), false ) ). + arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ). + arg( recurEnd( incidence ) ); + if ( recur->duration() > 0 ) { + txt += i18n( " (%1 occurrences)" ).arg( recur->duration() ); + } + return txt; + } + txt = i18n( "%1 on the %2 %3 of %4" ). + arg( recurStr ). + arg( dayList[rule.pos() + 31] ). + arg( calSys->weekDayName( rule.day(), false ) ). + arg( calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ); + return txt; + } else { + return recurStr; + } + break; + } + } + + return i18n( "Incidence recurs" ); +} + +TQString IncidenceFormatter::timeToString( const TQDateTime &date, bool shortfmt ) +{ + return KGlobal::locale()->formatTime( date.time(), !shortfmt ); +} + +TQString IncidenceFormatter::dateToString( const TQDateTime &date, bool shortfmt ) +{ + return + KGlobal::locale()->formatDate( date.date(), shortfmt ); +} + +TQString IncidenceFormatter::dateTimeToString( const TQDateTime &date, + bool allDay, bool shortfmt ) +{ + if ( allDay ) { + return dateToString( date, shortfmt ); } + + return KGlobal::locale()->formatDateTime( date, shortfmt ); +} + +TQString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence ) +{ + if ( !calendar || !incidence ) { + return TQString::null; + } + + CalendarResources *calendarResource = dynamic_cast<CalendarResources*>( calendar ); + if ( !calendarResource ) { + return TQString::null; + } + + ResourceCalendar *resourceCalendar = calendarResource->resource( incidence ); + if ( resourceCalendar ) { + if ( !resourceCalendar->subresources().isEmpty() ) { + TQString subRes = resourceCalendar->subresourceIdentifier( incidence ); + if ( subRes.isEmpty() ) { + return resourceCalendar->resourceName(); + } else { + return resourceCalendar->labelForSubresource( subRes ); + } + } + return resourceCalendar->resourceName(); + } + + return TQString::null; +} + +static TQString secs2Duration( int secs ) +{ + TQString tmp; + int days = secs / 86400; + if ( days > 0 ) { + tmp += i18n( "1 day", "%n days", days ); + tmp += ' '; + secs -= ( days * 86400 ); + } + int hours = secs / 3600; + if ( hours > 0 ) { + tmp += i18n( "1 hour", "%n hours", hours ); + tmp += ' '; + secs -= ( hours * 3600 ); + } + int mins = secs / 60; + if ( mins > 0 ) { + tmp += i18n( "1 minute", "%n minutes", mins ); + } + return tmp; +} + +TQString IncidenceFormatter::durationString( Incidence *incidence ) +{ + TQString tmp; + if ( incidence->type() == "Event" ) { + Event *event = static_cast<Event *>( incidence ); + if ( event->hasEndDate() ) { + if ( !event->doesFloat() ) { + tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) ); + } else { + tmp = i18n( "1 day", "%n days", + event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 ); + } + } else { + tmp = i18n( "forever" ); + } + } else if ( incidence->type() == "Todo" ) { + Todo *todo = static_cast<Todo *>( incidence ); + if ( todo->hasDueDate() ) { + if ( todo->hasStartDate() ) { + if ( !todo->doesFloat() ) { + tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) ); + } else { + tmp = i18n( "1 day", "%n days", + todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 ); + } + } + } + } + return tmp; +} + +TQStringList IncidenceFormatter::reminderStringList( Incidence *incidence, bool shortfmt ) +{ + //TODO: implement shortfmt=false + Q_UNUSED( shortfmt ); + + TQStringList reminderStringList; + + if ( incidence ) { + Alarm::List alarms = incidence->alarms(); + Alarm::List::ConstIterator it; + for ( it = alarms.begin(); it != alarms.end(); ++it ) { + Alarm *alarm = *it; + int offset = 0; + TQString 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 = i18n( "N days/hours/minutes before the start datetime", + "%1 before the start" ); + } else if ( offset > 0 ) { + offsetStr = i18n( "N days/hours/minutes after the start datetime", + "%1 after the start" ); + } 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 = i18n( "N days/hours/minutes before the due datetime", + "%1 before the to-do is due" ); + } else { + offsetStr = i18n( "N days/hours/minutes before the end datetime", + "%1 before the end" ); + } + } else if ( offset > 0 ) { + if ( incidence->type() == "Todo" ) { + offsetStr = i18n( "N days/hours/minutes after the due datetime", + "%1 after the to-do is due" ); + } else { + offsetStr = i18n( "N days/hours/minutes after the end datetime", + "%1 after the end" ); + } + } else { //offset is 0 + if ( incidence->type() == "Todo" ) { + Todo *t = static_cast<Todo *>( incidence ); + if ( t->dtDue().isValid() ) { + atStr = KGlobal::locale()->formatDateTime( t->dtDue() ); + } + } else { + Event *e = static_cast<Event *>( incidence ); + if ( e->dtEnd().isValid() ) { + atStr = KGlobal::locale()->formatDateTime( e->dtEnd() ); + } + } + } + } + if ( offset == 0 ) { + if ( !atStr.isEmpty() ) { + remStr = i18n( "reminder occurs at datetime", "at %1" ).arg( atStr ); + } + } else { + remStr = offsetStr.arg( secs2Duration( offset ) ); + } + + if ( alarm->repeatCount() > 0 ) { + TQString countStr = i18n( "repeats once", "repeats %n times", alarm->repeatCount() ); + TQString intervalStr = i18n( "interval is N days/hours/minutes", "interval is %1" ). + arg( secs2Duration( alarm->snoozeTime().asSeconds() ) ); + TQString repeatStr = i18n( "(repeat string, interval string)", "(%1, %2)" ). + arg( countStr, intervalStr ); + remStr = remStr + ' ' + repeatStr; + + } + reminderStringList << remStr; + } + } + + return reminderStringList; } diff --git a/libkcal/incidenceformatter.h b/libkcal/incidenceformatter.h index ce37b0164..b2dcd4324 100644 --- a/libkcal/incidenceformatter.h +++ b/libkcal/incidenceformatter.h @@ -3,6 +3,7 @@ Copyright (c) 2001-2003 Cornelius Schumacher <schumacher@kde.org> Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com> + Copyright (c) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public @@ -22,10 +23,12 @@ #ifndef KCAL_INCIDENCEFORMATTER_H #define KCAL_INCIDENCEFORMATTER_H -#include <tqstring.h> - #include "libkcal_export.h" +#include <tqdatetime.h> +#include <tqstring.h> +#include <tqstringlist.h> + namespace KCal { class Calendar; class Incidence; @@ -36,7 +39,7 @@ class LIBKCAL_EXPORT InvitationFormatterHelper public: virtual TQString generateLinkURL( const TQString &id ) { return id; } virtual TQString makeLink( const TQString &id, const TQString &text ); - virtual Calendar* calendar() const { return 0; } + virtual Calendar *calendar() const { return 0; } }; /** @@ -49,12 +52,27 @@ class LIBKCAL_EXPORT InvitationFormatterHelper class LIBKCAL_EXPORT IncidenceFormatter { public: - static TQString toolTipString( IncidenceBase *incidence, bool richText = true ); + static TQString KDE_DEPRECATED toolTipString( IncidenceBase *incidence, bool richText = true ); + static TQString toolTipStr( Calendar *calendar, + IncidenceBase *incidence, + const TQDate &date=TQDate(), + bool richText = true ); static TQString mailBodyString( IncidenceBase *incidencebase ); - static TQString extensiveDisplayString( IncidenceBase *incidence ); + static TQString KDE_DEPRECATED extensiveDisplayString( IncidenceBase *incidence ); + static TQString extensiveDisplayStr( Calendar *calendar, + IncidenceBase *incidence, + const TQDate &date=TQDate() ); static TQString formatICalInvitation( TQString invitation, Calendar *mCalendar, InvitationFormatterHelper *helper ); + static TQString KDE_DEPRECATED formatICalInvitationNoHtml( TQString invitation, + Calendar *mCalendar, + InvitationFormatterHelper *helper ); + static TQString formatICalInvitationNoHtml( TQString invitation, + Calendar *mCalendar, + InvitationFormatterHelper *helper, + const TQString &sender ); + // Format a TNEF attachment to an HTML mail static TQString formatTNEFInvitation( const TQByteArray& tnef, Calendar *mCalendar, @@ -63,7 +81,44 @@ class LIBKCAL_EXPORT IncidenceFormatter static TQString msTNEFToVPart( const TQByteArray& tnef ); static TQString recurrenceString( Incidence *incidence ); + + /* + Returns a reminder string computed for the specified Incidence. + Each item of the returning TQStringList corresponds to a string + representation of an reminder belonging to this incidence. + @param incidence is a pointer to the Incidence. + @param shortfmt if false, a short version of each reminder is printed; + else a longer version of each reminder is printed. + */ + static TQStringList reminderStringList( Incidence *incidence, bool shortfmt = true ); + + static TQString timeToString( const TQDateTime &date, bool shortfmt = true ); + + static TQString dateToString( const TQDateTime &date, bool shortfmt = true ); + + static TQString dateTimeToString( const TQDateTime &date, + bool dateOnly = false, + bool shortfmt = true ); + /** + Returns a Calendar Resource label name for the specified Incidence. + @param calendar is a pointer to the Calendar. + @param incidence is a pointer to the Incidence. + */ + static TQString resourceString( Calendar *calendar, Incidence *incidence ); + + /** + Returns a duration string computed for the specified Incidence. + Only makes sense for Events and Todos. + @param incidence is a pointer to the Incidence. + */ + static TQString durationString( Incidence *incidence ); + private: + static TQString formatICalInvitationHelper( TQString invitation, + Calendar *mCalendar, + InvitationFormatterHelper *helper, + bool noHtmlMode, + const TQString &sender ); class EventViewerVisitor; class ScheduleMessageVisitor; class InvitationHeaderVisitor; diff --git a/libkcal/kcal_manager.desktop b/libkcal/kcal_manager.desktop index e606a9d6b..a0a72e282 100644 --- a/libkcal/kcal_manager.desktop +++ b/libkcal/kcal_manager.desktop @@ -29,7 +29,6 @@ Name[hu]=Naptár Name[is]=Dagatal Name[it]=Calendario Name[ja]=カレンダー -Name[ka]=კალენდარი Name[kk]=Күнтізбе Name[km]=ប្រតិទិន Name[lt]=Kalendorius @@ -56,8 +55,7 @@ Name[tg]=Тақвим Name[th]=บันทึกประจำวัน Name[tr]=Takvim Name[uk]=Календар -Name[uz]=Kalendar -Name[uz@cyrillic]=Календар +Name[uz]=Календар Name[zh_CN]=日历 Name[zh_TW]=行事曆 Type=Service diff --git a/libkcal/libical/configure.in.in b/libkcal/libical/configure.in.in new file mode 100644 index 000000000..79aafbc30 --- /dev/null +++ b/libkcal/libical/configure.in.in @@ -0,0 +1,20 @@ +dnl Checks for programs. +AC_PROG_YACC +AM_PROG_LEX + +AC_CHECK_PROGS(PERL, perl5 perl) + +AC_DEFINE(ICAL_SAFESAVES,1, [safe saves]) +AC_DEFINE(ICAL_UNIX_NEWLINE,1, [unix newline]) + +AC_CHECK_HEADERS(time.h sys/types.h assert.h) + +dnl Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_TYPE_SIZE_T +AC_STRUCT_TM +AM_PROG_LEX + +dnl Checks for library functions. +AC_CHECK_FUNCS(strdup) + diff --git a/libkcal/libical/src/libical/icalattach.c b/libkcal/libical/src/libical/icalattach.c new file mode 100644 index 000000000..106096bf9 --- /dev/null +++ b/libkcal/libical/src/libical/icalattach.c @@ -0,0 +1,151 @@ +/* -*- Mode: C -*- + ====================================================================== + FILE: icalattach.c + CREATOR: acampi 28 May 02 + + $Id: icalattach.c 1024886 2009-09-17 13:28:13Z winterz $ + $Locker: $ + + + (C) COPYRIGHT 2000, Andrea Campi + + This program is free software; you can redistribute it and/or modify + it under the terms of either: + + The LGPL as published by the Free Software Foundation, version + 2.1, available at: http://www.fsf.org/copyleft/lesser.html + + Or: + + The Mozilla Public License Version 1.0. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + The original code is icaltypes.c + + ======================================================================*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "icaltypes.h" +#include "icalerror.h" +#include "icalmemory.h" +#include "icalattachimpl.h" +#include <stdlib.h> /* for malloc and abs() */ +#include <errno.h> /* for errno */ +#include <string.h> /* for icalmemory_strdup */ +#include <assert.h> + +icalattach * +icalattach_new_from_url (const char *url) +{ + icalattach *attach; + char *url_copy; + + icalerror_check_arg_rz ((url != NULL), "url"); + + if ((attach = malloc (sizeof (icalattach))) == NULL) { + errno = ENOMEM; + return NULL; + } + + if ((url_copy = strdup (url)) == NULL) { + free (attach); + errno = ENOMEM; + return NULL; + } + + attach->refcount = 1; + attach->is_url = 1; + attach->u.url.url = url_copy; + + return attach; +} + +icalattach * +icalattach_new_from_data (unsigned char *data, icalattach_free_fn_t free_fn, + void *free_fn_data) +{ + icalattach *attach; + char *data_copy; + + icalerror_check_arg_rz ((data != NULL), "data"); + + if ((attach = malloc (sizeof (icalattach))) == NULL) { + errno = ENOMEM; + return NULL; + } + + if ((data_copy = strdup (data)) == NULL) { + free (attach); + errno = ENOMEM; + return NULL; + } + + attach->refcount = 1; + attach->is_url = 0; + attach->u.data.data = data_copy; + attach->u.data.free_fn = free_fn; + attach->u.data.free_fn_data = free_fn_data; + + return attach; +} + +void +icalattach_ref (icalattach *attach) +{ + icalerror_check_arg_rv ((attach != NULL), "attach"); + icalerror_check_arg_rv ((attach->refcount > 0), "attach->refcount > 0"); + + attach->refcount++; +} + +void +icalattach_unref (icalattach *attach) +{ + icalerror_check_arg_rv ((attach != NULL), "attach"); + icalerror_check_arg_rv ((attach->refcount > 0), "attach->refcount > 0"); + + attach->refcount--; + + if (attach->refcount != 0) + return; + + if (attach->is_url) { + free (attach->u.url.url); + } else { + free (attach->u.data.data); +/* unused for now + if (attach->u.data.free_fn) + (* attach->u.data.free_fn) (attach->u.data.data, attach->u.data.free_fn_data); +*/ + } + + free (attach); +} + +int +icalattach_get_is_url (icalattach *attach) +{ + icalerror_check_arg_rz ((attach != NULL), "attach"); + + return attach->is_url ? 1 : 0; +} + +const char * +icalattach_get_url (icalattach *attach) +{ + icalerror_check_arg_rz ((attach != NULL), "attach"); + icalerror_check_arg_rz ((attach->is_url), "attach->is_url"); + + return attach->u.url.url; +} + +unsigned char * +icalattach_get_data (icalattach *attach) +{ + icalerror_check_arg_rz ((attach != NULL), "attach"); + icalerror_check_arg_rz ((!attach->is_url), "!attach->is_url"); + + return attach->u.data.data; +} diff --git a/libkcal/local.desktop b/libkcal/local.desktop index 669565922..9cf91413e 100644 --- a/libkcal/local.desktop +++ b/libkcal/local.desktop @@ -26,7 +26,6 @@ Name[hu]=Helyi fájlban tárolt naptár Name[is]=Dagatal í staðbundinni skrá Name[it]=Calendario in file locale Name[ja]=ローカルファイルのカレンダー -Name[ka]=კალენდარი ლოკალურ ფაილში Name[kk]=Жергілікті файлдағы күнтізбе Name[km]=ប្រតិទិននៅក្នុងឯកសារមូលដ្ឋាន Name[lt]=Kalendorius vietinėje byloje diff --git a/libkcal/localdir.desktop b/libkcal/localdir.desktop index f65fe7450..d7e4b4137 100644 --- a/libkcal/localdir.desktop +++ b/libkcal/localdir.desktop @@ -26,7 +26,6 @@ Name[hu]=Helyi könyvtárban tárolt naptár Name[is]=Dagatal í staðbundinni möppu Name[it]=Calendario nella directory locale Name[ja]=ローカルディレクトリのカレンダー -Name[ka]=კალენდარი ლოკალურ დირექტორიაში Name[kk]=Жергілікті каталогтағы күнтізбе Name[km]=ប្រតិទិននៅក្នុងថតមូលដ្ឋាន Name[lt]=Kalendorius vietiniame aplanke diff --git a/libkcal/period.cpp b/libkcal/period.cpp index be07d7d3b..ff944ee94 100644 --- a/libkcal/period.cpp +++ b/libkcal/period.cpp @@ -51,6 +51,14 @@ bool Period::operator<( const Period& other ) return start() < other.start(); } +bool Period::operator==( const Period &other ) const +{ + return + mStart == other.mStart && + mEnd == other.mEnd && + mHasDuration == other.mHasDuration; +} + TQDateTime Period::start() const { return mStart; diff --git a/libkcal/period.h b/libkcal/period.h index 64dd24d9c..b0ca32a33 100644 --- a/libkcal/period.h +++ b/libkcal/period.h @@ -42,6 +42,24 @@ class KDE_EXPORT Period /** Returns true if this element is smaller than the @param other one */ bool operator<( const Period& other ); + /** + Returns true if this period is equal to the @p other one. + Even if their start and end times are the same, two periods are + considered not equal if one is defined in terms of a duration and the + other in terms of a start and end time. + + @param other the other period to compare + */ + bool operator==( const Period &other ) const; + + /** + Returns true if this period is not equal to the @p other one. + + @param other the other period to compare + @see operator==() + */ + bool operator!=( const Period &other ) const { return !operator==( other ); } + TQDateTime start() const; TQDateTime end() const; Duration duration(); diff --git a/libkcal/recurrence.cpp b/libkcal/recurrence.cpp index c2d7897a0..ae47db326 100644 --- a/libkcal/recurrence.cpp +++ b/libkcal/recurrence.cpp @@ -34,7 +34,6 @@ using namespace KCal; - Recurrence::Recurrence() : mFloating( false ), mRecurReadOnly(false), @@ -282,9 +281,8 @@ bool Recurrence::recursOn(const TQDate &qd) const if ( mRDates.contains( qd ) ) return true; - // Check if it might recur today at all. bool recurs = false; - if ( startDate() == qd ) recurs = true; + for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) { recurs = recurs || (*rr)->recursOn( qd ); } @@ -770,10 +768,74 @@ TimeList Recurrence::recurTimesOn( const TQDate &date ) const return times; } +DateTimeList Recurrence::timesInInterval( const TQDateTime &start, const TQDateTime &end ) const +{ + int i, count; + DateTimeList times; + for ( i = 0, count = mRRules.count(); i < count; ++i ) { + times += mRRules[i]->timesInInterval( start, end ); + } + + // add rdatetimes that fit in the interval + for ( i = 0, count = mRDateTimes.count(); i < count; ++i ) { + if ( mRDateTimes[i] >= start && mRDateTimes[i] <= end ) { + times += mRDateTimes[i]; + } + } + + // add rdates that fit in the interval + TQDateTime qdt( mStartDateTime ); + for ( i = 0, count = mRDates.count(); i < count; ++i ) { + qdt.setDate( mRDates[i] ); + if ( qdt >= start && qdt <= end ) { + times += qdt; + } + } + + // Recurrence::timesInInterval(...) doesn't explicitly add mStartDateTime to the list + // of times to be returned. It calls mRRules[i]->timesInInterval(...) which include + // mStartDateTime. + // So, If we have rdates/rdatetimes but don't have any rrule we must explicitly + // add mStartDateTime to the list, otherwise we won't see the first occurrence. + if ( ( !mRDates.isEmpty() || !mRDateTimes.isEmpty() ) && + mRRules.isEmpty() && + start <= mStartDateTime && + end >= mStartDateTime ) { + times += mStartDateTime; + } + + qSortUnique( times ); + + // Remove excluded times + int idt = 0; + int enddt = times.count(); + for ( i = 0, count = mExDates.count(); i < count && idt < enddt; ++i ) { + while ( idt < enddt && times[idt].date() < mExDates[i] ) ++idt; + while ( idt < enddt && times[idt].date() == mExDates[i] ) { + times.remove( times.at( idt ) ); + --enddt; + } + } + DateTimeList extimes; + for ( i = 0, count = mExRules.count(); i < count; ++i ) { + extimes += mExRules[i]->timesInInterval( start, end ); + } + extimes += mExDateTimes; + qSortUnique( extimes ); + + int st = 0; + for ( i = 0, count = extimes.count(); i < count; ++i ) { + int j = removeSorted( times, extimes[i], st ); + if ( j >= 0 ) { + st = j; + } + } + + return times; +} TQDateTime Recurrence::getNextDateTime( const TQDateTime &preDateTime ) const { -//kdDebug(5800) << " Recurrence::getNextDateTime after " << preDateTime << endl; TQDateTime nextDT = preDateTime; // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g. // the exrule is identical to the rrule). If an occurrence is found, break @@ -795,40 +857,51 @@ TQDateTime Recurrence::getNextDateTime( const TQDateTime &preDateTime ) const ++loop; // First, get the next recurrence from the RDate lists DateTimeList dates; - if ( nextDT < startDateTime() ) dates << startDateTime(); - DateTimeList::ConstIterator it = mRDateTimes.begin(); + if ( nextDT < startDateTime() ) { + dates << startDateTime(); + } + + int end; // Assume that the rdatetime list is sorted - while ( it != mRDateTimes.end() && (*it) <= nextDT ) ++it; - if ( it != mRDateTimes.end() ) dates << (*it); + int i = findGT( mRDateTimes, nextDT, 0 ); + if ( i >= 0 ) { + dates << mRDateTimes[i]; + } -/*kdDebug(5800) << " nextDT: " << nextDT << ", startDT: " << startDateTime() << endl; -kdDebug(5800) << " getNextDateTime: found " << dates.count() << " RDATES and DTSTART in loop " << loop << endl;*/ - DateList::ConstIterator dit = mRDates.begin(); - while ( dit != mRDates.end() && TQDateTime( (*dit), startDateTime().time() ) <= nextDT ) ++dit; - if ( dit != mRDates.end() ) dates << TQDateTime( (*dit), startDateTime().time() ); + TQDateTime qdt( startDateTime() ); + for ( i = 0, end = mRDates.count(); i < end; ++i ) { + qdt.setDate( mRDates[i] ); + if ( qdt > nextDT ) { + dates << qdt; + break; + } + } // Add the next occurrences from all RRULEs. - for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) { - TQDateTime dt = (*rr)->getNextDate( nextDT ); - if ( dt.isValid() ) dates << dt; + for ( i = 0, end = mRRules.count(); i < end; ++i ) { + TQDateTime dt = mRRules[i]->getNextDate( nextDT ); + if ( dt.isValid() ) { + dates << dt; + } } // Take the first of these (all others can't be used later on) qSortUnique( dates ); -// kdDebug(5800) << " getNextDateTime: found " << dates.count() << " dates in loop " << loop << endl; - - if ( dates.isEmpty() ) return TQDateTime(); + if ( dates.isEmpty() ) { + return TQDateTime(); + } nextDT = dates.first(); // Check if that date/time is excluded explicitly or by an exrule: - if ( !mExDates.contains( nextDT.date() ) && !mExDateTimes.contains( nextDT ) ) { -// kdDebug(5800) << " NextDT" << nextDT << " not excluded by EXDATE " << endl; + if ( !containsSorted( mExDates, nextDT.date() ) && + !containsSorted( mExDateTimes, nextDT ) ) { bool allowed = true; - for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) { - allowed = allowed && !( (*rr)->recursAt( nextDT ) ); + for ( i = 0, end = mExRules.count(); i < end; ++i ) { + allowed = allowed && !( mExRules[i]->recursAt( nextDT ) ); + } + if ( allowed ) { + return nextDT; } -// kdDebug(5800) << " NextDT " << nextDT << ", allowed=" << allowed << endl; - if ( allowed ) return nextDT; } } @@ -856,44 +929,50 @@ TQDateTime Recurrence::getPreviousDateTime( const TQDateTime &afterDateTime ) co ++loop; // First, get the next recurrence from the RDate lists DateTimeList dates; - if ( prevDT > startDateTime() ) dates << startDateTime(); - - DateTimeList::ConstIterator dtit = mRDateTimes.end(); - if ( dtit != mRDateTimes.begin() ) { - do { - --dtit; - } while ( dtit != mRDateTimes.begin() && (*dtit) >= prevDT ); - if ( (*dtit) < prevDT ) dates << (*dtit); + if ( prevDT > startDateTime() ) { + dates << startDateTime(); + } + + int i = findLT( mRDateTimes, prevDT, 0 ); + if ( i >= 0 ) { + dates << mRDateTimes[i]; } - DateList::ConstIterator dit = mRDates.end(); - if ( dit != mRDates.begin() ) { - do { - --dit; - } while ( dit != mRDates.begin() && TQDateTime((*dit), startDateTime().time()) >= prevDT ); - if ( TQDateTime((*dit), startDateTime().time()) < prevDT ) - dates << TQDateTime( (*dit), startDateTime().time() ); + TQDateTime qdt( startDateTime() ); + for ( i = mRDates.count(); --i >= 0; ) { + qdt.setDate( mRDates[i] ); + if ( qdt < prevDT ) { + dates << qdt; + break; + } } // Add the previous occurrences from all RRULEs. - for ( RecurrenceRule::List::ConstIterator rr = mRRules.begin(); rr != mRRules.end(); ++rr ) { - TQDateTime dt = (*rr)->getPreviousDate( prevDT ); - if ( dt.isValid() ) dates << dt; + int end; + for ( i = 0, end = mRRules.count(); i < end; ++i ) { + TQDateTime dt = mRRules[i]->getPreviousDate( prevDT ); + if ( dt.isValid() ) { + dates << dt; + } } -//kdDebug(5800) << " getPreviousDateTime: found " << dates.count() << " dates in loop " << loop << endl; // Take the last of these (all others can't be used later on) qSortUnique( dates ); - if ( dates.isEmpty() ) return TQDateTime(); + if ( dates.isEmpty() ) { + return TQDateTime(); + } prevDT = dates.last(); // Check if that date/time is excluded explicitly or by an exrule: - if ( !mExDates.contains( prevDT.date() ) && !mExDateTimes.contains( prevDT ) ) { + if ( !containsSorted( mExDates, prevDT.date() ) && + !containsSorted( mExDateTimes, prevDT ) ) { bool allowed = true; - for ( RecurrenceRule::List::ConstIterator rr = mExRules.begin(); rr != mExRules.end(); ++rr ) { - allowed = allowed && !( (*rr)->recursAt( prevDT ) ); + for ( i = 0, end = mExRules.count(); i < end; ++i ) { + allowed = allowed && !( mExRules[i]->recursAt( prevDT ) ); + } + if ( allowed ) { + return prevDT; } - if ( allowed ) return prevDT; } } diff --git a/libkcal/recurrence.h b/libkcal/recurrence.h index ec6965977..0a594a6fb 100644 --- a/libkcal/recurrence.h +++ b/libkcal/recurrence.h @@ -166,6 +166,21 @@ class LIBKCAL_EXPORT Recurrence : public RecurrenceRule::Observer */ TQValueList<TQTime> recurTimesOn(const TQDate &date) const; + /** Returns a list of all the times at which the recurrence will occur + * between two specified times. + * + * There is a (large) maximum limit to the number of times returned. If due to + * this limit the list is incomplete, this is indicated by the last entry being + * set to an invalid TQDateTime value. If you need further values, call the + * method again with a start time set to just after the last valid time returned. + * + * @param start inclusive start of interval + * @param end inclusive end of interval + * @return list of date/time values + */ + DateTimeList timesInInterval( const TQDateTime &start, const TQDateTime &end ) const; + + /** Returns the date and time of the next recurrence, after the specified date/time. * If the recurrence has no time, the next date after the specified date is returned. * @param preDateTime the date/time after which to find the recurrence. diff --git a/libkcal/recurrencerule.cpp b/libkcal/recurrencerule.cpp index ce64aadc1..17292ec53 100644 --- a/libkcal/recurrencerule.cpp +++ b/libkcal/recurrencerule.cpp @@ -32,6 +32,8 @@ using namespace KCal; +// Maximum number of intervals to process +const int LOOP_LIMIT = 10000; // FIXME: If Qt is ever changed so that TQDateTime:::addSecs takes into account // DST shifts, we need to use our own addSecs method, too, since we @@ -774,6 +776,8 @@ void RecurrenceRule::setWeekStart( short weekStart ) void RecurrenceRule::buildConstraints() { + mTimedRepetition = 0; + mNoByRules = mBySetPos.isEmpty(); mConstraints.clear(); Constraint con; if ( mWeekStart > 0 ) con.weekstart = mWeekStart; @@ -785,6 +789,7 @@ void RecurrenceRule::buildConstraints() #define intConstraint( list, element ) \ if ( !list.isEmpty() ) { \ + mNoByRules = false; \ for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \ for ( intit = list.constBegin(); intit != list.constEnd(); ++intit ) { \ con = (*it); \ @@ -806,6 +811,7 @@ void RecurrenceRule::buildConstraints() #undef intConstraint if ( !mByDays.isEmpty() ) { + mNoByRules = false; for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { TQValueList<WDayPos>::const_iterator dayit; for ( dayit = mByDays.constBegin(); dayit != mByDays.constEnd(); ++dayit ) { @@ -866,12 +872,28 @@ void RecurrenceRule::buildConstraints() } #undef fixConstraint - Constraint::List::Iterator conit = mConstraints.begin(); - while ( conit != mConstraints.end() ) { - if ( (*conit).isConsistent( mPeriod ) ) { - ++conit; - } else { - conit = mConstraints.remove( conit ); + if ( mNoByRules ) { + switch ( mPeriod ) { + case rHourly: + mTimedRepetition = mFrequency * 3600; + break; + case rMinutely: + mTimedRepetition = mFrequency * 60; + break; + case rSecondly: + mTimedRepetition = mFrequency; + break; + default: + break; + } + } else { + Constraint::List::Iterator conit = mConstraints.begin(); + while ( conit != mConstraints.end() ) { + if ( (*conit).isConsistent( mPeriod ) ) { + ++conit; + } else { + conit = mConstraints.remove( conit ); + } } } } @@ -941,81 +963,179 @@ bool RecurrenceRule::dateMatchesRules( const TQDateTime &qdt ) const bool RecurrenceRule::recursOn( const TQDate &qd ) const { -// kdDebug(5800) << " RecurrenceRule::recursOn: " << qd << endl; - if ( qd < startDt().date() ) return false; + int i, iend; + if ( doesFloat() ) { + // It's a date-only rule, so it has no time specification. + if ( qd < mDateStart.date() ) { + return false; + } + // Start date is only included if it really matches + TQDate endDate; + if ( mDuration >= 0 ) { + endDate = endDt().date(); + if ( qd > endDate ) { + return false; + } + } + + // The date must be in an appropriate interval (getNextValidDateInterval), + // Plus it must match at least one of the constraints + bool match = false; + for ( i = 0, iend = mConstraints.count(); i < iend && !match; ++i ) { + match = mConstraints[i].matches( qd, recurrenceType() ); + } + if ( !match ) { + return false; + } + + TQDateTime start( qd, TQTime( 0, 0, 0 ) ); + Constraint interval( getNextValidDateInterval( start, recurrenceType() ) ); + // Constraint::matches is quite efficient, so first check if it can occur at + // all before we calculate all actual dates. + if ( !interval.matches( qd, recurrenceType() ) ) { + return false; + } + // We really need to obtain the list of dates in this interval, since + // otherwise BYSETPOS will not work (i.e. the date will match the interval, + // but BYSETPOS selects only one of these matching dates! + TQDateTime end = start.addDays(1); + do { + DateTimeList dts = datesForInterval( interval, recurrenceType() ); + for ( i = 0, iend = dts.count(); i < iend; ++i ) { + if ( dts[i].date() >= qd ) { + return dts[i].date() == qd; + } + } + interval.increase( recurrenceType(), frequency() ); + } while ( interval.intervalDateTime( recurrenceType() ) < end ); + return false; + } + + // It's a date-time rule, so we need to take the time specification into account. + TQDateTime start( qd, TQTime( 0, 0, 0 ) ); + TQDateTime end = start.addDays( 1 ); + if ( end < mDateStart ) { + return false; + } + if ( start < mDateStart ) { + start = mDateStart; + } + // Start date is only included if it really matches -// if ( qd == startDt().date() ) return true; - if ( mDuration >= 0 && qd > endDt().date() ) return false; + if ( mDuration >= 0 ) { + TQDateTime endRecur = endDt(); + if ( endRecur.isValid() ) { + if ( start > endRecur ) { + return false; + } + if ( end > endRecur ) { + end = endRecur; // limit end-of-day time to end of recurrence rule + } + } + } + + if ( mTimedRepetition ) { + // It's a simple sub-daily recurrence with no constraints + int n = static_cast<int>( ( mDateStart.secsTo( start ) - 1 ) % mTimedRepetition ); + return start.addSecs( mTimedRepetition - n ) < end; + } + + // Find the start and end dates in the time spec for the rule + TQDate startDay = start.date(); + TQDate endDay = end.addSecs( -1 ).date(); + int dayCount = startDay.daysTo( endDay ) + 1; // The date must be in an appropriate interval (getNextValidDateInterval), // Plus it must match at least one of the constraints bool match = false; - for ( Constraint::List::ConstIterator it = mConstraints.begin(); - it!=mConstraints.end(); ++it ) { - match = match || ( (*it).matches( qd, recurrenceType() ) ); + for ( i = 0, iend = mConstraints.count(); i < iend && !match; ++i ) { + match = mConstraints[i].matches( startDay, recurrenceType() ); + for ( int day = 1; day < dayCount && !match; ++day ) { + match = mConstraints[i].matches( startDay.addDays( day ), recurrenceType() ); + } + } + if ( !match ) { + return false; } - if ( !match ) return false; - TQDateTime tmp( qd, TQTime( 0, 0, 0 ) ); - Constraint interval( getNextValidDateInterval( tmp, recurrenceType() ) ); + + Constraint interval( getNextValidDateInterval( start, recurrenceType() ) ); // Constraint::matches is quite efficient, so first check if it can occur at // all before we calculate all actual dates. - if ( !interval.matches( qd, recurrenceType() ) ) return false; + match = false; + Constraint intervalm = interval; + do { + match = intervalm.matches( startDay, recurrenceType() ); + for ( int day = 1; day < dayCount && !match; ++day ) { + match = intervalm.matches( startDay.addDays( day ), recurrenceType() ); + } + if ( match ) { + break; + } + intervalm.increase( recurrenceType(), frequency() ); + } while ( intervalm.intervalDateTime( recurrenceType() ) < end ); + if ( !match ) { + return false; + } + // We really need to obtain the list of dates in this interval, since // otherwise BYSETPOS will not work (i.e. the date will match the interval, // but BYSETPOS selects only one of these matching dates! - DateTimeList times = datesForInterval( interval, recurrenceType() ); - DateTimeList::ConstIterator it = times.begin(); - while ( ( it != times.end() ) && ( (*it).date() < qd ) ) - ++it; - if ( it != times.end() ) { - // If we are beyond the end... - if ( mDuration >= 0 && (*it) > endDt() ) - return false; - if ( (*it).date() == qd ) - return true; - } + do { + DateTimeList dts = datesForInterval( interval, recurrenceType() ); + int i = findGE( dts, start, 0 ); + if ( i >= 0 ) { + return dts[i] < end; + } + interval.increase( recurrenceType(), frequency() ); + } while ( interval.intervalDateTime( recurrenceType() ) < end ); + return false; } - -bool RecurrenceRule::recursAt( const TQDateTime &qd ) const +bool RecurrenceRule::recursAt( const TQDateTime &dt ) const { -// kdDebug(5800) << " RecurrenceRule::recursAt: " << qd << endl; - if ( doesFloat() ) return recursOn( qd.date() ); - if ( qd < startDt() ) return false; + if ( doesFloat() ) { + return recursOn( dt.date() ); + } + if ( dt < mDateStart ) { + return false; + } // Start date is only included if it really matches -// if ( qd == startDt() ) return true; - if ( mDuration >= 0 && qd > endDt() ) return false; + if ( mDuration >= 0 && dt > endDt() ) { + return false; + } + + if ( mTimedRepetition ) { + // It's a simple sub-daily recurrence with no constraints + return !( mDateStart.secsTo( dt ) % mTimedRepetition ); + } // The date must be in an appropriate interval (getNextValidDateInterval), // Plus it must match at least one of the constraints - bool match = dateMatchesRules( qd ); - if ( !match ) return false; + if ( !dateMatchesRules( dt ) ) { + return false; + } // if it recurs every interval, speed things up... -// if ( mFrequency == 1 && mBySetPos.isEmpty() && mByDays.isEmpty() ) return true; - Constraint interval( getNextValidDateInterval( qd, recurrenceType() ) ); +// if ( d->mFrequency == 1 && d->mBySetPos.isEmpty() && d->mByDays.isEmpty() ) return true; + Constraint interval( getNextValidDateInterval( dt, recurrenceType() ) ); // TODO_Recurrence: Does this work with BySetPos??? - if ( interval.matches( qd, recurrenceType() ) ) return true; - + if ( interval.matches( dt, recurrenceType() ) ) { + return true; + } return false; } - TimeList RecurrenceRule::recurTimesOn( const TQDate &date ) const { -// kdDebug(5800) << " RecurrenceRule::recurTimesOn: " << date << endl; TimeList lst; - if ( !recursOn( date ) ) return lst; - - if ( doesFloat() ) return lst; - - TQDateTime dt( date, TQTime( 0, 0, 0 ) ); - bool valid = dt.isValid() && ( dt.date() == date ); - while ( valid ) { - // TODO: Add a flag so that the date is never increased! - dt = getNextDate( dt ); - valid = dt.isValid() && ( dt.date() == date ); - if ( valid ) lst.append( dt.time() ); + if ( doesFloat() ) { + return lst; + } + TQDateTime start( date, TQTime( 0, 0, 0 ) ); + TQDateTime end = start.addDays( 1 ).addSecs( -1 ); + DateTimeList dts = timesInInterval( start, end ); // returns between start and end inclusive + for ( int i = 0, iend = dts.count(); i < iend; ++i ) { + lst += dts[i].time(); } return lst; } @@ -1150,6 +1270,104 @@ TQDateTime RecurrenceRule::getNextDate( const TQDateTime &preDate ) const return TQDateTime(); } +DateTimeList RecurrenceRule::timesInInterval( const TQDateTime &dtStart, + const TQDateTime &dtEnd ) const +{ + TQDateTime start = dtStart; + TQDateTime end = dtEnd; + DateTimeList result; + if ( end < mDateStart ) { + return result; // before start of recurrence + } + TQDateTime enddt = end; + if ( mDuration >= 0 ) { + TQDateTime endRecur = endDt(); + if ( endRecur.isValid() ) { + if ( start > endRecur ) { + return result; // beyond end of recurrence + } + if ( end > endRecur ) { + enddt = endRecur; // limit end time to end of recurrence rule + } + } + } + + if ( mTimedRepetition ) { + // It's a simple sub-daily recurrence with no constraints + int n = static_cast<int>( ( mDateStart.secsTo( start ) - 1 ) % mTimedRepetition ); + TQDateTime dt = start.addSecs( mTimedRepetition - n ); + if ( dt < enddt ) { + n = static_cast<int>( ( dt.secsTo( enddt ) - 1 ) / mTimedRepetition ) + 1; + // limit n by a sane value else we can "explode". + n = QMIN( n, LOOP_LIMIT ); + for ( int i = 0; i < n; dt = dt.addSecs( mTimedRepetition ), ++i ) { + result += dt; + } + } + return result; + } + + TQDateTime st = start; + bool done = false; + if ( mDuration > 0 ) { + if ( !mCached ) { + buildCache(); + } + if ( mCachedDateEnd.isValid() && start > mCachedDateEnd ) { + return result; // beyond end of recurrence + } + int i = findGE( mCachedDates, start, 0 ); + if ( i >= 0 ) { + int iend = findGT( mCachedDates, enddt, i ); + if ( iend < 0 ) { + iend = mCachedDates.count(); + } else { + done = true; + } + while ( i < iend ) { + result += mCachedDates[i++]; + } + } + if ( mCachedDateEnd.isValid() ) { + done = true; + } else if ( !result.isEmpty() ) { + result += TQDateTime(); // indicate that the returned list is incomplete + done = true; + } + if ( done ) { + return result; + } + // We don't have any result yet, but we reached the end of the incomplete cache + st = mCachedLastDate.addSecs( 1 ); + } + + Constraint interval( getNextValidDateInterval( st, recurrenceType() ) ); + int loop = 0; + do { + DateTimeList dts = datesForInterval( interval, recurrenceType() ); + int i = 0; + int iend = dts.count(); + if ( loop == 0 ) { + i = findGE( dts, st, 0 ); + if ( i < 0 ) { + i = iend; + } + } + int j = findGT( dts, enddt, i ); + if ( j >= 0 ) { + iend = j; + loop = LOOP_LIMIT; + } + while ( i < iend ) { + result += dts[i++]; + } + // Increase the interval. + interval.increase( recurrenceType(), frequency() ); + } while ( ++loop < LOOP_LIMIT && + interval.intervalDateTime( recurrenceType() ) < end ); + return result; +} + RecurrenceRule::Constraint RecurrenceRule::getPreviousValidDateInterval( const TQDateTime &preDate, PeriodType type ) const { // kdDebug(5800) << " (o) getPreviousValidDateInterval after " << preDate << ", type=" << type << endl; diff --git a/libkcal/recurrencerule.h b/libkcal/recurrencerule.h index 049d9c523..86b8ca8ea 100644 --- a/libkcal/recurrencerule.h +++ b/libkcal/recurrencerule.h @@ -50,6 +50,109 @@ Q_INLINE_TEMPLATES void qSortUnique( TQValueList<T> &lst ) } } +template <class T> +Q_INLINE_TEMPLATES int findGE( const TQValueList<T> &lst, const T &value, int start ) +{ + // Do a binary search to find the first item >= value + int st = start - 1; + int end = lst.count(); + while ( end - st > 1 ) { + int i = ( st + end ) / 2; + if ( value <= lst[i] ) { + end = i; + } else { + st = i; + } + } + ++st; + return ( st == int( lst.count() ) ) ? -1 : st; +} + +template <class T> +Q_INLINE_TEMPLATES int findGT( const TQValueList<T> &lst, const T &value, int start ) +{ + // Do a binary search to find the first item > value + int st = start - 1; + int end = lst.count(); + while ( end - st > 1 ) { + int i = ( st + end ) / 2; + if ( value < lst[i] ) { + end = i; + } else { + st = i; + } + } + ++st; + return ( st == int( lst.count() ) ) ? -1 : st; +} + +template <class T> +Q_INLINE_TEMPLATES int findLE( const TQValueList<T> &lst, const T &value, int start ) +{ + // Do a binary search to find the last item <= value + int st = start - 1; + int end = lst.count(); + while ( end - st > 1 ) { + int i = ( st + end ) / 2; + if ( value < lst[i] ) { + end = i; + } else { + st = i; + } + } + return ( end > start ) ? st : -1; +} + +template <class T> +Q_INLINE_TEMPLATES int findLT( const TQValueList<T> &lst, const T &value, int start ) +{ + // Do a binary search to find the last item < value + int st = start - 1; + int end = lst.count(); + while ( end - st > 1 ) { + int i = ( st + end ) / 2; + if ( value <= lst[i] ) { + end = i; + } else { + st = i; + } + } + return ( end > start ) ? st : -1; +} + +template <class T> +Q_INLINE_TEMPLATES int findSorted( const TQValueList<T> &lst, const T &value, int start ) +{ + // Do a binary search to find the item == value + int st = start - 1; + int end = lst.count(); + while ( end - st > 1 ) { + int i = ( st + end ) / 2; + if ( value < lst[i] ) { + end = i; + } else { + st = i; + } + } + return ( end > start && value == lst[st] ) ? st : -1; +} + +template <class T> +Q_INLINE_TEMPLATES int removeSorted( TQValueList<T> &lst, const T &value, int start ) +{ + int i = findSorted( lst, value, start ); + if ( i >= 0 ) { + lst.remove( lst.at( i ) ); + } + return i; +} + +template <class T> +Q_INLINE_TEMPLATES bool containsSorted( const TQValueList<T> &lst, const T &value ) +{ + return findSorted( lst, value, 0 ) >= 0; +} + namespace KCal { @@ -188,6 +291,18 @@ class LIBKCAL_EXPORT RecurrenceRule */ TimeList recurTimesOn( const TQDate &date ) const; + /** Returns a list of all the times at which the recurrence will occur + * between two specified times. + * + * There is a (large) maximum limit to the number of times returned. If due to + * this limit the list is incomplete, this is indicated by the last entry being + * set to an invalid KDateTime value. If you need further values, call the + * method again with a start time set to just after the last valid time returned. + * @param start inclusive start of interval + * @param end inclusive end of interval + * @return list of date/time values + */ + DateTimeList timesInInterval( const TQDateTime &start, const TQDateTime &end ) const; /** Returns the date and time of the next recurrence, after the specified date/time. * If the recurrence has no time, the next date after the specified date is returned. @@ -331,8 +446,12 @@ class LIBKCAL_EXPORT RecurrenceRule // Cache for duration mutable DateTimeList mCachedDates; - mutable bool mCached; mutable TQDateTime mCachedDateEnd; + mutable TQDateTime mCachedLastDate; // when mCachedDateEnd invalid, last date checked + mutable bool mCached; + + bool mNoByRules; // no BySeconds, ByMinutes, ... rules exist + uint mTimedRepetition; // repeats at a regular number of seconds interval, or 0 class Private; Private *d; diff --git a/libkcal/resourcecached.cpp b/libkcal/resourcecached.cpp index 1a4e8b19e..dacab7e9d 100644 --- a/libkcal/resourcecached.cpp +++ b/libkcal/resourcecached.cpp @@ -50,7 +50,7 @@ static bool m_editoropen = false; ResourceCached::ResourceCached( const KConfig* config ) : ResourceCalendar( config ), mCalendar( TQString::fromLatin1( "UTC" ) ), - mReloadPolicy( ReloadNever ), mReloadInterval( 10 ), + mReloadPolicy( ReloadNever ), mReloadInterval( 10 ), mReloadTimer( 0, "mReloadTimer" ), mReloaded( false ), mSavePolicy( SaveNever ), mSaveInterval( 10 ), mSaveTimer( 0, "mSaveTimer" ), mIdMapper( "kcal/uidmaps/" ) @@ -161,6 +161,12 @@ bool ResourceCached::addEvent(Event *event) return mCalendar.addEvent( event ); } +bool ResourceCached::addEvent(Event *event, const TQString &subresource ) +{ + Q_UNUSED( subresource ); // CalendarLocal does not support subresources + return mCalendar.addEvent( event ); +} + // probably not really efficient, but...it works for now. bool ResourceCached::deleteEvent( Event *event ) { @@ -205,6 +211,12 @@ bool ResourceCached::addTodo( Todo *todo ) return mCalendar.addTodo( todo ); } +bool ResourceCached::addTodo( Todo *todo, const TQString &subresource ) +{ + Q_UNUSED( subresource ); // CalendarLocal does not support subresources + return mCalendar.addTodo( todo ); +} + bool ResourceCached::deleteTodo( Todo *todo ) { return mCalendar.deleteTodo( todo ); @@ -231,11 +243,14 @@ Todo::List ResourceCached::rawTodosForDate( const TQDate &date ) return mCalendar.rawTodosForDate( date ); } - bool ResourceCached::addJournal( Journal *journal ) { - kdDebug(5800) << "Adding Journal on " << journal->dtStart().toString() << endl; + return mCalendar.addJournal( journal ); +} +bool ResourceCached::addJournal( Journal *journal, const TQString &subresource ) +{ + Q_UNUSED( subresource ); // CalendarLocal does not support subresources return mCalendar.addJournal( journal ); } diff --git a/libkcal/resourcecached.h b/libkcal/resourcecached.h index 60976698a..74fcf5d62 100644 --- a/libkcal/resourcecached.h +++ b/libkcal/resourcecached.h @@ -136,7 +136,9 @@ class KDE_EXPORT ResourceCached : public ResourceCalendar, /** Add event to calendar. */ - bool addEvent(Event *anEvent); + KDE_DEPRECATED bool addEvent( Event *event ); + bool addEvent( Event *event, const TQString &subresource ); + /** Deletes an event from this calendar. */ @@ -174,7 +176,9 @@ class KDE_EXPORT ResourceCached : public ResourceCalendar, /** Add a todo to the todolist. */ - bool addTodo( Todo *todo ); + KDE_DEPRECATED bool addTodo( Todo *todo ); + bool addTodo( Todo *todo, const TQString &subresource ); + /** Remove a todo from the todolist. */ @@ -195,15 +199,17 @@ class KDE_EXPORT ResourceCached : public ResourceCalendar, /** Add a Journal entry to calendar */ - virtual bool addJournal( Journal * ); + KDE_DEPRECATED bool addJournal( Journal *journal ); + bool addJournal( Journal *journal, const TQString &subresource ); + /** Remove a Journal from the calendar */ - virtual bool deleteJournal( Journal * ); + bool deleteJournal( Journal * ); /** Return Journal with given unique id. */ - virtual Journal *journal( const TQString &uid ); + Journal *journal( const TQString &uid ); /** Return list of all journals. */ diff --git a/libkcal/resourcecalendar.cpp b/libkcal/resourcecalendar.cpp index c008f0308..9f611e75e 100644 --- a/libkcal/resourcecalendar.cpp +++ b/libkcal/resourcecalendar.cpp @@ -33,18 +33,38 @@ using namespace KCal; ResourceCalendar::ResourceCalendar( const KConfig *config ) - : KRES::Resource( config ),mResolveConflict( false ) + : KRES::Resource( config ), mResolveConflict( false ) { + mException = 0; } ResourceCalendar::~ResourceCalendar() { + delete mException; +} + +void ResourceCalendar::clearException() +{ + delete mException; + mException = 0; +} + +void ResourceCalendar::setException( ErrorFormat *exception ) +{ + delete mException; + mException = exception; +} + +ErrorFormat *ResourceCalendar::exception() +{ + return mException; } void ResourceCalendar::setResolveConflict( bool b) { mResolveConflict = b; } + TQString ResourceCalendar::infoText() const { TQString txt; @@ -84,6 +104,12 @@ bool ResourceCalendar::addIncidence( Incidence *incidence ) return incidence->accept( v ); } +bool ResourceCalendar::addIncidence( Incidence *incidence, const TQString &subresource ) +{ + Incidence::AddSubResourceVisitor<ResourceCalendar> v( this, subresource ); + return incidence->accept( v ); +} + bool ResourceCalendar::deleteIncidence( Incidence *incidence ) { Incidence::DeleteVisitor<ResourceCalendar> v( this ); @@ -192,6 +218,8 @@ void ResourceCalendar::saveError( const TQString &err ) bool ResourceCalendar::setValue( const TQString &key, const TQString &value ) { + Q_UNUSED( key ); + Q_UNUSED( value ); return false; } @@ -201,5 +229,21 @@ TQString ResourceCalendar::subresourceType( const TQString &resource ) return TQString(); } +bool ResourceCalendar::subresourceWritable( const TQString &resource ) const +{ + if ( resource.isEmpty() ) { + return !readOnly(); + } else { + return false; + } +} + +void ResourceCalendar::beginAddingIncidences() +{ +} + +void ResourceCalendar::endAddingIncidences() +{ +} #include "resourcecalendar.moc" diff --git a/libkcal/resourcecalendar.h b/libkcal/resourcecalendar.h index 3fea84a1b..91f3d1aee 100644 --- a/libkcal/resourcecalendar.h +++ b/libkcal/resourcecalendar.h @@ -36,6 +36,7 @@ #include "event.h" #include "journal.h" #include "calendar.h" +#include "exceptions.h" #include <kresources/resource.h> #include <kresources/manager.h> @@ -55,11 +56,28 @@ class CalFormat; */ class LIBKCAL_EXPORT ResourceCalendar : public KRES::Resource { - Q_OBJECT + Q_OBJECT public: ResourceCalendar( const KConfig * ); virtual ~ResourceCalendar(); + /** + Clears the exception status. + */ + void clearException(); + + /** + Set exception for this object. This is used by the functions of this + class to report errors. + */ + void setException( ErrorFormat *error ); + + /** + Returns an exception, if there is any, containing information about the + last error that occurred. + */ + ErrorFormat *exception(); + void setResolveConflict( bool b); virtual void writeConfig( KConfig* config ); @@ -123,8 +141,14 @@ class LIBKCAL_EXPORT ResourceCalendar : public KRES::Resource /** Add incidence to resource. + @deprecated use addIncidence(Incidence *,const TQString &) instead. + */ + virtual KDE_DEPRECATED bool addIncidence( Incidence * ); + + /** + Add incidence to resource and subresource. */ - virtual bool addIncidence( Incidence * ); + virtual bool addIncidence( Incidence *, const TQString &subresource ); /** Delete incidence from resource. @@ -139,8 +163,10 @@ class LIBKCAL_EXPORT ResourceCalendar : public KRES::Resource /** Add event to resource. + @deprecated use addEvent(Event *,const TQString&) instead. */ - virtual bool addEvent( Event *event ) = 0; + virtual KDE_DEPRECATED bool addEvent( Event *event ) = 0; + virtual bool addEvent( Event *event, const TQString &subresource ) = 0; /** Delete event from this resource. @@ -241,8 +267,11 @@ class LIBKCAL_EXPORT ResourceCalendar : public KRES::Resource public: /** Add a todo to the todolist. + @deprecated use addTodo(Todo *,const TQString &) instead. */ - virtual bool addTodo( Todo *todo ) = 0; + virtual KDE_DEPRECATED bool addTodo( Todo *todo ) = 0; + virtual bool addTodo( Todo *todo, const TQString &subresource ) = 0; + /** Remove a todo from the todolist. */ @@ -265,8 +294,10 @@ class LIBKCAL_EXPORT ResourceCalendar : public KRES::Resource /** Add a Journal entry to the resource. + @deprecated use addJournal(Journal *,const TQString &) instead. */ - virtual bool addJournal( Journal * ) = 0; + virtual KDE_DEPRECATED bool addJournal( Journal * ) = 0; + virtual bool addJournal( Journal *journal, const TQString &subresource ) = 0; /** Remove a Journal entry from calendar. @@ -324,6 +355,11 @@ class LIBKCAL_EXPORT ResourceCalendar : public KRES::Resource virtual bool subresourceActive( const TQString& ) const { return true; } /** + Is this subresource writable or not? + */ + virtual bool subresourceWritable( const TQString& ) const; + + /** What is the label for this subresource? */ virtual const TQString labelForSubresource( const TQString& resource ) const @@ -360,6 +396,18 @@ class LIBKCAL_EXPORT ResourceCalendar : public KRES::Resource */ virtual TQString subresourceType( const TQString &resource ); + /** + * Called when we starting adding a batch of incidences. + * So we don't show the same warnings for each incidence. + */ + virtual void beginAddingIncidences(); + + /** + * Called when we finish adding a batch of incidences. + * @see beginAddingIncidences() + */ + virtual void endAddingIncidences(); + public slots: /** (De-)activate a subresource. @@ -402,6 +450,8 @@ class LIBKCAL_EXPORT ResourceCalendar : public KRES::Resource bool mReceivedLoadError; bool mReceivedSaveError; + ErrorFormat *mException; + class Private; Private *d; }; diff --git a/libkcal/resourcelocaldir.cpp b/libkcal/resourcelocaldir.cpp index 1d5c9caa8..f1e00f327 100644 --- a/libkcal/resourcelocaldir.cpp +++ b/libkcal/resourcelocaldir.cpp @@ -128,7 +128,7 @@ bool ResourceLocalDir::doLoad() TQString dirName = mURL.path(); if ( !( KStandardDirs::exists( dirName ) || KStandardDirs::exists( dirName + "/") ) ) { - kdDebug(5800) << "ResourceLocalDir::load(): Directory '" << dirName + kdDebug(5800) << "ResourceLocalDir::load(): Directory '" << dirName << "' doesn't exist yet. Creating it..." << endl; // Create the directory. Use 0775 to allow group-writable if the umask // allows it (permissions will be 0775 & ~umask). This is desired e.g. for @@ -139,7 +139,7 @@ bool ResourceLocalDir::doLoad() // The directory exists. Now try to open (the files in) it. kdDebug(5800) << "ResourceLocalDir::load(): '" << dirName << "'" << endl; TQFileInfo dirInfo( dirName ); - if ( !( dirInfo.isDir() && dirInfo.isReadable() && + if ( !( dirInfo.isDir() && dirInfo.isReadable() && ( dirInfo.isWritable() || readOnly() ) ) ) return false; @@ -193,6 +193,11 @@ bool ResourceLocalDir::doSave() bool ResourceLocalDir::doSave( Incidence *incidence ) { + if ( mDeletedIncidences.contains( incidence ) ) { + mDeletedIncidences.remove( incidence ); + return true; + } + mDirWatch.stopScan(); // do prohibit the dirty() signal and a following reload() TQString fileName = mURL.path() + "/" + incidence->uid(); @@ -231,28 +236,46 @@ void ResourceLocalDir::reload( const TQString &file ) bool ResourceLocalDir::deleteEvent(Event *event) { kdDebug(5800) << "ResourceLocalDir::deleteEvent" << endl; - if ( deleteIncidenceFile(event) ) - return( mCalendar.deleteEvent( event ) ); - else - return( false ); + if ( deleteIncidenceFile(event) ) { + if ( mCalendar.deleteEvent( event ) ) { + mDeletedIncidences.append( event ); + return true; + } else { + return false; + } + } else { + return false; + } } bool ResourceLocalDir::deleteTodo(Todo *todo) { - if ( deleteIncidenceFile(todo) ) - return( mCalendar.deleteTodo( todo ) ); - else - return( false ); + if ( deleteIncidenceFile(todo) ) { + if ( mCalendar.deleteTodo( todo ) ) { + mDeletedIncidences.append( todo ); + return true; + } else { + return false; + } + } else { + return false; + } } bool ResourceLocalDir::deleteJournal( Journal *journal ) { - if ( deleteIncidenceFile( journal ) ) - return( mCalendar.deleteJournal( journal ) ); - else - return( false ); + if ( deleteIncidenceFile( journal ) ) { + if ( mCalendar.deleteJournal( journal ) ) { + mDeletedIncidences.append( journal ); + return true; + } else { + return false; + } + } else { + return false; + } } diff --git a/libkcal/resourcelocaldir.h b/libkcal/resourcelocaldir.h index e6c5526a9..e1316f212 100644 --- a/libkcal/resourcelocaldir.h +++ b/libkcal/resourcelocaldir.h @@ -94,6 +94,7 @@ class LIBKCAL_EXPORT ResourceLocalDir : public ResourceCached KABC::Lock *mLock; + TQPtrList<Incidence>mDeletedIncidences; class Private; Private *d; }; diff --git a/libkcal/resourcelocaldirconfig.cpp b/libkcal/resourcelocaldirconfig.cpp index 2c8d89838..df2412b3d 100644 --- a/libkcal/resourcelocaldirconfig.cpp +++ b/libkcal/resourcelocaldirconfig.cpp @@ -26,6 +26,7 @@ #include <klocale.h> #include <kdebug.h> +#include <kmessagebox.h> #include <kstandarddirs.h> #include "vcaldrag.h" @@ -41,7 +42,7 @@ using namespace KCal; ResourceLocalDirConfig::ResourceLocalDirConfig( TQWidget* parent, const char* name ) : KRES::ConfigWidget( parent, name ) { - resize( 245, 115 ); + resize( 245, 115 ); TQGridLayout *mainLayout = new TQGridLayout( this, 2, 2 ); TQLabel *label = new TQLabel( i18n( "Location:" ), this ); @@ -65,6 +66,14 @@ void ResourceLocalDirConfig::saveSettings( KRES::Resource *resource ) ResourceLocalDir* res = static_cast<ResourceLocalDir*>( resource ); if (res) { res->mURL = mURL->url(); + if ( mURL->url().isEmpty() && !resource->readOnly() ) { + KMessageBox::information( + this, + i18n( "No location specified. The calendar will be read-only." ), + TQString(), + "ResourceLocalDirUrl" ); + resource->setReadOnly( true ); + } } else kdDebug(5700) << "ERROR: ResourceLocalDirConfig::saveSettings(): no ResourceLocalDir, cast failed" << endl; } diff --git a/libkcal/scheduler.cpp b/libkcal/scheduler.cpp index 802c977f6..6e3cf3850 100644 --- a/libkcal/scheduler.cpp +++ b/libkcal/scheduler.cpp @@ -25,12 +25,15 @@ #include <kmessagebox.h> #include <kstandarddirs.h> +#include "calhelper.h" #include "event.h" #include "todo.h" #include "freebusy.h" #include "icalformat.h" #include "calendar.h" +#include "calendarresources.h" #include "freebusycache.h" +#include "assignmentvisitor.h" #include "scheduler.h" @@ -94,7 +97,10 @@ FreeBusyCache *Scheduler::freeBusyCache() const return d->mFreeBusyCache; } -bool Scheduler::acceptTransaction(IncidenceBase *incidence,Method method,ScheduleMessage::Status status) +bool Scheduler::acceptTransaction( IncidenceBase *incidence, + Method method, + ScheduleMessage::Status status, + const TQString &attendee ) { kdDebug(5800) << "Scheduler::acceptTransaction, method=" << methodName( method ) << endl; @@ -103,11 +109,11 @@ bool Scheduler::acceptTransaction(IncidenceBase *incidence,Method method,Schedul case Publish: return acceptPublish(incidence, status, method); case Request: - return acceptRequest(incidence, status); + return acceptRequest( incidence, status, attendee ); case Add: return acceptAdd(incidence, status); case Cancel: - return acceptCancel(incidence, status); + return acceptCancel(incidence, status, attendee ); case Declinecounter: return acceptDeclineCounter(incidence, status); case Reply: @@ -185,24 +191,28 @@ bool Scheduler::acceptPublish( IncidenceBase *newIncBase, bool res = false; kdDebug(5800) << "Scheduler::acceptPublish, status=" - << ScheduleMessage::statusName( status ) << endl; + << ScheduleMessage::statusName( status ) << endl; Incidence *newInc = static_cast<Incidence *>( newIncBase ); Incidence *calInc = mCalendar->incidence( newIncBase->uid() ); switch ( status ) { case ScheduleMessage::Unknown: case ScheduleMessage::PublishNew: case ScheduleMessage::PublishUpdate: - res = true; - if ( calInc ) { + if ( calInc && newInc ) { if ( (newInc->revision() > calInc->revision()) || (newInc->revision() == calInc->revision() && newInc->lastModified() > calInc->lastModified() ) ) { - mCalendar->deleteIncidence( calInc ); - } else - res = false; + AssignmentVisitor visitor; + const TQString oldUid = calInc->uid(); + if ( !visitor.assign( calInc, newInc ) ) { + kdError(5800) << "assigning different incidence types" << endl; + } else { + calInc->setUid( oldUid ); + calInc->setSchedulingID( newInc->uid() ); + res = true; + } + } } - if ( res ) - mCalendar->addIncidence( newInc ); break; case ScheduleMessage::Obsolete: res = true; @@ -214,36 +224,169 @@ bool Scheduler::acceptPublish( IncidenceBase *newIncBase, return res; } -bool Scheduler::acceptRequest(IncidenceBase *newIncBase, ScheduleMessage::Status /* status */) +bool Scheduler::acceptRequest( IncidenceBase *incidence, + ScheduleMessage::Status status, + const TQString &attendee ) { - if (newIncBase->type()=="FreeBusy") { + Incidence *inc = static_cast<Incidence *>(incidence); + if ( !inc ) + return false; + if (inc->type()=="FreeBusy") { // reply to this request is handled in korganizer's incomingdialog return true; } - Incidence *newInc = dynamic_cast<Incidence *>( newIncBase ); - if ( newInc ) { - bool res = true; - Incidence *exInc = mCalendar->incidenceFromSchedulingID( newIncBase->uid() ); - if ( exInc ) { - res = false; - if ( (newInc->revision() > exInc->revision()) || - (newInc->revision() == exInc->revision() && - newInc->lastModified()>exInc->lastModified()) ) { - mCalendar->deleteIncidence( exInc ); - res = true; + + const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() ); + kdDebug(5800) << "Scheduler::acceptRequest status=" << ScheduleMessage::statusName( status ) << ": found " << existingIncidences.count() << " incidences with schedulingID " << inc->schedulingID() << endl; + Incidence::List::ConstIterator incit = existingIncidences.begin(); + for ( ; incit != existingIncidences.end() ; ++incit ) { + Incidence* const i = *incit; + kdDebug(5800) << "Considering this found event (" + << ( i->isReadOnly() ? "readonly" : "readwrite" ) + << ") :" << mFormat->toString( i ) << endl; + // If it's readonly, we can't possible update it. + if ( i->isReadOnly() ) + continue; + if ( i->revision() <= inc->revision() ) { + // The new incidence might be an update for the found one + bool isUpdate = true; + // Code for new invitations: + // If you think we could check the value of "status" to be RequestNew: we can't. + // It comes from a similar check inside libical, where the event is compared to + // other events in the calendar. But if we have another version of the event around + // (e.g. shared folder for a group), the status could be RequestNew, Obsolete or Updated. + kdDebug(5800) << "looking in " << i->uid() << "'s attendees" << endl; + // This is supposed to be a new request, not an update - however we want to update + // the existing one to handle the "clicking more than once on the invitation" case. + // So check the attendee status of the attendee. + const KCal::Attendee::List attendees = i->attendees(); + KCal::Attendee::List::ConstIterator ait; + for ( ait = attendees.begin(); ait != attendees.end(); ++ait ) { + if( (*ait)->email() == attendee && (*ait)->status() == Attendee::NeedsAction ) { + // This incidence wasn't created by me - it's probably in a shared folder + // and meant for someone else, ignore it. + kdDebug(5800) << "ignoring " << i->uid() << " since I'm still NeedsAction there" << endl; + isUpdate = false; + break; + } + } + if ( isUpdate ) { + if ( i->revision() == inc->revision() && + i->lastModified() > inc->lastModified() ) { + // This isn't an update - the found incidence was modified more recently + kdDebug(5800) << "This isn't an update - the found incidence was modified more recently" << endl; + deleteTransaction(incidence); + return false; + } + kdDebug(5800) << "replacing existing incidence " << i->uid() << endl; + bool res = true; + AssignmentVisitor visitor; + const TQString oldUid = i->uid(); + if ( !visitor.assign( i, inc ) ) { + kdError(5800) << "assigning different incidence types" << endl; + res = false; + } else { + i->setUid( oldUid ); + i->setSchedulingID( inc->uid() ); + } + deleteTransaction( incidence ); + return res; } + } else { + // This isn't an update - the found incidence has a bigger revision number + kdDebug(5800) << "This isn't an update - the found incidence has a bigger revision number" << endl; + deleteTransaction(incidence); + return false; + } + } + + // Move the uid to be the schedulingID and make a unique UID + inc->setSchedulingID( inc->uid() ); + inc->setUid( CalFormat::createUniqueId() ); + // notify the user in case this is an update and we didn't find the to-be-updated incidence + if ( existingIncidences.count() == 0 && inc->revision() > 0 ) { + KMessageBox::information( + 0, + i18n( "<qt>" + "You accepted an invitation update, but an earlier version of the " + "item could not be found in your calendar.<p>" + "This may have occurred because:<ul>" + "<li>the organizer did not include you in the original invitation</li>" + "<li>you did not accept the original invitation yet</li>" + "<li>you deleted the original invitation from your calendar</li>" + "<li>you no longer have access to the calendar containing the invitation</li>" + "</ul>" + "This is not a problem, but we thought you should know.</qt>" ), + i18n( "Cannot find invitation to be updated" ), "AcceptCantFindIncidence" ); + } + kdDebug(5800) << "Storing new incidence with scheduling uid=" << inc->schedulingID() + << " and uid=" << inc->uid() << endl; + + CalendarResources *stdcal = dynamic_cast<CalendarResources *>( mCalendar ); + if( stdcal && !stdcal->hasCalendarResources() ) { + KMessageBox::sorry( + 0, + i18n( "No calendars found, unable to save the invitation." ) ); + return false; + } + + // FIXME: This is a nasty hack, since we need to set a parent for the + // resource selection dialog. However, we don't have any UI methods + // in the calendar, only in the CalendarResources::DestinationPolicy + // So we need to type-cast it and extract it from the CalendarResources + TQWidget *tmpparent = 0; + if ( stdcal ) { + tmpparent = stdcal->dialogParentWidget(); + stdcal->setDialogParentWidget( 0 ); + } + +TryAgain: + bool success = false; + if ( stdcal ) { + success = stdcal->addIncidence( inc ); + } else { + success = mCalendar->addIncidence( inc ); + } + + if ( !success ) { + ErrorFormat *e = stdcal ? stdcal->exception() : 0; + + if ( e && e->errorCode() == KCal::ErrorFormat::UserCancel && + KMessageBox::warningYesNo( + 0, + i18n( "You canceled the save operation. Therefore, the appointment will not be " + "stored in your calendar even though you accepted the invitation. " + "Are you certain you want to discard this invitation? " ), + i18n( "Discard this invitation?" ), + i18n( "Discard" ), i18n( "Go Back to Folder Selection" ) ) == KMessageBox::Yes ) { + KMessageBox::information( + 0, + i18n( "The invitation \"%1\" was not saved to your calendar " + "but you are still listed as an attendee for that appointment.\n" + "If you mistakenly accepted the invitation or do not plan to attend, please notify " + "the organizer %2 and ask them to remove you from the attendee list."). + arg( inc->summary(), inc->organizer().fullName() ) ); + deleteTransaction( incidence ); + return true; + } else { + goto TryAgain; } - if ( res ) { - // Move the uid to be the schedulingID and make a unique UID - newInc->setSchedulingID( newInc->uid() ); - newInc->setUid( CalFormat::createUniqueId() ); - mCalendar->addIncidence(newInc); + // We can have a failure if the user pressed [cancel] in the resource + // selectdialog, so check the exception. + if ( !e || + ( e && ( e->errorCode() != KCal::ErrorFormat::UserCancel && + e->errorCode() != KCal::ErrorFormat::NoWritableFound ) ) ) { + TQString errMessage = i18n( "Unable to save %1 \"%2\"." ). + arg( i18n( inc->type() ) ). + arg( inc->summary() ); + KMessageBox::sorry( 0, errMessage ); } - deleteTransaction( newIncBase ); - return res; + return false; } - return false; + + deleteTransaction( incidence ); + return true; } bool Scheduler::acceptAdd(IncidenceBase *incidence,ScheduleMessage::Status /* status */) @@ -252,22 +395,131 @@ bool Scheduler::acceptAdd(IncidenceBase *incidence,ScheduleMessage::Status /* st return false; } -bool Scheduler::acceptCancel(IncidenceBase *incidence,ScheduleMessage::Status /* status */) +bool Scheduler::acceptCancel( IncidenceBase *incidence, + ScheduleMessage::Status status, + const TQString &attendee ) { + Incidence *inc = static_cast<Incidence *>( incidence ); + if ( !inc ) { + return false; + } + + if ( inc->type() == "FreeBusy" ) { + // reply to this request is handled in korganizer's incomingdialog + return true; + } + + const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() ); + kdDebug(5800) << "Scheduler::acceptCancel=" + << ScheduleMessage::statusName( status ) + << ": found " << existingIncidences.count() + << " incidences with schedulingID " << inc->schedulingID() + << endl; + + // Remove existing incidences that aren't stored in my calendar as we + // will never attempt to remove those -- even if we have write-access. + Incidence::List myExistingIncidences; + Incidence::List::ConstIterator incit = existingIncidences.begin(); + for ( ; incit != existingIncidences.end() ; ++incit ) { + Incidence *i = *incit; + if ( CalHelper::isMyCalendarIncidence( mCalendar, i ) ) { + myExistingIncidences.append( i ); + } + } + bool ret = false; + incit = myExistingIncidences.begin(); + for ( ; incit != myExistingIncidences.end() ; ++incit ) { + Incidence *i = *incit; + kdDebug(5800) << "Considering this found event (" + << ( i->isReadOnly() ? "readonly" : "readwrite" ) + << ") :" << mFormat->toString( i ) << endl; + + // If it's readonly, we can't possible remove it. + if ( i->isReadOnly() ) { + continue; + } + + // Code for new invitations: + // We cannot check the value of "status" to be RequestNew because + // "status" comes from a similar check inside libical, where the event + // is compared to other events in the calendar. But if we have another + // version of the event around (e.g. shared folder for a group), the + // status could be RequestNew, Obsolete or Updated. + kdDebug(5800) << "looking in " << i->uid() << "'s attendees" << endl; + + // This is supposed to be a new request, not an update - however we want + // to update the existing one to handle the "clicking more than once + // on the invitation" case. So check the attendee status of the attendee. + bool isMine = true; + const KCal::Attendee::List attendees = i->attendees(); + KCal::Attendee::List::ConstIterator ait; + for ( ait = attendees.begin(); ait != attendees.end(); ++ait ) { + if ( (*ait)->email() == attendee && + (*ait)->status() == Attendee::NeedsAction ) { + // This incidence wasn't created by me - it's probably in a shared + // folder and meant for someone else, ignore it. + kdDebug(5800) << "ignoring " << i->uid() + << " since I'm still NeedsAction there" << endl; + isMine = false; + break; + } + } + + if ( isMine ) { + kdDebug(5800) << "removing existing incidence " << i->uid() << endl; + if ( i->type() == "Event" ) { + Event *event = mCalendar->event( i->uid() ); + ret = ( event && mCalendar->deleteEvent( event ) ); + } else if ( i->type() == "Todo" ) { + Todo *todo = mCalendar->todo( i->uid() ); + ret = ( todo && mCalendar->deleteTodo( todo ) ); + } + deleteTransaction( incidence ); + return ret; + } + } + + // in case we didn't find the to-be-removed incidence + if ( myExistingIncidences.count() > 0 && inc->revision() > 0 ) { + KMessageBox::information( + 0, + i18n( "The event or task could not be removed from your calendar. " + "Maybe it has already been deleted or is not owned by you. " + "Or it might belong to a read-only or disabled calendar." ) ); + } + deleteTransaction( incidence ); + return ret; +} + +bool Scheduler::acceptCancel(IncidenceBase *incidence,ScheduleMessage::Status /* status */) +{ const IncidenceBase *toDelete = mCalendar->incidenceFromSchedulingID( incidence->uid() ); + + bool ret = true; if ( toDelete ) { - Event *even = mCalendar->event(toDelete->uid()); - if (even) { - mCalendar->deleteEvent(even); - ret = true; - } else { - Todo *todo = mCalendar->todo(toDelete->uid()); - if (todo) { - mCalendar->deleteTodo(todo); - ret = true; - } + if ( toDelete->type() == "Event" ) { + Event *event = mCalendar->event( toDelete->uid() ); + ret = ( event && mCalendar->deleteEvent( event ) ); + } else if ( toDelete->type() == "Todo" ) { + Todo *todo = mCalendar->todo( toDelete->uid() ); + ret = ( todo && mCalendar->deleteTodo( todo ) ); } + } else { + // only complain if we failed to determine the toDelete incidence + // on non-initial request. + Incidence *inc = static_cast<Incidence *>( incidence ); + if ( inc->revision() > 0 ) { + ret = false; + } + } + + if ( !ret ) { + KMessageBox::information( + 0, + i18n( "The event or task to be canceled could not be removed from your calendar. " + "Maybe it has already been deleted or is not owned by you. " + "Or it might belong to a read-only or disabled calendar." ) ); } deleteTransaction(incidence); return ret; @@ -370,13 +622,25 @@ bool Scheduler::acceptReply(IncidenceBase *incidence,ScheduleMessage::Status /* // send update about new participants if ( attendeeAdded ) { + bool sendMail = false; + if ( ev || to ) { + if ( KMessageBox::questionYesNo( 0, i18n( "An attendee was added to the incidence. " + "Do you want to email the attendees an update message?" ), + i18n( "Attendee Added" ), i18n( "Send Messages" ), + i18n( "Do Not Send" ) ) == KMessageBox::Yes ) { + sendMail = true; + } + } + if ( ev ) { ev->setRevision( ev->revision() + 1 ); - performTransaction( ev, Scheduler::Request ); + if ( sendMail ) + performTransaction( ev, Scheduler::Request ); } if ( to ) { - to->setRevision( ev->revision() + 1 ); - performTransaction( to, Scheduler::Request ); + to->setRevision( to->revision() + 1 ); + if ( sendMail ) + performTransaction( to, Scheduler::Request ); } } diff --git a/libkcal/scheduler.h b/libkcal/scheduler.h index 17e19ec47..bf22c9c81 100644 --- a/libkcal/scheduler.h +++ b/libkcal/scheduler.h @@ -49,14 +49,14 @@ class ScheduleMessage */ enum Status { PublishNew, PublishUpdate, Obsolete, RequestNew, RequestUpdate, Unknown }; - + /** Create a scheduling message with method as defined in Scheduler::Method and a status. */ ScheduleMessage( IncidenceBase *, int method, Status status ); ~ScheduleMessage() {}; - + /** Return event associated with this message. */ @@ -102,13 +102,13 @@ class LIBKCAL_EXPORT Scheduler */ enum Method { Publish,Request,Refresh,Cancel,Add,Reply,Counter, Declinecounter,NoMethod }; - + /** Create scheduler for calendar specified as argument. */ Scheduler( Calendar *calendar ); virtual ~Scheduler(); - + /** iTIP publish action */ @@ -121,8 +121,8 @@ class LIBKCAL_EXPORT Scheduler virtual bool performTransaction( IncidenceBase *incidence, Method method ) = 0; /** - Perform iTIP transaction on incidence to specified recipient(s). The - method is specified as the method argumanet and can be any valid iTIP + Perform iTIP transaction on incidence to specified recipient(s). The + method is specified as the method argumanet and can be any valid iTIP method. */ virtual bool performTransaction( IncidenceBase *incidence, Method method, @@ -136,10 +136,12 @@ class LIBKCAL_EXPORT Scheduler Accept transaction. The incidence argument specifies the iCal compoennt on which the transaction acts. The status is the result of processing a iTIP message with the current calendar and specifies the action to be - taken for this incidence. + taken for this incidence. The attendee is the email address of the person + on who's behalf this transaction is to be performed. */ bool acceptTransaction( IncidenceBase *, Method method, - ScheduleMessage::Status status ); + ScheduleMessage::Status status, + const TQString& attendee = TQString::null ); /** Return a machine-readable name for a iTIP method. @@ -151,7 +153,7 @@ class LIBKCAL_EXPORT Scheduler static TQString translatedMethodName( Method ); virtual bool deleteTransaction( IncidenceBase *incidence ); - + /** Returns the directory where the free-busy information is stored. */ @@ -169,9 +171,12 @@ class LIBKCAL_EXPORT Scheduler protected: bool acceptPublish( IncidenceBase *, ScheduleMessage::Status status, Method method ); - bool acceptRequest( IncidenceBase *, ScheduleMessage::Status status ); + bool acceptRequest( IncidenceBase *, ScheduleMessage::Status status, + const TQString & attendee ); bool acceptAdd( IncidenceBase *, ScheduleMessage::Status status ); - bool acceptCancel( IncidenceBase *, ScheduleMessage::Status status ); + KDE_DEPRECATED bool acceptCancel( IncidenceBase *, ScheduleMessage::Status status ); + bool acceptCancel( IncidenceBase *, ScheduleMessage::Status status, + const TQString & attendee ); bool acceptDeclineCounter( IncidenceBase *, ScheduleMessage::Status status ); bool acceptReply( IncidenceBase *, ScheduleMessage::Status status, diff --git a/libkcal/tests/Makefile.am b/libkcal/tests/Makefile.am index 6e7096a12..1a09143ef 100644 --- a/libkcal/tests/Makefile.am +++ b/libkcal/tests/Makefile.am @@ -18,7 +18,8 @@ check_PROGRAMS = testtostring \ testrecurson \ testrecurrencetype \ testvcalexport \ - testfb + testfb \ + testcalselectdialog METASOURCES = AUTO @@ -76,6 +77,10 @@ testfb_SOURCES = testfb.cpp testfb_LDFLAGS = $(all_libraries) $(KDE_RPATH) testfb_LDADD = ../libkcal.la +testcalselectdialog_SOURCES = testcalselectdialog.cpp +testcalselectdialog_LDFLAGS = $(all_libraries) $(KDE_RPATH) +testcalselectdialog_LDADD = ../libkcal.la + TESTFILES = test1.ics test2.ics test3.ics test4.ics test5.ics test_Mozilla.ics check-local: readandwrite testrecurrence testrecurprevious testrecurson testvcalexport diff --git a/libkcal/tests/data/RecurrenceRule/ConnectDaily/ConnectDaily11.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/ConnectDaily/ConnectDaily11.ics.recurson.ref index 1698d7d26..783c984af 100644 --- a/libkcal/tests/data/RecurrenceRule/ConnectDaily/ConnectDaily11.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/ConnectDaily/ConnectDaily11.ics.recurson.ref @@ -1,4 +1,3 @@ -2005-04-01 2005-05-03 2005-05-05 2005-06-07 diff --git a/libkcal/tests/data/RecurrenceRule/KAlarm_3.4/KAlarm_TestCase06.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/KAlarm_3.4/KAlarm_TestCase06.ics.recurson.ref index 2562eae7c..8b1378917 100644 --- a/libkcal/tests/data/RecurrenceRule/KAlarm_3.4/KAlarm_TestCase06.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/KAlarm_3.4/KAlarm_TestCase06.ics.recurson.ref @@ -1,2 +1 @@ -2005-05-31 diff --git a/libkcal/tests/data/RecurrenceRule/LibICal/LibICal_TestCase02.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/LibICal/LibICal_TestCase02.ics.recurson.ref index eb9f81585..a17df247a 100644 --- a/libkcal/tests/data/RecurrenceRule/LibICal/LibICal_TestCase02.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/LibICal/LibICal_TestCase02.ics.recurson.ref @@ -1,4 +1,3 @@ -2002-04-02 2002-04-04 2002-04-11 2002-04-18 diff --git a/libkcal/tests/data/RecurrenceRule/LibICal/LibICal_TestCase24.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/LibICal/LibICal_TestCase24.ics.recurson.ref index 7abf1e5ad..13edfa434 100644 --- a/libkcal/tests/data/RecurrenceRule/LibICal/LibICal_TestCase24.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/LibICal/LibICal_TestCase24.ics.recurson.ref @@ -1,4 +1,3 @@ -1997-09-02 1997-09-03 1997-09-05 1997-09-15 diff --git a/libkcal/tests/data/RecurrenceRule/LibICal/LibICal_TestCase42.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/LibICal/LibICal_TestCase42.ics.recurson.ref index 887df0de5..e1d367ade 100644 --- a/libkcal/tests/data/RecurrenceRule/LibICal/LibICal_TestCase42.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/LibICal/LibICal_TestCase42.ics.recurson.ref @@ -1,4 +1,3 @@ -1997-09-02 1998-02-13 1998-03-13 1998-11-13 diff --git a/libkcal/tests/data/RecurrenceRule/RFC2445/RFC2445_RRULETestCase12.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/RFC2445/RFC2445_RRULETestCase12.ics.recurson.ref index 7abf1e5ad..24ad0551f 100644 --- a/libkcal/tests/data/RecurrenceRule/RFC2445/RFC2445_RRULETestCase12.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/RFC2445/RFC2445_RRULETestCase12.ics.recurson.ref @@ -1,4 +1,3 @@ -1997-09-02 1997-09-03 1997-09-05 1997-09-15 @@ -23,4 +22,3 @@ 1997-12-10 1997-12-12 1997-12-22 - diff --git a/libkcal/tests/data/RecurrenceRule/UntilInUTC/Until_TestCase02.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/UntilInUTC/Until_TestCase02.ics.recurson.ref index 2f3a7e16f..e69de29bb 100644 --- a/libkcal/tests/data/RecurrenceRule/UntilInUTC/Until_TestCase02.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/UntilInUTC/Until_TestCase02.ics.recurson.ref @@ -1 +0,0 @@ -1997-12-15 diff --git a/libkcal/tests/data/RecurrenceRule/UntilInUTC/Until_TestCase04.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/UntilInUTC/Until_TestCase04.ics.recurson.ref index 6fca0d2fd..f57f89d0e 100644 --- a/libkcal/tests/data/RecurrenceRule/UntilInUTC/Until_TestCase04.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/UntilInUTC/Until_TestCase04.ics.recurson.ref @@ -1,2 +1 @@ 1997-09-02 - diff --git a/libkcal/tests/data/RecurrenceRule/unsorted/lastworkday.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/unsorted/lastworkday.ics.recurson.ref index 05df6b377..c55b5583d 100644 --- a/libkcal/tests/data/RecurrenceRule/unsorted/lastworkday.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/unsorted/lastworkday.ics.recurson.ref @@ -1,4 +1,3 @@ -2005-05-12 2005-05-31 2005-06-30 2005-07-29 diff --git a/libkcal/tests/data/RecurrenceRule/unsorted/monthly.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/unsorted/monthly.ics.recurson.ref index bbf1358c2..4c2c61aae 100644 --- a/libkcal/tests/data/RecurrenceRule/unsorted/monthly.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/unsorted/monthly.ics.recurson.ref @@ -1,4 +1,3 @@ -2005-05-12 2005-05-18 2005-05-24 2005-06-15 diff --git a/libkcal/tests/data/RecurrenceRule/unsorted/rdate.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/unsorted/rdate.ics.recurson.ref index 4082c9c20..70712a5e1 100644 --- a/libkcal/tests/data/RecurrenceRule/unsorted/rdate.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/unsorted/rdate.ics.recurson.ref @@ -1,4 +1,3 @@ -2005-05-12 2005-05-14 2005-05-16 2005-05-17 diff --git a/libkcal/tests/data/RecurrenceRule/unsorted/test1.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/unsorted/test1.ics.recurson.ref index 71cfa3b67..5bd295fc9 100644 --- a/libkcal/tests/data/RecurrenceRule/unsorted/test1.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/unsorted/test1.ics.recurson.ref @@ -1,4 +1,3 @@ -2005-05-12 2007-01-07 2007-01-14 2007-01-21 diff --git a/libkcal/tests/data/RecurrenceRule/unsorted/weekly.ics.recurson.ref b/libkcal/tests/data/RecurrenceRule/unsorted/weekly.ics.recurson.ref index 59d81cdc1..1b1e01918 100644 --- a/libkcal/tests/data/RecurrenceRule/unsorted/weekly.ics.recurson.ref +++ b/libkcal/tests/data/RecurrenceRule/unsorted/weekly.ics.recurson.ref @@ -1,4 +1,3 @@ -2005-05-12 2005-05-23 2005-05-25 2005-06-06 @@ -293,3 +292,210 @@ 2010-12-15 2010-12-27 2010-12-29 +2011-01-10 +2011-01-12 +2011-01-24 +2011-01-26 +2011-02-07 +2011-02-09 +2011-02-21 +2011-02-23 +2011-03-07 +2011-03-09 +2011-03-21 +2011-03-23 +2011-04-04 +2011-04-06 +2011-04-18 +2011-04-20 +2011-05-02 +2011-05-04 +2011-05-16 +2011-05-18 +2011-05-30 +2011-06-01 +2011-06-13 +2011-06-15 +2011-06-27 +2011-06-29 +2011-07-11 +2011-07-13 +2011-07-25 +2011-07-27 +2011-08-08 +2011-08-10 +2011-08-22 +2011-08-24 +2011-09-05 +2011-09-07 +2011-09-19 +2011-09-21 +2011-10-03 +2011-10-05 +2011-10-17 +2011-10-19 +2011-10-31 +2011-11-02 +2011-11-14 +2011-11-16 +2011-11-28 +2011-11-30 +2011-12-12 +2011-12-14 +2011-12-26 +2011-12-28 +2012-01-09 +2012-01-11 +2012-01-23 +2012-01-25 +2012-02-06 +2012-02-08 +2012-02-20 +2012-02-22 +2012-03-05 +2012-03-07 +2012-03-19 +2012-03-21 +2012-04-02 +2012-04-04 +2012-04-16 +2012-04-18 +2012-04-30 +2012-05-02 +2012-05-14 +2012-05-16 +2012-05-28 +2012-05-30 +2012-06-11 +2012-06-13 +2012-06-25 +2012-06-27 +2012-07-09 +2012-07-11 +2012-07-23 +2012-07-25 +2012-08-06 +2012-08-08 +2012-08-20 +2012-08-22 +2012-09-03 +2012-09-05 +2012-09-17 +2012-09-19 +2012-10-01 +2012-10-03 +2012-10-15 +2012-10-17 +2012-10-29 +2012-10-31 +2012-11-12 +2012-11-14 +2012-11-26 +2012-11-28 +2012-12-10 +2012-12-12 +2012-12-24 +2012-12-26 +2013-01-07 +2013-01-09 +2013-01-21 +2013-01-23 +2013-02-04 +2013-02-06 +2013-02-18 +2013-02-20 +2013-03-04 +2013-03-06 +2013-03-18 +2013-03-20 +2013-04-01 +2013-04-03 +2013-04-15 +2013-04-17 +2013-04-29 +2013-05-01 +2013-05-13 +2013-05-15 +2013-05-27 +2013-05-29 +2013-06-10 +2013-06-12 +2013-06-24 +2013-06-26 +2013-07-08 +2013-07-10 +2013-07-22 +2013-07-24 +2013-08-05 +2013-08-07 +2013-08-19 +2013-08-21 +2013-09-02 +2013-09-04 +2013-09-16 +2013-09-18 +2013-09-30 +2013-10-02 +2013-10-14 +2013-10-16 +2013-10-28 +2013-10-30 +2013-11-11 +2013-11-13 +2013-11-25 +2013-11-27 +2013-12-09 +2013-12-11 +2013-12-23 +2013-12-25 +2014-01-06 +2014-01-08 +2014-01-20 +2014-01-22 +2014-02-03 +2014-02-05 +2014-02-17 +2014-02-19 +2014-03-03 +2014-03-05 +2014-03-17 +2014-03-19 +2014-03-31 +2014-04-02 +2014-04-14 +2014-04-16 +2014-04-28 +2014-04-30 +2014-05-12 +2014-05-14 +2014-05-26 +2014-05-28 +2014-06-09 +2014-06-11 +2014-06-23 +2014-06-25 +2014-07-07 +2014-07-09 +2014-07-21 +2014-07-23 +2014-08-04 +2014-08-06 +2014-08-18 +2014-08-20 +2014-09-01 +2014-09-03 +2014-09-15 +2014-09-17 +2014-09-29 +2014-10-01 +2014-10-13 +2014-10-15 +2014-10-27 +2014-10-29 +2014-11-10 +2014-11-12 +2014-11-24 +2014-11-26 +2014-12-08 +2014-12-10 +2014-12-22 diff --git a/libkcal/tests/runtestcase.pl b/libkcal/tests/runtestcase.pl index 52e6ead1e..4cad27cc6 100755 --- a/libkcal/tests/runtestcase.pl +++ b/libkcal/tests/runtestcase.pl @@ -78,7 +78,7 @@ sub checkfile() exit 1; } while( <REF> ) { - push @ref, $_; + push @ref, $_ if($_ !~ m/^\s*$/); #skip blank lines in the ref } close REF; @@ -92,10 +92,13 @@ sub checkfile() $line = 0; my $errorlines = 0; while( <READ> ) { + next if ($_ =~ m/^\s*$/); #skip blank lines in the output $out = $_; $ref = @ref[$i++]; $line++; + $out =~ s/\s*$//; #remove trailing whitespace + $ref =~ s/\s*$//; #remove trailing whitespace # DTSTAMP, LAST-MODIFIED and CREATED might be different to the reference... if ( $out =~ /^DTSTAMP:[0-9ZT]+\r?$/ && $ref =~ /^DTSTAMP:[0-9ZT]+\r?$/ ) { next; @@ -122,7 +125,6 @@ sub checkfile() print " <Remaining error suppressed>\n"; } } - } close READ; @@ -150,7 +152,7 @@ sub checkfile() } else { print "\n FAILED: $error errors found.\n"; if ( $error > 5 ) { - system( "diff -u $file.$id.ref $outfile" ); + system( "diff -u $file.$id.ref $outfile" ); } system( "touch FAILED" ); exit 1; diff --git a/libkcal/tests/testcalselectdialog.cpp b/libkcal/tests/testcalselectdialog.cpp new file mode 100644 index 000000000..5c5130450 --- /dev/null +++ b/libkcal/tests/testcalselectdialog.cpp @@ -0,0 +1,45 @@ +/* + Copyright (c) 2010 Klar�lvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "calselectdialog.h" +using namespace KCal; + +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <klocale.h> +#include <kdebug.h> + +int main( int argc, char **argv ) +{ + KCmdLineArgs::init( argc, argv, "testcalselectdialog", 0, + "KCalSelectDialogTest", "1.0", + "kcalselectedialog test app" ); + KApplication app; + TQStringList cals; + cals << "standard" << "shared" << "mine" << "yours"; + TQString cal = CalSelectDialog::getItem( i18n( "Calendar Selection" ), + i18n( "Please select a calendar" ), + cals ); + + if ( !cal.isEmpty() ) { + kdDebug() << "Selected calendar " << cal << endl; + } else { + kdDebug() << "nothing selected. user cancel" << endl; + } +} diff --git a/libkcal/todo.cpp b/libkcal/todo.cpp index 9787ff2ad..b3fe65d9c 100644 --- a/libkcal/todo.cpp +++ b/libkcal/todo.cpp @@ -116,10 +116,13 @@ void Todo::setDtDue(const TQDateTime &dtDue, bool first ) TQDateTime Todo::dtDue( bool first ) const { - if ( doesRecur() && !first && mDtRecurrence.isValid() ) + if ( doesRecur() && !first && mDtRecurrence.isValid() ) { return mDtRecurrence; - - return mDtDue; + } else if ( hasDueDate() ) { + return mDtDue; + } else { + return TQDateTime(); + } } TQString Todo::dtDueTimeStr() const @@ -173,10 +176,17 @@ void Todo::setHasStartDate(bool f) TQDateTime Todo::dtStart( bool first ) const { - if ( doesRecur() && !first ) - return mDtRecurrence.addDays( dtDue( first ).daysTo( IncidenceBase::dtStart() ) ); - else + if ( doesRecur() && !first ) { + TQDateTime dt = mDtRecurrence.addDays( dtDue( true ).daysTo( IncidenceBase::dtStart() ) ); + + // We want the dtStart's time, not dtDue's + dt.setTime( IncidenceBase::dtStart().time() ); + return dt; + } else if ( hasStartDate() ) { return IncidenceBase::dtStart(); + } else { + return TQDateTime(); + } } void Todo::setDtStart( const TQDateTime &dtStart ) @@ -255,10 +265,14 @@ int Todo::percentComplete() const return mPercentComplete; } -void Todo::setPercentComplete(int v) +void Todo::setPercentComplete( int v ) { mPercentComplete = v; - if ( v != 100 ) mHasCompletedDate = false; + if ( v != 100 ) { + mHasCompletedDate = false; + mCompleted = TQDateTime(); + } + updated(); } @@ -292,7 +306,8 @@ bool Todo::recurTodo() while ( !recursAt( nextDate ) || nextDate <= TQDateTime::currentDateTime() ) { - if ( !nextDate.isValid() || nextDate > endDateTime ) { + if ( !nextDate.isValid() || + ( nextDate > endDateTime && r->duration() != -1 ) ) { return false; } diff --git a/libkcal/todo.h b/libkcal/todo.h index 1a7b050d4..74e1b39df 100644 --- a/libkcal/todo.h +++ b/libkcal/todo.h @@ -66,21 +66,26 @@ class LIBKCAL_EXPORT Todo : public Incidence /** Returns due time as string formatted according to the users locale settings. + @deprecated use IncidenceFormatter::timeToString() */ - TQString dtDueTimeStr() const; + KDE_DEPRECATED TQString dtDueTimeStr() const; + /** Returns due date as string formatted according to the users locale settings. @param shortfmt If set to true, use short date format, if set to false use long format. + @deprecated use IncidenceFormatter::dateToString() */ - TQString dtDueDateStr( bool shortfmt = true ) const; + KDE_DEPRECATED TQString dtDueDateStr( bool shortfmt = true ) const; + /** Returns due date and time as string formatted according to the users locale settings. + @deprecated use IncidenceFormatter::dateTimeToString() */ - TQString dtDueStr() const; + KDE_DEPRECATED TQString dtDueStr() const; /** Returns true if the todo has a due date, otherwise return false. @@ -218,7 +223,14 @@ class LIBKCAL_EXPORT Todo : public Incidence private: bool accept(Visitor &v) { return v.visit( this ); } - /** Returns true if the todo got a new date, else false will be returned. */ + + /** + * If the todo recurs, mDtRecurrence is set to the next occurrence + * that's after today, mPercentComplete is set to 0 and true is returned. + * + * If the todo doesn't recur or if there aren't anymore occurrences + * it just returns false. + */ bool recurTodo(); TQDateTime mDtDue; // due date of todo |