Initial (i.e. read only) support for RECURRENCE-ID modified incidence series.

Write support requires further debugging and/or compliance checks with respect to Zimbra;
there is no obvious reason why it should not be working but Zimbra fails with 409 when saving.
User interface support is mostly complete, with event links being tracked across deletes.


git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1168937 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
v3.5.13-sru
tpearson 14 years ago
parent a3e46fcf74
commit cfa6b4114c

@ -1170,7 +1170,7 @@ void KOAgendaView::changeIncidenceDisplayAdded( Incidence *incidence )
for( dit = mSelectedDates.begin(); dit != mSelectedDates.end(); ++dit ) {
curDate = *dit;
// FIXME: This breaks with recurring multi-day events!
if ( incidence->recursOn( curDate ) ) {
if ( incidence->recursOn( curDate, calendar() ) ) {
insertIncidence( incidence, curDate );
}
}

@ -970,7 +970,7 @@ void KOMonthView::changeIncidenceDisplayAdded( Incidence *incidence, MonthViewCe
if ( incidence->doesRecur() ) {
for ( uint i = 0; i < mCells.count(); ++i ) {
if ( incidence->recursOn( mCells[i]->date() ) ) {
if ( incidence->recursOn( mCells[i]->date(), calendar() ) ) {
// handle multiday events
int length = gdv.startDate().daysTo( gdv.endDate().addSecs( floats ? 0 : -1 ).date() );

@ -506,16 +506,24 @@ TQString ResourceCalDav::getICalString(const Incidence::List& inc) {
CalendarLocal loc(timeZoneId());
TQString data = "";
TQString header = "";
TQString footer = "";
ICalFormat ical;
// Get the iCal header and footer
header = ical.toString(&loc);
int location = header.find("END:VCALENDAR", 0, true);
footer = header.mid(location, 0xffffffff);
header.remove("END:VCALENDAR");
data = data + header;
// NOTE: This is very susceptible to invalid entries in added/changed/deletedIncidences
// Be very careful with clearChange/clearChanges, and be sure to clear after load and save...
for(Incidence::List::ConstIterator it = inc.constBegin(); it != inc.constEnd(); ++it) {
Incidence *i = (*it)->clone();
loc.addIncidence(i);
data = data + ical.toString(i, &mCalendar);
}
data = ical.toString(&loc);
data = data + footer;
return data;
}

@ -290,6 +290,25 @@ bool Calendar::addIncidence( Incidence *incidence )
bool Calendar::deleteIncidence( Incidence *incidence )
{
if ( beginChange( incidence ) ) {
if (incidence->hasRecurrenceID()) {
// Delete this event's UID from the parent's list of children
Incidence *parentIncidence;
IncidenceList il = incidence->childIncidences();
IncidenceListIterator it;
it = il.begin();
parentIncidence = this->incidence(*it);
parentIncidence->deleteChildIncidence(incidence->uid());
}
else {
// Delete all children as well
IncidenceList il = incidence->childIncidences();
IncidenceListIterator it;
for ( it = il.begin(); it != il.end(); ++it ) {
deleteIncidence( this->incidence(*it) );
// Avoid a crash, reset the iterator every time the list is modified
it = il.begin();
}
}
Incidence::DeleteVisitor<Calendar> v( this );
bool result = incidence->accept( v );
endChange( incidence );

@ -126,6 +126,10 @@ bool CalendarLocal::deleteEvent( Event *event )
setModified( true );
notifyIncidenceDeleted( event );
mDeletedIncidences.append( event );
// Delete child events
if (!event->hasRecurrenceID()) {
deleteChildEvents(event);
}
return true;
} else {
kdWarning() << "CalendarLocal::deleteEvent(): Event not found." << endl;
@ -133,6 +137,21 @@ bool CalendarLocal::deleteEvent( Event *event )
}
}
bool CalendarLocal::deleteChildEvents( Event *event )
{
EventDictIterator it( mEvents );
for( ; it.current(); ++it ) {
Event *e = *it;
if (e->uid() == event->uid()) {
if ( e->hasRecurrenceID() ) {
deleteEvent(( e ));
}
}
}
return true;
}
void CalendarLocal::deleteAllEvents()
{
// kdDebug(5800) << "CalendarLocal::deleteAllEvents" << endl;
@ -178,6 +197,10 @@ bool CalendarLocal::deleteTodo( Todo *todo )
setModified( true );
notifyIncidenceDeleted( todo );
mDeletedIncidences.append( todo );
// Delete child todos
if (!todo->hasRecurrenceID()) {
deleteChildTodos(todo);
}
return true;
} else {
kdWarning() << "CalendarLocal::deleteTodo(): Todo not found." << endl;
@ -185,6 +208,21 @@ bool CalendarLocal::deleteTodo( Todo *todo )
}
}
bool CalendarLocal::deleteChildTodos( Todo *todo )
{
Todo::List::ConstIterator it;
for( it = mTodoList.begin(); it != mTodoList.end(); ++it ) {
Todo *t = *it;
if (t->uid() == todo->uid()) {
if ( t->hasRecurrenceID() ) {
deleteTodo(( t ));
}
}
}
return true;
}
void CalendarLocal::deleteAllTodos()
{
// kdDebug(5800) << "CalendarLocal::deleteAllTodos()\n";
@ -392,13 +430,13 @@ Event::List CalendarLocal::rawEventsForDate( const TQDate &qd,
int extraDays = event->dtStart().date().daysTo( event->dtEnd().date() );
int i;
for ( i = 0; i <= extraDays; i++ ) {
if ( event->recursOn( qd.addDays( -i ) ) ) {
if ( event->recursOn( qd.addDays( -i ), this ) ) {
eventList.append( event );
break;
}
}
} else {
if ( event->recursOn( qd ) )
if ( event->recursOn( qd, this ) )
eventList.append( event );
}
} else {
@ -527,6 +565,10 @@ bool CalendarLocal::deleteJournal( Journal *journal )
setModified( true );
notifyIncidenceDeleted( journal );
mDeletedIncidences.append( journal );
// Delete child journals
if (!journal->hasRecurrenceID()) {
deleteChildJournals(journal);
}
return true;
} else {
kdWarning() << "CalendarLocal::deleteJournal(): Journal not found." << endl;
@ -534,6 +576,21 @@ bool CalendarLocal::deleteJournal( Journal *journal )
}
}
bool CalendarLocal::deleteChildJournals( Journal *journal )
{
Journal::List::ConstIterator it;
for( it = mJournalList.begin(); it != mJournalList.end(); ++it ) {
Journal *j = *it;
if (j->uid() == journal->uid()) {
if ( j->hasRecurrenceID() ) {
deleteJournal(( j ));
}
}
}
return true;
}
void CalendarLocal::deleteAllJournals()
{
Journal::List::ConstIterator it;

@ -88,6 +88,10 @@ class LIBKCAL_EXPORT CalendarLocal : public Calendar
Deletes an event from this calendar.
*/
bool deleteEvent( Event *event );
/**
Deletes a child event from this calendar.
*/
bool deleteChildEvents( Event *event );
/**
Deletes all events from this calendar.
*/
@ -110,6 +114,10 @@ class LIBKCAL_EXPORT CalendarLocal : public Calendar
Remove a todo from the todolist.
*/
bool deleteTodo( Todo * );
/**
Deletes a child todo from this calendar.
*/
bool deleteChildTodos( Todo *todo );
/**
Deletes all todos from this calendar.
*/
@ -136,6 +144,10 @@ class LIBKCAL_EXPORT CalendarLocal : public Calendar
Remove a Journal from the calendar.
*/
bool deleteJournal( Journal * );
/**
Delete a child journal from this calendar.
*/
bool deleteChildJournals( Journal *journal );
/**
Deletes all journals from this calendar.
*/

@ -293,6 +293,50 @@ TQString ICalFormat::toString( Incidence *incidence )
return text;
}
TQString ICalFormat::toString( Incidence *incidence, Calendar *calendar )
{
icalcomponent *component;
TQString text = "";
// See if there are any parent or child events that must be added to the string
if ( incidence->hasRecurrenceID() ) {
// Get the parent
IncidenceList il = incidence->childIncidences();
IncidenceListIterator it;
it = il.begin();
Incidence *parentIncidence;
parentIncidence = calendar->incidence(*it);
il = parentIncidence->childIncidences();
if (il.count() > 0) {
for ( it = il.begin(); it != il.end(); ++it ) {
component = mImpl->writeIncidence( calendar->incidence(*it) );
text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
icalcomponent_free( component );
}
}
component = mImpl->writeIncidence( parentIncidence );
text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
icalcomponent_free( component );
}
else {
// This incidence is a potential parent
IncidenceList il = incidence->childIncidences();
if (il.count() > 0) {
IncidenceListIterator it;
for ( it = il.begin(); it != il.end(); ++it ) {
component = mImpl->writeIncidence( calendar->incidence(*it) );
text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
icalcomponent_free( component );
}
}
component = mImpl->writeIncidence( incidence );
text = text + TQString::fromUtf8( icalcomponent_as_ical_string( component ) );
icalcomponent_free( component );
}
return text;
}
TQString ICalFormat::toString( RecurrenceRule *recurrence )
{
icalproperty *property;

@ -86,6 +86,13 @@ class LIBKCAL_EXPORT ICalFormat : public CalFormat
Return incidence as iCalendar formatted text.
*/
TQString toString( Incidence * );
/**
Return incidence as iCalendar formatted text.
This function includes all RECURRENCE-ID related incidences.
@return TQString of iCalendar formatted text.
@since 3.5.12
*/
TQString toString( Incidence *, Calendar * );
/**
Return recurrence rule as iCalendar formatted text.
*/

@ -314,10 +314,24 @@ void ICalFormatImpl::writeIncidence(icalcomponent *parent,Incidence *incidence)
icalcomponent_add_property(parent,p);
}
if ( incidence->schedulingID() != incidence->uid() )
TQString modifiedUid;
if ( incidence->hasRecurrenceID() ) {
// Recurring incidences are special; they must match their parent's UID
// Each child has the parent set as the first item in the list
// So, get and set the UID...
IncidenceList il = incidence->childIncidences();
IncidenceListIterator it;
it = il.begin();
modifiedUid = (*it);
}
else {
modifiedUid = incidence->uid();
}
if ( incidence->schedulingID() != modifiedUid )
// We need to store the UID in here. The rawSchedulingID will
// go into the iCal UID component
incidence->setCustomProperty( "LIBKCAL", "ID", incidence->uid() );
incidence->setCustomProperty( "LIBKCAL", "ID", modifiedUid );
else
incidence->removeCustomProperty( "LIBKCAL", "ID" );
@ -330,9 +344,15 @@ void ICalFormatImpl::writeIncidence(icalcomponent *parent,Incidence *incidence)
// unique id
// If the scheduling ID is different from the real UID, the real
// one is stored on X-REALID above
if ( !incidence->schedulingID().isEmpty() ) {
icalcomponent_add_property(parent,icalproperty_new_uid(
incidence->schedulingID().utf8()));
if ( incidence->hasRecurrenceID() ) {
// Recurring incidences are special; they must match their parent's UID
icalcomponent_add_property(parent,icalproperty_new_uid(modifiedUid.utf8()));
}
else {
if ( !incidence->schedulingID().isEmpty() ) {
icalcomponent_add_property(parent,icalproperty_new_uid(
incidence->schedulingID().utf8()));
}
}
// revision
@ -426,6 +446,11 @@ void ICalFormatImpl::writeIncidence(icalcomponent *parent,Incidence *incidence)
incidence->relatedToUid().utf8()));
}
// recurrenceid
if ( incidence->hasRecurrenceID() ) {
icalcomponent_add_property(parent, icalproperty_new_recurrenceid( writeICalDateTime( incidence->recurrenceID() ) ));
}
// kdDebug(5800) << "Write recurrence for '" << incidence->summary() << "' (" << incidence->uid()
// << ")" << endl;
@ -1359,10 +1384,21 @@ void ICalFormatImpl::readIncidence(icalcomponent *parent, icaltimezone *tz, Inci
categories.append(TQString::fromUtf8(text));
break;
case ICAL_RECURRENCEID_PROPERTY: // recurrenceID
icaltime = icalproperty_get_recurrenceid(p);
incidence->setRecurrenceID( readICalDateTime( p, icaltime ) );
incidence->setHasRecurrenceID( true );
break;
case ICAL_RRULE_PROPERTY:
readRecurrenceRule( p, incidence );
break;
// case ICAL_CONTACT_PROPERTY:
// incidenceBase->addContact(
// QString::fromUtf8( icalproperty_get_contact( p ) ) );
// break;
case ICAL_RDATE_PROPERTY: {
icaldatetimeperiodtype rd = icalproperty_get_rdate( p );
if ( icaltime_is_valid_time( rd.time ) ) {
@ -2031,11 +2067,29 @@ bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar)
// kdDebug(5800) << "----Todo found" << endl;
Todo *todo = readTodo(c);
if (todo) {
if (!cal->todo(todo->uid())) {
cal->addTodo(todo);
} else {
delete todo;
mTodosRelate.remove( todo );
if (todo->hasRecurrenceID()) {
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->event(originalUid)) {
printf("FIXME! [WARNING] Parent for child event does not yet exist!\n\r");
}
else {
// Add this todo to its parent
cal->todo(originalUid)->addChildIncidence(todo->uid());
// And the parent to the child
todo->addChildIncidence(cal->todo(originalUid)->uid());
}
}
}
else {
if (!cal->todo(todo->uid())) {
cal->addTodo(todo);
} else {
delete todo;
mTodosRelate.remove( todo );
}
}
}
c = icalcomponent_get_next_component(calendar,ICAL_VTODO_COMPONENT);
@ -2047,11 +2101,29 @@ bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar)
// kdDebug(5800) << "----Event found" << endl;
Event *event = readEvent(c, ctz);
if (event) {
if (!cal->event(event->uid())) {
cal->addEvent(event);
} else {
delete event;
mEventsRelate.remove( event );
if (event->hasRecurrenceID()) {
TQString originalUid = event->uid();
event->setUid(originalUid + QString("-recur-%1").arg(event->recurrenceID().toTime_t()));
if (!cal->event(event->uid())) {
cal->addEvent(event);
if (!cal->event(originalUid)) {
printf("FIXME! [WARNING] Parent for child event does not yet exist!\n\r");
}
else {
// Add this event to its parent
cal->event(originalUid)->addChildIncidence(event->uid());
// And the parent to the child
event->addChildIncidence(cal->event(originalUid)->uid());
}
}
}
else {
if (!cal->event(event->uid())) {
cal->addEvent(event);
} else {
delete event;
mEventsRelate.remove( event );
}
}
}
c = icalcomponent_get_next_component(calendar,ICAL_VEVENT_COMPONENT);
@ -2063,10 +2135,28 @@ bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar)
// kdDebug(5800) << "----Journal found" << endl;
Journal *journal = readJournal(c);
if (journal) {
if (!cal->journal(journal->uid())) {
cal->addJournal(journal);
} else {
delete journal;
if (journal->hasRecurrenceID()) {
TQString originalUid = journal->uid();
journal->setUid(originalUid + QString("-recur-%1").arg(journal->recurrenceID().toTime_t()));
if (!cal->journal(journal->uid())) {
cal->addJournal(journal);
if (!cal->event(originalUid)) {
printf("FIXME! [WARNING] Parent for child event does not yet exist!\n\r");
}
else {
// Add this journal to its parent
cal->journal(originalUid)->addChildIncidence(journal->uid());
// And the parent to the child
journal->addChildIncidence(cal->journal(originalUid)->uid());
}
}
}
else {
if (!cal->journal(journal->uid())) {
cal->addJournal(journal);
} else {
delete journal;
}
}
}
c = icalcomponent_get_next_component(calendar,ICAL_VJOURNAL_COMPONENT);

@ -27,13 +27,15 @@
#include "calformat.h"
#include "incidence.h"
#include "calendar.h"
using namespace KCal;
Incidence::Incidence() :
IncidenceBase(),
mRelatedTo(0), mStatus(StatusNone), mSecrecy(SecrecyPublic),
mPriority(0), mRecurrence(0)
mPriority(0), mRecurrence(0),
mHasRecurrenceID( false ), mChildRecurrenceEvents()
{
recreate();
@ -59,6 +61,9 @@ Incidence::Incidence( const Incidence &i ) : IncidenceBase( i ),Recurrence::Obse
mSecrecy = i.mSecrecy;
mPriority = i.mPriority;
mLocation = i.mLocation;
mRecurrenceID = i.mRecurrenceID;
mHasRecurrenceID = i.mHasRecurrenceID;
mChildRecurrenceEvents = i.mChildRecurrenceEvents;
// Alarms and Attachments are stored in ListBase<...>, which is a TQValueList<...*>.
// We need to really duplicate the objects stored therein, otherwise deleting
@ -124,6 +129,9 @@ Incidence& Incidence::operator=( const Incidence &i )
mSecrecy = i.mSecrecy;
mPriority = i.mPriority;
mLocation = i.mLocation;
mRecurrenceID = i.mRecurrenceID;
mHasRecurrenceID = i.mHasRecurrenceID;
mChildRecurrenceEvents = i.mChildRecurrenceEvents;
mAlarms.clearAll();
Alarm::List::ConstIterator it;
@ -413,12 +421,58 @@ bool Incidence::doesRecur() const
bool Incidence::recursOn(const TQDate &qd) const
{
return ( mRecurrence && mRecurrence->recursOn(qd) );
bool doesRecur = false;
doesRecur = mRecurrence && mRecurrence->recursOn(qd);
return doesRecur;
}
bool Incidence::recursAt(const TQDateTime &qdt) const
{
return ( mRecurrence && mRecurrence->recursAt(qdt) );
bool doesRecur = false;
doesRecur = mRecurrence && mRecurrence->recursAt(qdt);
return doesRecur;
}
bool Incidence::recursOn(const TQDate &qd, Calendar *cal) const
{
bool doesRecur = false;
doesRecur = mRecurrence && mRecurrence->recursOn(qd);
// Make sure that this instance has not been moved through a RECURRENCE-ID statement
if (hasRecurrenceID() == false) {
IncidenceList il = childIncidences();
IncidenceListIterator it;
for ( it = il.begin(); it != il.end(); ++it ) {
QDateTime modifiedDt = cal->incidence(*it)->recurrenceID();
modifiedDt.setTime(QTime());
if (QDateTime(qd) == modifiedDt) {
doesRecur = false;
}
}
}
return doesRecur;
}
bool Incidence::recursAt(const TQDateTime &qdt, Calendar *cal) const
{
bool doesRecur = false;
doesRecur = mRecurrence && mRecurrence->recursAt(qdt);
// Make sure that this instance has not been moved through a RECURRENCE-ID statement
if (hasRecurrenceID() == false) {
IncidenceList il = childIncidences();
IncidenceListIterator it;
for ( it = il.begin(); it != il.end(); ++it ) {
if (qdt == cal->incidence(*it)->recurrenceID()) {
doesRecur = false;
}
}
}
return doesRecur;
}
/**
@ -836,6 +890,52 @@ TQString Incidence::schedulingID() const
return mSchedulingID;
}
bool Incidence::hasRecurrenceID() const
{
return mHasRecurrenceID;
}
void Incidence::setHasRecurrenceID( bool hasRecurrenceID )
{
if ( mReadOnly ) {
return;
}
mHasRecurrenceID = hasRecurrenceID;
updated();
}
TQDateTime Incidence::recurrenceID() const
{
return mRecurrenceID;
}
void Incidence::setRecurrenceID( const TQDateTime &recurrenceID )
{
if ( mReadOnly ) {
return;
}
// update();
mRecurrenceID = recurrenceID;
updated();
}
void Incidence::addChildIncidence( TQString childIncidence )
{
mChildRecurrenceEvents.append(childIncidence);
}
void Incidence::deleteChildIncidence( TQString childIncidence )
{
mChildRecurrenceEvents.remove(childIncidence);
}
IncidenceList Incidence::childIncidences() const
{
return mChildRecurrenceEvents;
}
/** Observer interface for the recurrence class. If the recurrence is changed,
this method will be called for the incidence the recurrence object
belongs to. */

@ -36,6 +36,10 @@
namespace KCal {
class Calendar;
typedef QStringList IncidenceList;
typedef QStringList::iterator IncidenceListIterator;
/**
This class provides the base class common to all calendar components.
@ -248,6 +252,21 @@ class LIBKCAL_EXPORT Incidence : public IncidenceBase, public Recurrence::Observ
*/
bool recursAt( const TQDateTime &qdt ) const;
/**
Returns true if the date specified is one on which the incidence will
recur.
This function takes RECURRENCE-ID parameters into account
@param cal the calendar owning the incidence
*/
virtual bool recursOn( const TQDate &qd, Calendar *cal ) const;
/**
Returns true if the date/time specified is one on which the incidence will
recur.
This function takes RECURRENCE-ID parameters into account
@param cal the calendar owning the incidence
*/
bool recursAt( const TQDateTime &qdt, Calendar *cal ) const;
/**
Calculates the start date/time for all recurrences that happen at some time
on the given date (might start before that date, but end on or after the
@ -378,6 +397,58 @@ class LIBKCAL_EXPORT Incidence : public IncidenceBase, public Recurrence::Observ
*/
int priority() const;
/**
Returns true if the incidence has recurrenceID, otherwise return false.
@see setHasRecurrenceID(), setRecurrenceID(TQDateTime)
@since 3.5.12
*/
bool hasRecurrenceID() const;
/**
Sets if the incidence has recurrenceID.
@param hasRecurrenceID true if incidence has recurrenceID, otherwise false
@see hasRecurrenceID(), recurrenceID()
@since 3.5.12
*/
void setHasRecurrenceID( bool hasRecurrenceID );
/**
Set the incidences recurrenceID.
@param recurrenceID is the incidence recurrenceID to set
@see recurrenceID().
@since 3.5.12
*/
void setRecurrenceID( const TQDateTime &recurrenceID );
/**
Returns the incidence recurrenceID.
@return incidences recurrenceID value
@see setRecurrenceID().
@since 3.5.12
*/
TQDateTime recurrenceID() const;
/**
Attach a child incidence to a parent incidence.
@param childIncidence is the child incidence to add
@since 3.5.12
*/
void addChildIncidence( TQString childIncidence );
/**
Detach a child incidence from its parent incidence.
@param childIncidence is the child incidence to remove
@since 3.5.12
*/
void deleteChildIncidence( TQString childIncidence );
/**
Returns an EventList of all child incidences.
@return EventList of all child incidences.
@since 3.5.12
*/
IncidenceList childIncidences() const;
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// %%%%% Alarm-related methods
@ -477,6 +548,11 @@ class LIBKCAL_EXPORT Incidence : public IncidenceBase, public Recurrence::Observ
// Scheduling ID - used only to identify between scheduling mails
TQString mSchedulingID;
TQDateTime mRecurrenceID; // recurrenceID
bool mHasRecurrenceID; // if incidence has recurrenceID
IncidenceList mChildRecurrenceEvents;
class Private;
Private *d;
};

Loading…
Cancel
Save