diff --git a/kcal/icaltimezones.cpp b/kcal/icaltimezones.cpp index 7fa43a806..1bb9d0ce0 100644 --- a/kcal/icaltimezones.cpp +++ b/kcal/icaltimezones.cpp @@ -1,1098 +1,1102 @@ /* This file is part of the kcal library. Copyright (c) 2005-2007 David Jarvie 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 "icaltimezones.h" #include "icalformat.h" #include "icalformat_p.h" extern "C" { #include #include } #include #include #include #include #include #include #include #include #include #include using namespace KCal; // Minimum repetition counts for VTIMEZONE RRULEs static const int minRuleCount = 5; // for any RRULE static const int minPhaseCount = 8; // for separate STANDARD/DAYLIGHT component // Convert an ical time to QDateTime, preserving the UTC indicator static QDateTime toQDateTime( const icaltimetype &t ) { return QDateTime( QDate( t.year, t.month, t.day ), QTime( t.hour, t.minute, t.second ), ( t.is_utc ? Qt::UTC : Qt::LocalTime ) ); } // Maximum date for time zone data. // It's not sensible to try to predict them very far in advance, because // they can easily change. Plus, it limits the processing required. static QDateTime MAX_DATE() { static QDateTime dt; if ( !dt.isValid() ) { dt = QDateTime( QDate::currentDate().addYears( 20 ), QTime( 0, 0, 0 ) ); } return dt; } static icaltimetype writeLocalICalDateTime( const QDateTime &utc, int offset ) { QDateTime local = utc.addSecs( offset ); icaltimetype t = icaltime_null_time(); t.year = local.date().year(); t.month = local.date().month(); t.day = local.date().day(); t.hour = local.time().hour(); t.minute = local.time().minute(); t.second = local.time().second(); t.is_date = 0; t.zone = 0; t.is_utc = 0; return t; } namespace KCal { /******************************************************************************/ //@cond PRIVATE class ICalTimeZonesPrivate { public: ICalTimeZonesPrivate() {} ICalTimeZones::ZoneMap zones; }; //@endcond ICalTimeZones::ICalTimeZones() : d( new ICalTimeZonesPrivate ) { } ICalTimeZones::~ICalTimeZones() { delete d; } const ICalTimeZones::ZoneMap ICalTimeZones::zones() const { return d->zones; } bool ICalTimeZones::add( const ICalTimeZone &zone ) { if ( !zone.isValid() ) { return false; } if ( d->zones.find( zone.name() ) != d->zones.end() ) { return false; // name already exists } d->zones.insert( zone.name(), zone ); return true; } ICalTimeZone ICalTimeZones::remove( const ICalTimeZone &zone ) { if ( zone.isValid() ) { for ( ZoneMap::Iterator it = d->zones.begin(), end = d->zones.end(); it != end; ++it ) { if ( it.value() == zone ) { d->zones.erase( it ); return ( zone == ICalTimeZone::utc() ) ? ICalTimeZone() : zone; } } } return ICalTimeZone(); } ICalTimeZone ICalTimeZones::remove( const QString &name ) { if ( !name.isEmpty() ) { ZoneMap::Iterator it = d->zones.find( name ); if ( it != d->zones.end() ) { ICalTimeZone zone = it.value(); d->zones.erase(it); return ( zone == ICalTimeZone::utc() ) ? ICalTimeZone() : zone; } } return ICalTimeZone(); } void ICalTimeZones::clear() { d->zones.clear(); } ICalTimeZone ICalTimeZones::zone( const QString &name ) const { if ( !name.isEmpty() ) { ZoneMap::ConstIterator it = d->zones.find( name ); if ( it != d->zones.end() ) { return it.value(); } } return ICalTimeZone(); // error } /******************************************************************************/ ICalTimeZoneBackend::ICalTimeZoneBackend() : KTimeZoneBackend() {} ICalTimeZoneBackend::ICalTimeZoneBackend( ICalTimeZoneSource *source, const QString &name, const QString &countryCode, float latitude, float longitude, const QString &comment ) : KTimeZoneBackend( source, name, countryCode, latitude, longitude, comment ) {} ICalTimeZoneBackend::ICalTimeZoneBackend( const KTimeZone &tz, const QDate &earliest ) : KTimeZoneBackend( 0, tz.name(), tz.countryCode(), tz.latitude(), tz.longitude(), tz.comment() ) { Q_UNUSED( earliest ); } ICalTimeZoneBackend::~ICalTimeZoneBackend() {} KTimeZoneBackend *ICalTimeZoneBackend::clone() const { return new ICalTimeZoneBackend( *this ); } QByteArray ICalTimeZoneBackend::type() const { return "ICalTimeZone"; } bool ICalTimeZoneBackend::hasTransitions( const KTimeZone *caller ) const { Q_UNUSED( caller ); return true; } /******************************************************************************/ ICalTimeZone::ICalTimeZone() : KTimeZone( new ICalTimeZoneBackend() ) {} ICalTimeZone::ICalTimeZone( ICalTimeZoneSource *source, const QString &name, ICalTimeZoneData *data ) : KTimeZone( new ICalTimeZoneBackend( source, name ) ) { setData( data ); } ICalTimeZone::ICalTimeZone( const KTimeZone &tz, const QDate &earliest ) : KTimeZone( new ICalTimeZoneBackend( 0, tz.name(), tz.countryCode(), tz.latitude(), tz.longitude(), tz.comment() ) ) { const KTimeZoneData *data = tz.data( true ); if ( data ) { const ICalTimeZoneData *icaldata = dynamic_cast( data ); if ( icaldata ) { setData( new ICalTimeZoneData( *icaldata ) ); } else { setData( new ICalTimeZoneData( *data, tz, earliest ) ); } } } ICalTimeZone::~ICalTimeZone() {} QString ICalTimeZone::city() const { const ICalTimeZoneData *dat = static_cast( data() ); return dat ? dat->city() : QString(); } QByteArray ICalTimeZone::url() const { const ICalTimeZoneData *dat = static_cast( data() ); return dat ? dat->url() : QByteArray(); } QDateTime ICalTimeZone::lastModified() const { const ICalTimeZoneData *dat = static_cast( data() ); return dat ? dat->lastModified() : QDateTime(); } QByteArray ICalTimeZone::vtimezone() const { const ICalTimeZoneData *dat = static_cast( data() ); return dat ? dat->vtimezone() : QByteArray(); } icaltimezone *ICalTimeZone::icalTimezone() const { const ICalTimeZoneData *dat = static_cast( data() ); return dat ? dat->icalTimezone() : 0; } bool ICalTimeZone::update( const ICalTimeZone &other ) { if ( !updateBase( other ) ) { return false; } setData( other.data()->clone(), other.source() ); return true; } ICalTimeZone ICalTimeZone::utc() { static ICalTimeZone utcZone; if ( !utcZone.isValid() ) { ICalTimeZoneSource tzs; utcZone = tzs.parse( icaltimezone_get_utc_timezone() ); } return utcZone; } /******************************************************************************/ //@cond PRIVATE class ICalTimeZoneDataPrivate { public: ICalTimeZoneDataPrivate() : icalComponent(0) {} ~ICalTimeZoneDataPrivate() { if ( icalComponent ) { icalcomponent_free( icalComponent ); } } icalcomponent *component() const { return icalComponent; } void setComponent( icalcomponent *c ) { if ( icalComponent ) { icalcomponent_free( icalComponent ); } icalComponent = c; } QString location; // name of city for this time zone QByteArray url; // URL of published VTIMEZONE definition (optional) QDateTime lastModified; // time of last modification of the VTIMEZONE component (optional) private: icalcomponent *icalComponent; // ical component representing this time zone }; //@endcond ICalTimeZoneData::ICalTimeZoneData() : d ( new ICalTimeZoneDataPrivate() ) { } ICalTimeZoneData::ICalTimeZoneData( const ICalTimeZoneData &rhs ) : KTimeZoneData( rhs ), d( new ICalTimeZoneDataPrivate() ) { d->location = rhs.d->location; d->url = rhs.d->url; d->lastModified = rhs.d->lastModified; d->setComponent( icalcomponent_new_clone( rhs.d->component() ) ); } ICalTimeZoneData::ICalTimeZoneData( const KTimeZoneData &rhs, const KTimeZone &tz, const QDate &earliest ) : KTimeZoneData( rhs ), d( new ICalTimeZoneDataPrivate() ) { // VTIMEZONE RRULE types enum { DAY_OF_MONTH = 0x01, WEEKDAY_OF_MONTH = 0x02, LAST_WEEKDAY_OF_MONTH = 0x04 }; if ( tz.type() == "KSystemTimeZone" ) { // Try to fetch a system time zone in preference, on the grounds // that system time zones are more likely to be up to date than // built-in libical ones. icalcomponent *c = 0; KTimeZone ktz = KSystemTimeZones::readZone( tz.name() ); if ( ktz.isValid() ) { if ( ktz.data(true) ) { ICalTimeZone icaltz( ktz, earliest ); icaltimezone *itz = icaltz.icalTimezone(); c = icalcomponent_new_clone( icaltimezone_get_component( itz ) ); icaltimezone_free( itz, 1 ); } } if ( !c ) { // Try to fetch a built-in libical time zone. icaltimezone *itz = icaltimezone_get_builtin_timezone( tz.name().toUtf8() ); c = icalcomponent_new_clone( icaltimezone_get_component( itz ) ); } if ( c ) { // TZID in built-in libical time zones has a standard prefix. // To make the VTIMEZONE TZID match TZID references in incidences // (as required by RFC2445), strip off the prefix. icalproperty *prop = icalcomponent_get_first_property( c, ICAL_TZID_PROPERTY ); if ( prop ) { icalvalue *value = icalproperty_get_value( prop ); const char *tzid = icalvalue_get_text( value ); QByteArray icalprefix = ICalTimeZoneSource::icalTzidPrefix(); int len = icalprefix.size(); if ( !strncmp( icalprefix, tzid, len ) ) { const char *s = strchr( tzid + len, '/' ); // find third '/' if ( s ) { QByteArray tzidShort( s + 1 ); // deep copy of string (needed by icalvalue_set_text()) icalvalue_set_text( value, tzidShort ); // Remove the X-LIC-LOCATION property, which is only used by libical prop = icalcomponent_get_first_property( c, ICAL_X_PROPERTY ); const char *xname = icalproperty_get_x_name( prop ); if ( xname && !strcmp( xname, "X-LIC-LOCATION" ) ) { icalcomponent_remove_property( c, prop ); } } } } } d->setComponent( c ); } else { // Write the time zone data into an iCal component icalcomponent *tzcomp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT); icalcomponent_add_property( tzcomp, icalproperty_new_tzid( tz.name().toUtf8() ) ); // icalcomponent_add_property(tzcomp, icalproperty_new_location( tz.name().toUtf8() )); // Compile an ordered list of transitions so that we can know the phases // which occur before and after each transition. QList transits = transitions(); if ( earliest.isValid() ) { // Remove all transitions earlier than those we are interested in for ( int i = 0, end = transits.count(); i < end; ++i ) { if ( transits[i].time().date() >= earliest ) { if ( i > 0 ) { transits.erase( transits.begin(), transits.begin() + i ); } break; } } } int trcount = transits.count(); QVector transitionsDone(trcount); transitionsDone.fill(false); // Go through the list of transitions and create an iCal component for each // distinct combination of phase after and UTC offset before the transition. icaldatetimeperiodtype dtperiod; dtperiod.period = icalperiodtype_null_period(); for ( ; ; ) { int i = 0; for ( ; i < trcount && transitionsDone[i]; ++i ) { ; } if ( i >= trcount ) { break; } // Found a phase combination which hasn't yet been processed int preOffset = ( i > 0 ) ? transits[i - 1].phase().utcOffset() : rhs.previousUtcOffset(); KTimeZone::Phase phase = transits[i].phase(); if ( phase.utcOffset() == preOffset ) { transitionsDone[i] = true; while ( ++i < trcount ) { if ( transitionsDone[i] || transits[i].phase() != phase || transits[i - 1].phase().utcOffset() != preOffset ) { continue; } transitionsDone[i] = true; } continue; } icalcomponent *phaseComp = icalcomponent_new( phase.isDst() ? ICAL_XDAYLIGHT_COMPONENT : ICAL_XSTANDARD_COMPONENT ); QList abbrevs = phase.abbreviations(); for ( int a = 0, aend = abbrevs.count(); a < aend; ++a ) { icalcomponent_add_property( phaseComp, icalproperty_new_tzname( static_cast( abbrevs[a]) ) ); } if ( !phase.comment().isEmpty() ) { icalcomponent_add_property( phaseComp, icalproperty_new_comment( phase.comment().toUtf8() ) ); } icalcomponent_add_property( phaseComp, icalproperty_new_tzoffsetfrom( preOffset ) ); icalcomponent_add_property( phaseComp, icalproperty_new_tzoffsetto( phase.utcOffset() ) ); // Create a component to hold initial RRULE if any, plus all RDATEs icalcomponent *phaseComp1 = icalcomponent_new_clone( phaseComp ); icalcomponent_add_property( phaseComp1, icalproperty_new_dtstart( writeLocalICalDateTime( transits[i].time(), preOffset ) ) ); bool useNewRRULE = false; // Compile the list of UTC transition dates/times, and check // if the list can be reduced to an RRULE instead of multiple RDATEs. QTime time; QDate date; int year = 0, month = 0, daysInMonth = 0, dayOfMonth = 0; // avoid compiler warnings int dayOfWeek = 0; // Monday = 1 int nthFromStart = 0; // nth (weekday) of month int nthFromEnd = 0; // nth last (weekday) of month int newRule; int rule = 0; QList rdates;// dates which (probably) need to be written as RDATEs QList times; QDateTime qdt = transits[i].time(); // set 'qdt' for start of loop times += qdt; transitionsDone[i] = true; do { if ( !rule ) { // Initialise data for detecting a new rule rule = DAY_OF_MONTH | WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH; time = qdt.time(); date = qdt.date(); year = date.year(); month = date.month(); daysInMonth = date.daysInMonth(); dayOfWeek = date.dayOfWeek(); // Monday = 1 dayOfMonth = date.day(); nthFromStart = ( dayOfMonth - 1 ) / 7 + 1; // nth (weekday) of month nthFromEnd = ( daysInMonth - dayOfMonth ) / 7 + 1; // nth last (weekday) of month } if ( ++i >= trcount ) { newRule = 0; times += QDateTime(); // append a dummy value since last value in list is ignored } else { if ( transitionsDone[i] || transits[i].phase() != phase || transits[i - 1].phase().utcOffset() != preOffset ) { continue; } transitionsDone[i] = true; qdt = transits[i].time(); if ( !qdt.isValid() ) { continue; } newRule = rule; times += qdt; date = qdt.date(); if ( qdt.time() != time || date.month() != month || date.year() != ++year ) { newRule = 0; } else { int day = date.day(); if ( ( newRule & DAY_OF_MONTH ) && day != dayOfMonth ) { newRule &= ~DAY_OF_MONTH; } if ( newRule & ( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH ) ) { if ( date.dayOfWeek() != dayOfWeek ) { newRule &= ~( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH ); } else { if ( ( newRule & WEEKDAY_OF_MONTH ) && ( day - 1 ) / 7 + 1 != nthFromStart ) { newRule &= ~WEEKDAY_OF_MONTH; } if ( ( newRule & LAST_WEEKDAY_OF_MONTH ) && ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) { newRule &= ~LAST_WEEKDAY_OF_MONTH; } } } } } if ( !newRule ) { // The previous rule (if any) no longer applies. // Write all the times up to but not including the current one. // First check whether any of the last RDATE values fit this rule. int yr = times[0].date().year(); while ( !rdates.isEmpty() ) { qdt = rdates.last(); date = qdt.date(); if ( qdt.time() != time || date.month() != month || date.year() != --yr ) { break; } int day = date.day(); if ( rule & DAY_OF_MONTH ) { if ( day != dayOfMonth ) { break; } } else { if ( date.dayOfWeek() != dayOfWeek || ( ( rule & WEEKDAY_OF_MONTH ) && ( day - 1 ) / 7 + 1 != nthFromStart ) || ( ( rule & LAST_WEEKDAY_OF_MONTH ) && ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) ) { break; } } times.prepend( qdt ); rdates.pop_back(); } if ( times.count() > ( useNewRRULE ? minPhaseCount : minRuleCount ) ) { // There are enough dates to combine into an RRULE icalrecurrencetype r; icalrecurrencetype_clear( &r ); r.freq = ICAL_YEARLY_RECURRENCE; r.count = ( year >= 2030 ) ? 0 : times.count() - 1; r.by_month[0] = month; if ( rule & DAY_OF_MONTH ) { r.by_month_day[0] = dayOfMonth; } else if ( rule & WEEKDAY_OF_MONTH ) { r.by_day[0] = ( dayOfWeek % 7 + 1 ) + ( nthFromStart * 8 ); // Sunday = 1 } else if ( rule & LAST_WEEKDAY_OF_MONTH ) { r.by_day[0] = -( dayOfWeek % 7 + 1 ) - ( nthFromEnd * 8 ); // Sunday = 1 } icalproperty *prop = icalproperty_new_rrule( r ); if ( useNewRRULE ) { // This RRULE doesn't start from the phase start date, so set it into // a new STANDARD/DAYLIGHT component in the VTIMEZONE. icalcomponent *c = icalcomponent_new_clone( phaseComp ); icalcomponent_add_property( c, icalproperty_new_dtstart( writeLocalICalDateTime( times[0], preOffset ) ) ); icalcomponent_add_property( c, prop ); icalcomponent_add_component( tzcomp, c ); } else { icalcomponent_add_property( phaseComp1, prop ); } } else { // Save dates for writing as RDATEs for ( int t = 0, tend = times.count() - 1; t < tend; ++t ) { rdates += times[t]; } } useNewRRULE = true; // All date/time values but the last have been added to the VTIMEZONE. // Remove them from the list. qdt = times.last(); // set 'qdt' for start of loop times.clear(); times += qdt; } rule = newRule; } while ( i < trcount ); // Write remaining dates as RDATEs for ( int rd = 0, rdend = rdates.count(); rd < rdend; ++rd ) { dtperiod.time = writeLocalICalDateTime( rdates[rd], preOffset ); icalcomponent_add_property( phaseComp1, icalproperty_new_rdate( dtperiod ) ); } icalcomponent_add_component( tzcomp, phaseComp1 ); icalcomponent_free( phaseComp ); } d->setComponent( tzcomp ); } } ICalTimeZoneData::~ICalTimeZoneData() { delete d; } ICalTimeZoneData &ICalTimeZoneData::operator=( const ICalTimeZoneData &rhs ) { + // check for self assignment + if ( &rhs == this ) + return *this; + KTimeZoneData::operator=( rhs ); d->location = rhs.d->location; d->url = rhs.d->url; d->lastModified = rhs.d->lastModified; d->setComponent( icalcomponent_new_clone( rhs.d->component() ) ); return *this; } KTimeZoneData *ICalTimeZoneData::clone() const { return new ICalTimeZoneData( *this ); } QString ICalTimeZoneData::city() const { return d->location; } QByteArray ICalTimeZoneData::url() const { return d->url; } QDateTime ICalTimeZoneData::lastModified() const { return d->lastModified; } QByteArray ICalTimeZoneData::vtimezone() const { return icalcomponent_as_ical_string( d->component() ); } icaltimezone *ICalTimeZoneData::icalTimezone() const { icaltimezone *icaltz = icaltimezone_new(); if ( !icaltz ) { return 0; } icalcomponent *c = icalcomponent_new_clone( d->component() ); if ( !icaltimezone_set_component( icaltz, c ) ) { icalcomponent_free( c ); icaltimezone_free( icaltz, 1 ); return 0; } return icaltz; } bool ICalTimeZoneData::hasTransitions() const { return true; } /******************************************************************************/ //@cond PRIVATE class ICalTimeZoneSourcePrivate { public: static QList parsePhase( icalcomponent *, bool daylight, int &prevOffset, KTimeZone::Phase & ); static QByteArray icalTzidPrefix; }; QByteArray ICalTimeZoneSourcePrivate::icalTzidPrefix; //@endcond ICalTimeZoneSource::ICalTimeZoneSource() : KTimeZoneSource( false ), d( 0 ) { } ICalTimeZoneSource::~ICalTimeZoneSource() { } bool ICalTimeZoneSource::parse( const QString &fileName, ICalTimeZones &zones ) { QFile file( fileName ); if ( !file.open( QIODevice::ReadOnly ) ) { return false; } QTextStream ts( &file ); ts.setCodec( "ISO 8859-1" ); QByteArray text = ts.readAll().trimmed().toLatin1(); file.close(); bool result = false; icalcomponent *calendar = icalcomponent_new_from_string( text.data() ); if ( calendar ) { if ( icalcomponent_isa( calendar ) == ICAL_VCALENDAR_COMPONENT ) { result = parse( calendar, zones ); } icalcomponent_free( calendar ); } return result; } bool ICalTimeZoneSource::parse( icalcomponent *calendar, ICalTimeZones &zones ) { for ( icalcomponent *c = icalcomponent_get_first_component( calendar, ICAL_VTIMEZONE_COMPONENT ); c; c = icalcomponent_get_next_component( calendar, ICAL_VTIMEZONE_COMPONENT ) ) { ICalTimeZone zone = parse( c ); if ( !zone.isValid() ) { return false; } ICalTimeZone oldzone = zones.zone( zone.name() ); if ( oldzone.isValid() ) { // The zone already exists in the collection, so update the definition // of the zone rather than using a newly created one. oldzone.update( zone ); } else if ( !zones.add( zone ) ) { return false; } } return true; } ICalTimeZone ICalTimeZoneSource::parse( icalcomponent *vtimezone ) { QString name; QString xlocation; ICalTimeZoneData *data = new ICalTimeZoneData(); // Read the fixed properties which can only appear once in VTIMEZONE icalproperty *p = icalcomponent_get_first_property( vtimezone, ICAL_ANY_PROPERTY ); while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_TZID_PROPERTY: name = QString::fromUtf8( icalproperty_get_tzid( p ) ); break; case ICAL_TZURL_PROPERTY: data->d->url = icalproperty_get_tzurl( p ); break; case ICAL_LOCATION_PROPERTY: // This isn't mentioned in RFC2445, but libical reads it ... data->d->location = QString::fromUtf8( icalproperty_get_location( p ) ); break; case ICAL_X_PROPERTY: { // use X-LIC-LOCATION if LOCATION is missing const char *xname = icalproperty_get_x_name( p ); if ( xname && !strcmp( xname, "X-LIC-LOCATION" ) ) { xlocation = QString::fromUtf8( icalproperty_get_x( p ) ); } break; } case ICAL_LASTMODIFIED_PROPERTY: { icaltimetype t = icalproperty_get_lastmodified(p); if ( t.is_utc ) { data->d->lastModified = toQDateTime( t ); } else { kDebug() << "LAST-MODIFIED not UTC"; } break; } default: break; } p = icalcomponent_get_next_property( vtimezone, ICAL_ANY_PROPERTY ); } if ( name.isEmpty() ) { kDebug() << "TZID missing"; delete data; return ICalTimeZone(); } if ( data->d->location.isEmpty() && !xlocation.isEmpty() ) { data->d->location = xlocation; } QString prefix = QString::fromUtf8( icalTzidPrefix() ); if ( name.startsWith( prefix ) ) { // Remove the prefix from libical built in time zone TZID int i = name.indexOf( '/', prefix.length() ); if ( i > 0 ) { name = name.mid( i + 1 ); } } //kDebug() << "---zoneId: \"" << name << '"'; /* * Iterate through all time zone rules for this VTIMEZONE, * and create a Phase object containing details for each one. */ int prevOffset = 0; QList transitions; QDateTime earliest; QList phases; for ( icalcomponent *c = icalcomponent_get_first_component( vtimezone, ICAL_ANY_COMPONENT ); c; c = icalcomponent_get_next_component( vtimezone, ICAL_ANY_COMPONENT ) ) { int prevoff; KTimeZone::Phase phase; QList times; icalcomponent_kind kind = icalcomponent_isa( c ); switch ( kind ) { case ICAL_XSTANDARD_COMPONENT: //kDebug() << "---standard phase: found"; times = ICalTimeZoneSourcePrivate::parsePhase( c, false, prevoff, phase ); break; case ICAL_XDAYLIGHT_COMPONENT: //kDebug() << "---daylight phase: found"; times = ICalTimeZoneSourcePrivate::parsePhase( c, true, prevoff, phase ); break; default: kDebug() << "Unknown component:" << kind; break; } int tcount = times.count(); if ( tcount ) { phases += phase; for ( int t = 0; t < tcount; ++t ) { transitions += KTimeZone::Transition( times[t], phase ); } if ( !earliest.isValid() || times[0] < earliest ) { prevOffset = prevoff; earliest = times[0]; } } } data->setPhases( phases, prevOffset ); // Remove any "duplicate" transitions, i.e. those where two consecutive // transitions have the same phase. qSort( transitions ); for ( int t = 1, tend = transitions.count(); t < tend; ) { if ( transitions[t].phase() == transitions[t - 1].phase() ) { transitions.removeAt( t ); --tend; } else { ++t; } } data->setTransitions( transitions ); data->d->setComponent( icalcomponent_new_clone( vtimezone ) ); kDebug() << "VTIMEZONE" << name; return ICalTimeZone( this, name, data ); } ICalTimeZone ICalTimeZoneSource::parse( icaltimezone *tz ) { /* Parse the VTIMEZONE component stored in the icaltimezone structure. * This is both easier and provides more complete information than * extracting already parsed data from icaltimezone. */ return parse( icaltimezone_get_component( tz ) ); } //@cond PRIVATE QList ICalTimeZoneSourcePrivate::parsePhase( icalcomponent *c, bool daylight, int &prevOffset, KTimeZone::Phase &phase ) { QList transitions; // Read the observance data for this standard/daylight savings phase QList abbrevs; QString comment; prevOffset = 0; int utcOffset = 0; bool recurs = false; bool found_dtstart = false; bool found_tzoffsetfrom = false; bool found_tzoffsetto = false; icaltimetype dtstart = icaltime_null_time(); // Now do the ical reading. icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY ); while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_TZNAME_PROPERTY: // abbreviated name for this time offset { // TZNAME can appear multiple times in order to provide language // translations of the time zone offset name. #ifdef __GNUC__ #warning Does this cope with multiple language specifications? #endif QByteArray tzname = icalproperty_get_tzname( p ); // Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME // strings, which is totally useless. So ignore those. if ( ( !daylight && tzname == "Standard Time" ) || ( daylight && tzname == "Daylight Time" ) ) { break; } if ( !abbrevs.contains( tzname ) ) { abbrevs += tzname; } break; } case ICAL_DTSTART_PROPERTY: // local time at which phase starts dtstart = icalproperty_get_dtstart( p ); found_dtstart = true; break; case ICAL_TZOFFSETFROM_PROPERTY: // UTC offset immediately before start of phase prevOffset = icalproperty_get_tzoffsetfrom( p ); found_tzoffsetfrom = true; break; case ICAL_TZOFFSETTO_PROPERTY: utcOffset = icalproperty_get_tzoffsetto( p ); found_tzoffsetto = true; break; case ICAL_COMMENT_PROPERTY: comment = QString::fromUtf8( icalproperty_get_comment( p ) ); break; case ICAL_RDATE_PROPERTY: case ICAL_RRULE_PROPERTY: recurs = true; break; default: kDebug() << "Unknown property:" << kind; break; } p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY ); } // Validate the phase data if ( !found_dtstart || !found_tzoffsetfrom || !found_tzoffsetto ) { kDebug() << "DTSTART/TZOFFSETFROM/TZOFFSETTO missing"; return transitions; } // Convert DTSTART to QDateTime, and from local time to UTC QDateTime localStart = toQDateTime( dtstart ); // local time dtstart.second -= prevOffset; dtstart.is_utc = 1; QDateTime utcStart = toQDateTime( icaltime_normalize( dtstart ) ); // UTC transitions += utcStart; if ( recurs ) { /* RDATE or RRULE is specified. There should only be one or the other, but * it doesn't really matter - the code can cope with both. * Note that we had to get DTSTART, TZOFFSETFROM, TZOFFSETTO before reading * recurrences. */ KDateTime klocalStart( localStart, KDateTime::Spec::ClockTime() ); KDateTime maxTime( MAX_DATE(), KDateTime::Spec::ClockTime() ); Recurrence recur; icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY ); while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_RDATE_PROPERTY: { icaltimetype t = icalproperty_get_rdate(p).time; if ( icaltime_is_date( t ) ) { // RDATE with a DATE value inherits the (local) time from DTSTART t.hour = dtstart.hour; t.minute = dtstart.minute; t.second = dtstart.second; t.is_date = 0; t.is_utc = 0; // dtstart is in local time } // RFC2445 states that RDATE must be in local time, // but we support UTC as well to be safe. if ( !t.is_utc ) { t.second -= prevOffset; // convert to UTC t.is_utc = 1; t = icaltime_normalize( t ); } transitions += toQDateTime( t ); break; } case ICAL_RRULE_PROPERTY: { RecurrenceRule r; ICalFormat icf; ICalFormatImpl impl( &icf ); impl.readRecurrence( icalproperty_get_rrule( p ), &r ); r.setStartDt( klocalStart ); // The end date time specified in an RRULE should be in UTC. // Convert to local time to avoid timesInInterval() getting things wrong. if ( r.duration() == 0 ) { KDateTime end( r.endDt() ); if ( end.timeSpec() == KDateTime::Spec::UTC() ) { end.setTimeSpec( KDateTime::Spec::ClockTime() ); r.setEndDt( end.addSecs( prevOffset ) ); } } DateTimeList dts = r.timesInInterval( klocalStart, maxTime ); for ( int i = 0, end = dts.count(); i < end; ++i ) { QDateTime utc = dts[i].dateTime(); utc.setTimeSpec( Qt::UTC ); transitions += utc.addSecs( -prevOffset ); } break; } default: break; } p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY ); } qSortUnique( transitions ); } phase = KTimeZone::Phase( utcOffset, abbrevs, daylight, comment ); return transitions; } //@endcond ICalTimeZone ICalTimeZoneSource::standardZone( const QString &zone, bool icalBuiltIn ) { if ( !icalBuiltIn ) { // Try to fetch a system time zone in preference, on the grounds // that system time zones are more likely to be up to date than // built-in libical ones. QString tzid = zone; QString prefix = QString::fromUtf8( icalTzidPrefix() ); if ( zone.startsWith( prefix ) ) { int i = zone.indexOf( '/', prefix.length() ); if ( i > 0 ) { tzid = zone.mid( i + 1 ); // strip off the libical prefix } } KTimeZone ktz = KSystemTimeZones::readZone( tzid ); if ( ktz.isValid() ) { if ( ktz.data( true ) ) { ICalTimeZone icaltz( ktz ); kDebug() << zone << " read from system database"; return icaltz; } } } // Try to fetch a built-in libical time zone. // First try to look it up as a geographical location (e.g. Europe/London) QByteArray zoneName = zone.toUtf8(); icaltimezone *icaltz = icaltimezone_get_builtin_timezone( zoneName ); if ( !icaltz ) { // This will find it if it includes the libical prefix icaltz = icaltimezone_get_builtin_timezone_from_tzid( zoneName ); if ( !icaltz ) { return ICalTimeZone(); } } return parse( icaltz ); } QByteArray ICalTimeZoneSource::icalTzidPrefix() { if ( ICalTimeZoneSourcePrivate::icalTzidPrefix.isEmpty() ) { icaltimezone *icaltz = icaltimezone_get_builtin_timezone( "Europe/London" ); QByteArray tzid = icaltimezone_get_tzid( icaltz ); if ( tzid.right( 13 ) == "Europe/London" ) { int i = tzid.indexOf( '/', 1 ); if ( i > 0 ) { ICalTimeZoneSourcePrivate::icalTzidPrefix = tzid.left( i + 1 ); return ICalTimeZoneSourcePrivate::icalTzidPrefix; } } kError() << "failed to get libical TZID prefix"; } return ICalTimeZoneSourcePrivate::icalTzidPrefix; } } // namespace KCal diff --git a/kcal/incidence.cpp b/kcal/incidence.cpp index 64112994e..866e016e7 100644 --- a/kcal/incidence.cpp +++ b/kcal/incidence.cpp @@ -1,946 +1,950 @@ /* This file is part of the kcal library. Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the Incidence class. @brief Provides the class common to non-FreeBusy (Events, To-dos, Journals) calendar components known as incidences. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ */ #include "incidence.h" #include "calformat.h" #include #include #include #include #include // for Qt::escape() and Qt::mightBeRichText() using namespace KCal; /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class KCal::Incidence::Private { public: Private() : mRecurrence( 0 ), mStatus( StatusNone ), mSecrecy( SecrecyPublic ), mPriority( 0 ), mRelatedTo( 0 ) { mAlarms.setAutoDelete( true ); mAttachments.setAutoDelete( true ); } Private( const Private &p ) : mCreated( p.mCreated ), mRevision( p.mRevision ), mDescription( p.mDescription ), mDescriptionIsRich( p.mDescriptionIsRich ), mSummary( p.mSummary ), mSummaryIsRich( p.mSummaryIsRich ), mLocation( p.mLocation ), mLocationIsRich( p.mLocationIsRich ), mCategories( p.mCategories ), mResources( p.mResources ), mStatus( p.mStatus ), mStatusString( p.mStatusString ), mSecrecy( p.mSecrecy ), mPriority( p.mPriority ), mSchedulingID( p.mSchedulingID ), mRelatedTo( 0 ), mRelatedToUid( p.mRelatedToUid ) // TODO: reenable attributes currently commented out. // Incidence *mRelatedTo; Incidence *mRelatedTo; // Incidence::List mRelations; Incidence::List mRelations; { mAlarms.setAutoDelete( true ); mAttachments.setAutoDelete( true ); } void clear() { mAlarms.clearAll(); mAttachments.clearAll(); delete mRecurrence; } KDateTime mCreated; // creation datetime int mRevision; // revision number QString mDescription; // description string bool mDescriptionIsRich; // description string is richtext. QString mSummary; // summary string bool mSummaryIsRich; // summary string is richtext. QString mLocation; // location string bool mLocationIsRich; // location string is richtext. QStringList mCategories; // category list mutable Recurrence *mRecurrence; // recurrence Attachment::List mAttachments; // attachments list Alarm::List mAlarms; // alarms list QStringList mResources; // resources list (not calendar resources) Status mStatus; // status QString mStatusString; // status string, for custom status Secrecy mSecrecy; // secrecy int mPriority; // priority: 1 = highest, 2 = less, etc. QString mSchedulingID; // ID for scheduling mails Incidence *mRelatedTo; // incidence this is related to QString mRelatedToUid; // incidence (by Uid) this is related to Incidence::List mRelations; // a list of incidences this is related to }; //@endcond Incidence::Incidence() : IncidenceBase(), d( new KCal::Incidence::Private ) { recreate(); } Incidence::Incidence( const Incidence &i ) : IncidenceBase( i ), Recurrence::RecurrenceObserver(), d( new KCal::Incidence::Private( *i.d ) ) { init( i ); } void Incidence::init( const Incidence &i ) { // TODO: reenable attributes currently commented out. d->mRevision = i.d->mRevision; d->mCreated = i.d->mCreated; d->mDescription = i.d->mDescription; d->mSummary = i.d->mSummary; d->mCategories = i.d->mCategories; // Incidence *mRelatedTo; Incidence *mRelatedTo; d->mRelatedTo = 0; d->mRelatedToUid = i.d->mRelatedToUid; // Incidence::List mRelations; Incidence::List mRelations; d->mResources = i.d->mResources; d->mStatusString = i.d->mStatusString; d->mStatus = i.d->mStatus; d->mSecrecy = i.d->mSecrecy; d->mPriority = i.d->mPriority; d->mLocation = i.d->mLocation; // Alarms and Attachments are stored in ListBase<...>, which is a QValueList<...*>. // We need to really duplicate the objects stored therein, otherwise deleting // i will also delete all attachments from this object (setAutoDelete...) Alarm::List::ConstIterator it; for ( it = i.d->mAlarms.begin(); it != i.d->mAlarms.end(); ++it ) { Alarm *b = new Alarm( **it ); b->setParent( this ); d->mAlarms.append( b ); } Attachment::List::ConstIterator it1; for ( it1 = i.d->mAttachments.begin(); it1 != i.d->mAttachments.end(); ++it1 ) { Attachment *a = new Attachment( **it1 ); d->mAttachments.append( a ); } if ( i.d->mRecurrence ) { d->mRecurrence = new Recurrence( *( i.d->mRecurrence ) ); d->mRecurrence->addObserver( this ); } else { d->mRecurrence = 0; } } Incidence::~Incidence() { Incidence::List relations = d->mRelations; foreach ( Incidence *incidence, relations ) { if ( incidence->relatedTo() == this ) { incidence->setRelatedTo( 0 ); } } if ( relatedTo() ) { relatedTo()->removeRelation( this ); } delete d->mRecurrence; delete d; } //@cond PRIVATE // A string comparison that considers that null and empty are the same static bool stringCompare( const QString &s1, const QString &s2 ) { return ( s1.isEmpty() && s2.isEmpty() ) || ( s1 == s2 ); } //@endcond Incidence &Incidence::operator=( const Incidence &other ) { + // check for self assignment + if ( &other == this ) + return *this; + d->clear(); //TODO: should relations be cleared out, as in destructor??? IncidenceBase::operator=( other ); init( other ); return *this; } bool Incidence::operator==( const Incidence &i2 ) const { if ( alarms().count() != i2.alarms().count() ) { return false; // no need to check further } Alarm::List::ConstIterator a1 = alarms().begin(); Alarm::List::ConstIterator a2 = i2.alarms().begin(); for ( ; a1 != alarms().end() && a2 != i2.alarms().end(); ++a1, ++a2 ) { if ( **a1 == **a2 ) { continue; } else { return false; } } if ( !IncidenceBase::operator==( i2 ) ) { return false; } bool recurrenceEqual = ( d->mRecurrence == 0 && i2.d->mRecurrence == 0 ); if ( !recurrenceEqual ) { recurrenceEqual = d->mRecurrence != 0 && i2.d->mRecurrence != 0 && *d->mRecurrence == *i2.d->mRecurrence; } return recurrenceEqual && created() == i2.created() && stringCompare( description(), i2.description() ) && stringCompare( summary(), i2.summary() ) && categories() == i2.categories() && // no need to compare mRelatedTo stringCompare( relatedToUid(), i2.relatedToUid() ) && relations() == i2.relations() && attachments() == i2.attachments() && resources() == i2.resources() && d->mStatus == i2.d->mStatus && ( d->mStatus == StatusNone || stringCompare( d->mStatusString, i2.d->mStatusString ) ) && secrecy() == i2.secrecy() && priority() == i2.priority() && stringCompare( location(), i2.location() ) && stringCompare( schedulingID(), i2.schedulingID() ); } void Incidence::recreate() { KDateTime nowUTC = KDateTime::currentUtcDateTime(); setCreated( nowUTC ); setUid( CalFormat::createUniqueId() ); setSchedulingID( QString() ); setRevision( 0 ); setLastModified( nowUTC ); } void Incidence::setReadOnly( bool readOnly ) { IncidenceBase::setReadOnly( readOnly ); if ( d->mRecurrence ) { d->mRecurrence->setRecurReadOnly( readOnly ); } } void Incidence::setAllDay( bool allDay ) { if ( mReadOnly ) { return; } if ( recurrence() ) { recurrence()->setAllDay( allDay ); } IncidenceBase::setAllDay( allDay ); } void Incidence::setCreated( const KDateTime &created ) { if ( mReadOnly ) { return; } d->mCreated = created.toUtc(); // FIXME: Shouldn't we call updated for the creation date, too? // updated(); } KDateTime Incidence::created() const { return d->mCreated; } void Incidence::setRevision( int rev ) { if ( mReadOnly ) { return; } d->mRevision = rev; updated(); } int Incidence::revision() const { return d->mRevision; } void Incidence::setDtStart( const KDateTime &dt ) { if ( d->mRecurrence ) { d->mRecurrence->setStartDateTime( dt ); d->mRecurrence->setAllDay( allDay() ); } IncidenceBase::setDtStart( dt ); } KDateTime Incidence::dtEnd() const { return KDateTime(); } void Incidence::shiftTimes( const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec ) { IncidenceBase::shiftTimes( oldSpec, newSpec ); if ( d->mRecurrence ) { d->mRecurrence->shiftTimes( oldSpec, newSpec ); } for ( int i = 0, end = d->mAlarms.count(); i < end; ++i ) { d->mAlarms[i]->shiftTimes( oldSpec, newSpec ); } } void Incidence::setDescription( const QString &description, bool isRich ) { if ( mReadOnly ) { return; } d->mDescription = description; d->mDescriptionIsRich = isRich; updated(); } void Incidence::setDescription( const QString &description ) { setDescription( description, Qt::mightBeRichText( description ) ); } QString Incidence::description() const { return d->mDescription; } QString Incidence::richDescription() const { if ( descriptionIsRich() ) { return d->mDescription; } else { return Qt::escape( d->mDescription ).replace( '\n', "
" ); } } bool Incidence::descriptionIsRich() const { return d->mDescriptionIsRich; } void Incidence::setSummary( const QString &summary, bool isRich ) { if ( mReadOnly ) { return; } d->mSummary = summary; d->mSummaryIsRich = isRich; updated(); } void Incidence::setSummary( const QString &summary ) { setSummary( summary, Qt::mightBeRichText( summary ) ); } QString Incidence::summary() const { return d->mSummary; } QString Incidence::richSummary() const { if ( summaryIsRich() ) { return d->mSummary; } else { return Qt::escape( d->mSummary ).replace( '\n', "
" ); } } bool Incidence::summaryIsRich() const { return d->mSummaryIsRich; } void Incidence::setCategories( const QStringList &categories ) { if ( mReadOnly ) { return; } d->mCategories = categories; updated(); } void Incidence::setCategories( const QString &catStr ) { if ( mReadOnly ) { return; } d->mCategories.clear(); if ( catStr.isEmpty() ) { return; } d->mCategories = catStr.split( ',' ); QStringList::Iterator it; for ( it = d->mCategories.begin();it != d->mCategories.end(); ++it ) { *it = (*it).trimmed(); } updated(); } QStringList Incidence::categories() const { return d->mCategories; } QString Incidence::categoriesStr() const { return d->mCategories.join( "," ); } void Incidence::setRelatedToUid( const QString &relatedToUid ) { if ( mReadOnly || d->mRelatedToUid == relatedToUid ) { return; } d->mRelatedToUid = relatedToUid; updated(); } QString Incidence::relatedToUid() const { return d->mRelatedToUid; } void Incidence::setRelatedTo( Incidence *incidence ) { if ( mReadOnly || d->mRelatedTo == incidence ) { return; } if ( d->mRelatedTo ) { d->mRelatedTo->removeRelation( this ); } d->mRelatedTo = incidence; if ( d->mRelatedTo ) { d->mRelatedTo->addRelation( this ); if ( d->mRelatedTo->uid() != d->mRelatedToUid ) { setRelatedToUid( d->mRelatedTo->uid() ); } } else { setRelatedToUid( QString() ); } } Incidence *Incidence::relatedTo() const { return d->mRelatedTo; } Incidence::List Incidence::relations() const { return d->mRelations; } void Incidence::addRelation( Incidence *incidence ) { if ( !d->mRelations.contains( incidence ) ) { d->mRelations.append( incidence ); } } void Incidence::removeRelation( Incidence *incidence ) { d->mRelations.removeRef( incidence ); d->mRelatedToUid.clear(); // if (incidence->getRelatedTo() == this) incidence->setRelatedTo(0); } // %%%%%%%%%%%% Recurrence-related methods %%%%%%%%%%%%%%%%%%%% Recurrence *Incidence::recurrence() const { if ( !d->mRecurrence ) { d->mRecurrence = new Recurrence(); d->mRecurrence->setStartDateTime( IncidenceBase::dtStart() ); d->mRecurrence->setAllDay( allDay() ); d->mRecurrence->setRecurReadOnly( mReadOnly ); d->mRecurrence->addObserver( const_cast( this ) ); } return d->mRecurrence; } void Incidence::clearRecurrence() { delete d->mRecurrence; d->mRecurrence = 0; } ushort Incidence::recurrenceType() const { if ( d->mRecurrence ) { return d->mRecurrence->recurrenceType(); } else { return Recurrence::rNone; } } bool Incidence::recurs() const { if ( d->mRecurrence ) { return d->mRecurrence->recurs(); } else { return false; } } bool Incidence::recursOn( const QDate &date, const KDateTime::Spec &timeSpec ) const { return d->mRecurrence && d->mRecurrence->recursOn( date, timeSpec ); } bool Incidence::recursAt( const KDateTime &qdt ) const { return d->mRecurrence && d->mRecurrence->recursAt( qdt ); } QList Incidence::startDateTimesForDate( const QDate &date, const KDateTime::Spec &timeSpec ) const { KDateTime start = dtStart(); KDateTime end = endDateRecurrenceBase(); QList result; // TODO_Recurrence: Also work if only due date is given... if ( !start.isValid() && ! end.isValid() ) { return result; } // if the incidence doesn't recur, KDateTime kdate( date, timeSpec ); if ( !recurs() ) { if ( !( start > kdate || end < kdate ) ) { result << start; } return result; } int days = start.daysTo( end ); // Account for possible recurrences going over midnight, while the original event doesn't QDate tmpday( date.addDays( -days - 1 ) ); KDateTime tmp; while ( tmpday <= date ) { if ( recurrence()->recursOn( tmpday, timeSpec ) ) { QList times = recurrence()->recurTimesOn( tmpday, timeSpec ); for ( QList::ConstIterator it = times.begin(); it != times.end(); ++it ) { tmp = KDateTime( tmpday, *it, start.timeSpec() ); if ( endDateForStart( tmp ) >= kdate ) { result << tmp; } } } tmpday = tmpday.addDays( 1 ); } return result; } QList Incidence::startDateTimesForDateTime( const KDateTime &datetime ) const { KDateTime start = dtStart(); KDateTime end = endDateRecurrenceBase(); QList result; // TODO_Recurrence: Also work if only due date is given... if ( !start.isValid() && ! end.isValid() ) { return result; } // if the incidence doesn't recur, if ( !recurs() ) { if ( !( start > datetime || end < datetime ) ) { result << start; } return result; } int days = start.daysTo( end ); // Account for possible recurrences going over midnight, while the original event doesn't QDate tmpday( datetime.date().addDays( -days - 1 ) ); KDateTime tmp; while ( tmpday <= datetime.date() ) { if ( recurrence()->recursOn( tmpday, datetime.timeSpec() ) ) { // Get the times during the day (in start date's time zone) when recurrences happen QList times = recurrence()->recurTimesOn( tmpday, start.timeSpec() ); for ( QList::ConstIterator it = times.begin(); it != times.end(); ++it ) { tmp = KDateTime( tmpday, *it, start.timeSpec() ); if ( !( tmp > datetime || endDateForStart( tmp ) < datetime ) ) { result << tmp; } } } tmpday = tmpday.addDays( 1 ); } return result; } KDateTime Incidence::endDateForStart( const KDateTime &startDt ) const { KDateTime start = dtStart(); KDateTime end = endDateRecurrenceBase(); if ( !end.isValid() ) { return start; } if ( !start.isValid() ) { return end; } return startDt.addSecs( start.secsTo( end ) ); } void Incidence::addAttachment( Attachment *attachment ) { if ( mReadOnly || !attachment ) { return; } d->mAttachments.append( attachment ); updated(); } void Incidence::deleteAttachment( Attachment *attachment ) { d->mAttachments.removeRef( attachment ); } void Incidence::deleteAttachments( const QString &mime ) { Attachment::List::Iterator it = d->mAttachments.begin(); while ( it != d->mAttachments.end() ) { if ( (*it)->mimeType() == mime ) { d->mAttachments.removeRef( it ); } else { ++it; } } } Attachment::List Incidence::attachments() const { return d->mAttachments; } Attachment::List Incidence::attachments( const QString &mime ) const { Attachment::List attachments; Attachment::List::ConstIterator it; for ( it = d->mAttachments.begin(); it != d->mAttachments.end(); ++it ) { if ( (*it)->mimeType() == mime ) { attachments.append( *it ); } } return attachments; } void Incidence::clearAttachments() { d->mAttachments.clearAll(); } void Incidence::setResources( const QStringList &resources ) { if ( mReadOnly ) { return; } d->mResources = resources; updated(); } QStringList Incidence::resources() const { return d->mResources; } void Incidence::setPriority( int priority ) { if ( mReadOnly ) { return; } d->mPriority = priority; updated(); } int Incidence::priority() const { return d->mPriority; } void Incidence::setStatus( Incidence::Status status ) { if ( mReadOnly || status == StatusX ) { return; } d->mStatus = status; d->mStatusString.clear(); updated(); } void Incidence::setCustomStatus( const QString &status ) { if ( mReadOnly ) { return; } d->mStatus = status.isEmpty() ? StatusNone : StatusX; d->mStatusString = status; updated(); } Incidence::Status Incidence::status() const { return d->mStatus; } QString Incidence::statusStr() const { if ( d->mStatus == StatusX ) { return d->mStatusString; } return statusName( d->mStatus ); } QString Incidence::statusName( Incidence::Status status ) { switch ( status ) { case StatusTentative: return i18nc( "@item event is tentative", "Tentative" ); case StatusConfirmed: return i18nc( "@item event is definite", "Confirmed" ); case StatusCompleted: return i18nc( "@item to-do is complete", "Completed" ); case StatusNeedsAction: return i18nc( "@item to-do needs action", "Needs-Action" ); case StatusCanceled: return i18nc( "@item event orto-do is canceled; journal is removed", "Canceled" ); case StatusInProcess: return i18nc( "@item to-do is in process", "In-Process" ); case StatusDraft: return i18nc( "@item journal is in draft form", "Draft" ); case StatusFinal: return i18nc( "@item journal is in final form", "Final" ); case StatusX: case StatusNone: default: return QString(); } } void Incidence::setSecrecy( Incidence::Secrecy secrecy ) { if ( mReadOnly ) { return; } d->mSecrecy = secrecy; updated(); } Incidence::Secrecy Incidence::secrecy() const { return d->mSecrecy; } QString Incidence::secrecyStr() const { return secrecyName( d->mSecrecy ); } QString Incidence::secrecyName( Incidence::Secrecy secrecy ) { switch ( secrecy ) { case SecrecyPublic: return i18nc( "@item incidence access if for everyone", "Public" ); case SecrecyPrivate: return i18nc( "@item incidence access is by owner only", "Private" ); case SecrecyConfidential: return i18nc( "@item incidence access is by owner and a controlled group", "Confidential" ); default: return QString(); // to make compilers happy } } QStringList Incidence::secrecyList() { QStringList list; list << secrecyName( SecrecyPublic ); list << secrecyName( SecrecyPrivate ); list << secrecyName( SecrecyConfidential ); return list; } const Alarm::List &Incidence::alarms() const { return d->mAlarms; } Alarm *Incidence::newAlarm() { Alarm *alarm = new Alarm( this ); d->mAlarms.append( alarm ); return alarm; } void Incidence::addAlarm( Alarm *alarm ) { d->mAlarms.append( alarm ); updated(); } void Incidence::removeAlarm( Alarm *alarm ) { d->mAlarms.removeRef( alarm ); updated(); } void Incidence::clearAlarms() { d->mAlarms.clearAll(); updated(); } bool Incidence::isAlarmEnabled() const { Alarm::List::ConstIterator it; for ( it = d->mAlarms.begin(); it != d->mAlarms.end(); ++it ) { if ( (*it)->enabled() ) { return true; } } return false; } void Incidence::setLocation( const QString &location, bool isRich ) { if ( mReadOnly ) { return; } d->mLocation = location; d->mLocationIsRich = isRich; updated(); } void Incidence::setLocation( const QString &location ) { setLocation( location, Qt::mightBeRichText( location ) ); } QString Incidence::location() const { return d->mLocation; } QString Incidence::richLocation() const { if ( locationIsRich() ) { return d->mLocation; } else { return Qt::escape( d->mLocation ).replace( '\n', "
" ); } } bool Incidence::locationIsRich() const { return d->mLocationIsRich; } void Incidence::setSchedulingID( const QString &sid ) { d->mSchedulingID = sid; } QString Incidence::schedulingID() const { if ( d->mSchedulingID.isNull() ) { // Nothing set, so use the normal uid return uid(); } return d->mSchedulingID; } /** Observer interface for the recurrence class. If the recurrence is changed, this method will be called for the incidence the recurrence object belongs to. */ void Incidence::recurrenceUpdated( Recurrence *recurrence ) { if ( recurrence == d->mRecurrence ) { updated(); } }