kde-playground/kdepimlibs/kalarmcal/kaevent.cpp
2015-04-14 21:49:29 +00:00

6466 lines
248 KiB
C++

/*
* kaevent.cpp - represents calendar events
* This file is part of kalarmcal library, which provides access to KAlarm
* calendar data.
* Copyright © 2001-2013 by David Jarvie <djarvie@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 "kaevent.h"
#include "alarmtext.h"
#include "identities.h"
#include "version.h"
#ifndef KALARMCAL_USE_KRESOURCES
#include <kcalcore/memorycalendar.h>
#else
#include <kcal/calendarlocal.h>
#endif
#include <kholidays/holidays.h>
using namespace KHolidays;
#include <ksystemtimezone.h>
#include <klocalizedstring.h>
#ifdef KALARMCAL_USE_KRESOURCES
#include <ksharedconfig.h>
#include <kglobal.h>
#include <kconfiggroup.h>
#endif
#include <kglobal.h>
#include <kdebug.h>
#ifndef KALARMCAL_USE_KRESOURCES
using namespace KCalCore;
#else
using namespace KCal;
#endif
using namespace KHolidays;
namespace KAlarmCal
{
//=============================================================================
#ifndef KALARMCAL_USE_KRESOURCES
typedef KCalCore::Person EmailAddress;
class EmailAddressList : public KCalCore::Person::List
#else
typedef KCal::Person EmailAddress;
class EmailAddressList : public QList<KCal::Person>
#endif
{
public:
#ifndef KALARMCAL_USE_KRESOURCES
EmailAddressList() : KCalCore::Person::List() { }
EmailAddressList(const KCalCore::Person::List& list) { operator=(list); }
EmailAddressList& operator=(const KCalCore::Person::List&);
#else
EmailAddressList() : QList<KCal::Person>() { }
EmailAddressList(const QList<KCal::Person>& list) { operator=(list); }
EmailAddressList& operator=(const QList<KCal::Person>&);
#endif
operator QStringList() const;
QString join(const QString& separator) const;
QStringList pureAddresses() const;
QString pureAddresses(const QString& separator) const;
private:
QString address(int index) const;
};
//=============================================================================
class KAAlarm::Private
{
public:
Private();
Action mActionType; // alarm action type
Type mType; // alarm type
DateTime mNextMainDateTime; // next time to display the alarm, excluding repetitions
Repetition mRepetition; // sub-repetition count and interval
int mNextRepeat; // repetition count of next due sub-repetition
bool mRepeatAtLogin; // whether to repeat the alarm at every login
bool mRecurs; // there is a recurrence rule for the alarm
bool mDeferred; // whether the alarm is an extra deferred/deferred-reminder alarm
bool mTimedDeferral; // if mDeferred = true: true if the deferral is timed, false if date-only
};
//=============================================================================
class KAEventPrivate : public QSharedData
{
public:
// Read-only internal flags additional to KAEvent::Flags enum values.
// NOTE: If any values are added to those in KAEvent::Flags, ensure
// that these values don't overlap them.
enum
{
REMINDER = 0x100000,
DEFERRAL = 0x200000,
TIMED_FLAG = 0x400000,
DATE_DEFERRAL = DEFERRAL,
TIME_DEFERRAL = DEFERRAL | TIMED_FLAG,
DISPLAYING_ = 0x800000,
READ_ONLY_FLAGS = 0xF00000 //!< mask for all read-only internal values
};
enum ReminderType // current active state of reminder
{
NO_REMINDER, // reminder is not due
ACTIVE_REMINDER, // reminder is due
HIDDEN_REMINDER // reminder-after is disabled due to main alarm being deferred past it
};
enum DeferType
{
NO_DEFERRAL = 0, // there is no deferred alarm
NORMAL_DEFERRAL, // the main alarm, a recurrence or a repeat is deferred
REMINDER_DEFERRAL // a reminder alarm is deferred
};
// Alarm types.
// This uses the same scheme as KAAlarm::Type, with some extra values.
// Note that the actual enum values need not be the same as in KAAlarm::Type.
enum AlarmType
{
INVALID_ALARM = 0, // Not an alarm
MAIN_ALARM = 1, // THE real alarm. Must be the first in the enumeration.
REMINDER_ALARM = 0x02, // Reminder in advance of/after the main alarm
DEFERRED_ALARM = 0x04, // Deferred alarm
DEFERRED_REMINDER_ALARM = REMINDER_ALARM | DEFERRED_ALARM, // Deferred reminder alarm
// The following values must be greater than the preceding ones, to
// ensure that in ordered processing they are processed afterwards.
AT_LOGIN_ALARM = 0x10, // Additional repeat-at-login trigger
DISPLAYING_ALARM = 0x20, // Copy of the alarm currently being displayed
// The following are extra internal KAEvent values
AUDIO_ALARM = 0x30, // sound to play when displaying the alarm
PRE_ACTION_ALARM = 0x40, // command to execute before displaying the alarm
POST_ACTION_ALARM = 0x50 // command to execute after the alarm window is closed
};
struct AlarmData
{
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr alarm;
#else
const Alarm* alarm;
#endif
QString cleanText; // text or audio file name
QFont font;
QColor bgColour, fgColour;
float soundVolume;
float fadeVolume;
int fadeSeconds;
int repeatSoundPause;
int nextRepeat;
uint emailFromId;
KAEventPrivate::AlarmType type;
KAAlarm::Action action;
int displayingFlags;
KAEvent::ExtraActionOptions extraActionOptions;
bool speak;
bool defaultFont;
bool isEmailText;
bool commandScript;
bool timedDeferral;
bool hiddenReminder;
};
typedef QMap<AlarmType, AlarmData> AlarmMap;
KAEventPrivate();
KAEventPrivate(const KDateTime&, const QString& message, const QColor& bg, const QColor& fg,
const QFont& f, KAEvent::SubAction, int lateCancel, KAEvent::Flags flags,
bool changesPending = false);
#ifndef KALARMCAL_USE_KRESOURCES
explicit KAEventPrivate(const KCalCore::Event::Ptr&);
#else
explicit KAEventPrivate(const KCal::Event*);
#endif
KAEventPrivate(const KAEventPrivate&);
~KAEventPrivate() { delete mRecurrence; }
KAEventPrivate& operator=(const KAEventPrivate& e) { if (&e != this) copy(e); return *this; }
#ifndef KALARMCAL_USE_KRESOURCES
void set(const KCalCore::Event::Ptr&);
#else
void set(const KCal::Event*);
#endif
void set(const KDateTime&, const QString& message, const QColor& bg, const QColor& fg,
const QFont&, KAEvent::SubAction, int lateCancel, KAEvent::Flags flags,
bool changesPending = false);
void setAudioFile(const QString& filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile);
KAEvent::OccurType setNextOccurrence(const KDateTime& preDateTime);
void setFirstRecurrence();
void setCategory(CalEvent::Type);
void setRepeatAtLogin(bool);
void setRepeatAtLoginTrue(bool clearReminder);
void setReminder(int minutes, bool onceOnly);
void activateReminderAfter(const DateTime& mainAlarmTime);
void defer(const DateTime&, bool reminder, bool adjustRecurrence = false);
void cancelDefer();
#ifndef KALARMCAL_USE_KRESOURCES
bool setDisplaying(const KAEventPrivate&, KAAlarm::Type, Akonadi::Collection::Id, const KDateTime& dt, bool showEdit, bool showDefer);
void reinstateFromDisplaying(const KCalCore::Event::Ptr&, Akonadi::Collection::Id&, bool& showEdit, bool& showDefer);
#else
bool setDisplaying(const KAEventPrivate&, KAAlarm::Type, const QString& resourceID, const KDateTime& dt, bool showEdit, bool showDefer);
void reinstateFromDisplaying(const KCal::Event*, QString& resourceID, bool& showEdit, bool& showDefer);
void setCommandError(const QString& configString);
void setCommandError(KAEvent::CmdErrType, bool writeConfig) const;
#endif
void startChanges() { ++mChangeCount; }
void endChanges();
void removeExpiredAlarm(KAAlarm::Type);
KAAlarm alarm(KAAlarm::Type) const;
KAAlarm firstAlarm() const;
KAAlarm nextAlarm(KAAlarm::Type) const;
#ifndef KALARMCAL_USE_KRESOURCES
bool updateKCalEvent(const KCalCore::Event::Ptr&, KAEvent::UidAction, bool setCustomProperties = true) const;
#else
bool updateKCalEvent(KCal::Event*, KAEvent::UidAction) const;
#endif
DateTime mainDateTime(bool withRepeats = false) const
{ return (withRepeats && mNextRepeat && mRepetition)
? mRepetition.duration(mNextRepeat).end(mNextMainDateTime.kDateTime()) : mNextMainDateTime; }
DateTime mainEndRepeatTime() const
{ return mRepetition ? mRepetition.duration().end(mNextMainDateTime.kDateTime()) : mNextMainDateTime; }
DateTime deferralLimit(KAEvent::DeferLimitType* = 0) const;
KAEvent::Flags flags() const;
bool isWorkingTime(const KDateTime&) const;
bool setRepetition(const Repetition&);
bool occursAfter(const KDateTime& preDateTime, bool includeRepetitions) const;
KAEvent::OccurType nextOccurrence(const KDateTime& preDateTime, DateTime& result, KAEvent::OccurOption = KAEvent::IGNORE_REPETITION) const;
KAEvent::OccurType previousOccurrence(const KDateTime& afterDateTime, DateTime& result, bool includeRepetitions = false) const;
void setRecurrence(const KARecurrence&);
#ifndef KALARMCAL_USE_KRESOURCES
bool setRecur(KCalCore::RecurrenceRule::PeriodType, int freq, int count, const QDate& end, KARecurrence::Feb29Type = KARecurrence::Feb29_None);
bool setRecur(KCalCore::RecurrenceRule::PeriodType, int freq, int count, const KDateTime& end, KARecurrence::Feb29Type = KARecurrence::Feb29_None);
#else
bool setRecur(KCal::RecurrenceRule::PeriodType, int freq, int count, const QDate& end, KARecurrence::Feb29Type = KARecurrence::Feb29_None);
bool setRecur(KCal::RecurrenceRule::PeriodType, int freq, int count, const KDateTime& end, KARecurrence::Feb29Type = KARecurrence::Feb29_None);
#endif
KARecurrence::Type checkRecur() const;
void clearRecur();
void calcTriggerTimes() const;
#ifdef KDE_NO_DEBUG_OUTPUT
void dumpDebug() const { }
#else
void dumpDebug() const;
#endif
#ifndef KALARMCAL_USE_KRESOURCES
static bool convertRepetition(const KCalCore::Event::Ptr&);
static bool convertStartOfDay(const KCalCore::Event::Ptr&);
static DateTime readDateTime(const KCalCore::Event::Ptr&, bool dateOnly, DateTime& start);
static void readAlarms(const KCalCore::Event::Ptr&, AlarmMap*, bool cmdDisplay = false);
static void readAlarm(const KCalCore::Alarm::Ptr&, AlarmData&, bool audioMain, bool cmdDisplay = false);
#else
static bool convertRepetition(KCal::Event*);
static bool convertStartOfDay(KCal::Event*);
static DateTime readDateTime(const KCal::Event*, bool dateOnly, DateTime& start);
static void readAlarms(const KCal::Event*, AlarmMap*, bool cmdDisplay = false);
static void readAlarm(const KCal::Alarm*, AlarmData&, bool audioMain, bool cmdDisplay = false);
#endif
private:
void copy(const KAEventPrivate&);
bool mayOccurDailyDuringWork(const KDateTime&) const;
int nextWorkRepetition(const KDateTime& pre) const;
void calcNextWorkingTime(const DateTime& nextTrigger) const;
DateTime nextWorkingTime() const;
KAEvent::OccurType nextRecurrence(const KDateTime& preDateTime, DateTime& result) const;
#ifndef KALARMCAL_USE_KRESOURCES
void setAudioAlarm(const KCalCore::Alarm::Ptr&) const;
KCalCore::Alarm::Ptr initKCalAlarm(const KCalCore::Event::Ptr&, const DateTime&, const QStringList& types, AlarmType = INVALID_ALARM) const;
KCalCore::Alarm::Ptr initKCalAlarm(const KCalCore::Event::Ptr&, int startOffsetSecs, const QStringList& types, AlarmType = INVALID_ALARM) const;
#else
void setAudioAlarm(KCal::Alarm*) const;
KCal::Alarm* initKCalAlarm(KCal::Event*, const DateTime&, const QStringList& types, AlarmType = INVALID_ALARM) const;
KCal::Alarm* initKCalAlarm(KCal::Event*, int startOffsetSecs, const QStringList& types, AlarmType = INVALID_ALARM) const;
#endif
inline void set_deferral(DeferType);
inline void activate_reminder(bool activate);
public:
#ifdef KALARMCAL_USE_KRESOURCES
static QString mCmdErrConfigGroup; // config file group for command error recording
#endif
static QFont mDefaultFont; // default alarm message font
static const KHolidays::HolidayRegion* mHolidays; // holiday region to use
static QBitArray mWorkDays; // working days of the week
static QTime mWorkDayStart; // start time of the working day
static QTime mWorkDayEnd; // end time of the working day
static int mWorkTimeIndex; // incremented every time working days/times are changed
#ifdef KALARMCAL_USE_KRESOURCES
AlarmResource* mResource; // resource which owns the event (for convenience - not used by this class)
#endif
mutable DateTime mAllTrigger; // next trigger time, including reminders, ignoring working hours
mutable DateTime mMainTrigger; // next trigger time, ignoring reminders and working hours
mutable DateTime mAllWorkTrigger; // next trigger time, taking account of reminders and working hours
mutable DateTime mMainWorkTrigger; // next trigger time, ignoring reminders but taking account of working hours
mutable KAEvent::CmdErrType mCommandError; // command execution error last time the alarm triggered
QString mEventID; // UID: KCal::Event unique ID
QString mTemplateName; // alarm template's name, or null if normal event
#ifndef KALARMCAL_USE_KRESOURCES
QMap<QByteArray, QString> mCustomProperties; // KCal::Event's non-KAlarm custom properties
Akonadi::Item::Id mItemId; // Akonadi::Item ID for this event
mutable Akonadi::Collection::Id mCollectionId; // ID of collection containing the event, or for a displaying event,
// saved collection ID (not the collection the event is in)
#else
QString mOriginalResourceId;// saved resource ID (not the resource the event is in)
#endif
QString mText; // message text, file URL, command, email body [or audio file for KAAlarm]
QString mAudioFile; // ATTACH: audio file to play
QString mPreAction; // command to execute before alarm is displayed
QString mPostAction; // command to execute after alarm window is closed
DateTime mStartDateTime; // DTSTART and DTEND: start and end time for event
KDateTime mCreatedDateTime; // CREATED: date event was created, or saved in archive calendar
DateTime mNextMainDateTime; // next time to display the alarm, excluding repetitions
KDateTime mAtLoginDateTime; // repeat-at-login end time
DateTime mDeferralTime; // extra time to trigger alarm (if alarm or reminder deferred)
DateTime mDisplayingTime; // date/time shown in the alarm currently being displayed
int mDisplayingFlags; // type of alarm which is currently being displayed (for display alarm)
int mReminderMinutes; // how long in advance reminder is to be, or 0 if none (<0 for reminder AFTER the alarm)
DateTime mReminderAfterTime; // if mReminderActive true, time to trigger reminder AFTER the main alarm, or invalid if not pending
ReminderType mReminderActive; // whether a reminder is due (before next, or after last, main alarm/recurrence)
int mDeferDefaultMinutes; // default number of minutes for deferral dialog, or 0 to select time control
bool mDeferDefaultDateOnly;// select date-only by default in deferral dialog
int mRevision; // SEQUENCE: revision number of the original alarm, or 0
KARecurrence* mRecurrence; // RECUR: recurrence specification, or 0 if none
Repetition mRepetition; // sub-repetition count and interval
int mNextRepeat; // repetition count of next due sub-repetition
int mAlarmCount; // number of alarms: count of !mMainExpired, mRepeatAtLogin, mDeferral, mReminderActive, mDisplaying
DeferType mDeferral; // whether the alarm is an extra deferred/deferred-reminder alarm
unsigned long mKMailSerialNumber; // if email text, message's KMail serial number
int mTemplateAfterTime; // time not specified: use n minutes after default time, or -1 (applies to templates only)
QColor mBgColour; // background colour of alarm message
QColor mFgColour; // foreground colour of alarm message, or invalid for default
QFont mFont; // font of alarm message (ignored if mUseDefaultFont true)
uint mEmailFromIdentity; // standard email identity uoid for 'From' field, or empty
EmailAddressList mEmailAddresses; // ATTENDEE: addresses to send email to
QString mEmailSubject; // SUMMARY: subject line of email
QStringList mEmailAttachments; // ATTACH: email attachment file names
mutable int mChangeCount; // >0 = inhibit calling calcTriggerTimes()
mutable bool mTriggerChanged; // true if need to recalculate trigger times
QString mLogFile; // alarm output is to be logged to this URL
float mSoundVolume; // volume for sound file (range 0 - 1), or < 0 for unspecified
float mFadeVolume; // initial volume for sound file (range 0 - 1), or < 0 for no fade
int mFadeSeconds; // fade time (seconds) for sound file, or 0 if none
int mRepeatSoundPause; // seconds to pause between sound file repetitions, or -1 if no repetition
int mLateCancel; // how many minutes late will cancel the alarm, or 0 for no cancellation
mutable const KHolidays::HolidayRegion*
mExcludeHolidays; // non-null to not trigger alarms on holidays (= mHolidays when trigger calculated)
mutable int mWorkTimeOnly; // non-zero to trigger alarm only during working hours (= mWorkTimeIndex when trigger calculated)
KAEvent::SubAction mActionSubType; // sub-action type for the event's main alarm
CalEvent::Type mCategory; // event category (active, archived, template, ...)
KAEvent::ExtraActionOptions mExtraActionOptions;// options for pre- or post-alarm actions
#ifndef KALARMCAL_USE_KRESOURCES
KACalendar::Compat mCompatibility; // event's storage format compatibility
bool mReadOnly; // event is read-only in its original calendar file
#endif
bool mConfirmAck; // alarm acknowledgement requires confirmation by user
bool mUseDefaultFont; // use default message font, not mFont
bool mCommandScript; // the command text is a script, not a shell command line
bool mCommandXterm; // command alarm is to be executed in a terminal window
bool mCommandDisplay; // command output is to be displayed in an alarm window
bool mEmailBcc; // blind copy the email to the user
bool mBeep; // whether to beep when the alarm is displayed
bool mSpeak; // whether to speak the message when the alarm is displayed
bool mCopyToKOrganizer; // KOrganizer should hold a copy of the event
bool mReminderOnceOnly; // the reminder is output only for the first recurrence
bool mAutoClose; // whether to close the alarm window after the late-cancel period
bool mMainExpired; // main alarm has expired (in which case a deferral alarm will exist)
bool mRepeatAtLogin; // whether to repeat the alarm at every login
bool mArchiveRepeatAtLogin; // if now archived, original event was repeat-at-login
bool mArchive; // event has triggered in the past, so archive it when closed
bool mDisplaying; // whether the alarm is currently being displayed (i.e. in displaying calendar)
bool mDisplayingDefer; // show Defer button (applies to displaying calendar only)
bool mDisplayingEdit; // show Edit button (applies to displaying calendar only)
bool mEnabled; // false if event is disabled
public:
static const QByteArray FLAGS_PROPERTY;
static const QString DATE_ONLY_FLAG;
static const QString EMAIL_BCC_FLAG;
static const QString CONFIRM_ACK_FLAG;
static const QString KORGANIZER_FLAG;
static const QString EXCLUDE_HOLIDAYS_FLAG;
static const QString WORK_TIME_ONLY_FLAG;
static const QString REMINDER_ONCE_FLAG;
static const QString DEFER_FLAG;
static const QString LATE_CANCEL_FLAG;
static const QString AUTO_CLOSE_FLAG;
static const QString TEMPL_AFTER_TIME_FLAG;
static const QString KMAIL_SERNUM_FLAG;
static const QString ARCHIVE_FLAG;
static const QByteArray NEXT_RECUR_PROPERTY;
static const QByteArray REPEAT_PROPERTY;
static const QByteArray LOG_PROPERTY;
static const QString xtermURL;
static const QString displayURL;
static const QByteArray TYPE_PROPERTY;
static const QString FILE_TYPE;
static const QString AT_LOGIN_TYPE;
static const QString REMINDER_TYPE;
static const QString REMINDER_ONCE_TYPE;
static const QString TIME_DEFERRAL_TYPE;
static const QString DATE_DEFERRAL_TYPE;
static const QString DISPLAYING_TYPE;
static const QString PRE_ACTION_TYPE;
static const QString POST_ACTION_TYPE;
static const QString SOUND_REPEAT_TYPE;
static const QByteArray NEXT_REPEAT_PROPERTY;
static const QString HIDDEN_REMINDER_FLAG;
static const QByteArray FONT_COLOUR_PROPERTY;
static const QByteArray VOLUME_PROPERTY;
static const QString EMAIL_ID_FLAG;
static const QString SPEAK_FLAG;
static const QString EXEC_ON_DEFERRAL_FLAG;
static const QString CANCEL_ON_ERROR_FLAG;
static const QString DONT_SHOW_ERROR_FLAG;
static const QString DISABLED_STATUS;
static const QString DISP_DEFER;
static const QString DISP_EDIT;
static const QString CMD_ERROR_VALUE;
static const QString CMD_ERROR_PRE_VALUE;
static const QString CMD_ERROR_POST_VALUE;
static const QString SC;
};
//=============================================================================
// KAlarm version which first used the current calendar/event format.
// If this changes, KAEvent::convertKCalEvents() must be changed correspondingly.
// The string version is the KAlarm version string used in the calendar file.
QByteArray KAEvent::currentCalendarVersionString() { return QByteArray("2.7.0"); }
int KAEvent::currentCalendarVersion() { return Version(2,7,0); }
// Custom calendar properties.
// Note that all custom property names are prefixed with X-KDE-KALARM- in the calendar file.
// Event properties
const QByteArray KAEventPrivate::FLAGS_PROPERTY("FLAGS"); // X-KDE-KALARM-FLAGS property
const QString KAEventPrivate::DATE_ONLY_FLAG = QLatin1String("DATE");
const QString KAEventPrivate::EMAIL_BCC_FLAG = QLatin1String("BCC");
const QString KAEventPrivate::CONFIRM_ACK_FLAG = QLatin1String("ACKCONF");
const QString KAEventPrivate::KORGANIZER_FLAG = QLatin1String("KORG");
const QString KAEventPrivate::EXCLUDE_HOLIDAYS_FLAG = QLatin1String("EXHOLIDAYS");
const QString KAEventPrivate::WORK_TIME_ONLY_FLAG = QLatin1String("WORKTIME");
const QString KAEventPrivate::REMINDER_ONCE_FLAG = QLatin1String("ONCE");
const QString KAEventPrivate::DEFER_FLAG = QLatin1String("DEFER"); // default defer interval for this alarm
const QString KAEventPrivate::LATE_CANCEL_FLAG = QLatin1String("LATECANCEL");
const QString KAEventPrivate::AUTO_CLOSE_FLAG = QLatin1String("LATECLOSE");
const QString KAEventPrivate::TEMPL_AFTER_TIME_FLAG = QLatin1String("TMPLAFTTIME");
const QString KAEventPrivate::KMAIL_SERNUM_FLAG = QLatin1String("KMAIL");
const QString KAEventPrivate::ARCHIVE_FLAG = QLatin1String("ARCHIVE");
const QByteArray KAEventPrivate::NEXT_RECUR_PROPERTY("NEXTRECUR"); // X-KDE-KALARM-NEXTRECUR property
const QByteArray KAEventPrivate::REPEAT_PROPERTY("REPEAT"); // X-KDE-KALARM-REPEAT property
const QByteArray KAEventPrivate::LOG_PROPERTY("LOG"); // X-KDE-KALARM-LOG property
const QString KAEventPrivate::xtermURL = QLatin1String("xterm:");
const QString KAEventPrivate::displayURL = QLatin1String("display:");
// - General alarm properties
const QByteArray KAEventPrivate::TYPE_PROPERTY("TYPE"); // X-KDE-KALARM-TYPE property
const QString KAEventPrivate::FILE_TYPE = QLatin1String("FILE");
const QString KAEventPrivate::AT_LOGIN_TYPE = QLatin1String("LOGIN");
const QString KAEventPrivate::REMINDER_TYPE = QLatin1String("REMINDER");
const QString KAEventPrivate::TIME_DEFERRAL_TYPE = QLatin1String("DEFERRAL");
const QString KAEventPrivate::DATE_DEFERRAL_TYPE = QLatin1String("DATE_DEFERRAL");
const QString KAEventPrivate::DISPLAYING_TYPE = QLatin1String("DISPLAYING"); // used only in displaying calendar
const QString KAEventPrivate::PRE_ACTION_TYPE = QLatin1String("PRE");
const QString KAEventPrivate::POST_ACTION_TYPE = QLatin1String("POST");
const QString KAEventPrivate::SOUND_REPEAT_TYPE = QLatin1String("SOUNDREPEAT");
const QByteArray KAEventPrivate::NEXT_REPEAT_PROPERTY("NEXTREPEAT"); // X-KDE-KALARM-NEXTREPEAT property
const QString KAEventPrivate::HIDDEN_REMINDER_FLAG = QLatin1String("HIDE");
// - Display alarm properties
const QByteArray KAEventPrivate::FONT_COLOUR_PROPERTY("FONTCOLOR"); // X-KDE-KALARM-FONTCOLOR property
// - Email alarm properties
const QString KAEventPrivate::EMAIL_ID_FLAG = QLatin1String("EMAILID");
// - Audio alarm properties
const QByteArray KAEventPrivate::VOLUME_PROPERTY("VOLUME"); // X-KDE-KALARM-VOLUME property
const QString KAEventPrivate::SPEAK_FLAG = QLatin1String("SPEAK");
// - Command alarm properties
const QString KAEventPrivate::EXEC_ON_DEFERRAL_FLAG = QLatin1String("EXECDEFER");
const QString KAEventPrivate::CANCEL_ON_ERROR_FLAG = QLatin1String("ERRCANCEL");
const QString KAEventPrivate::DONT_SHOW_ERROR_FLAG = QLatin1String("ERRNOSHOW");
// Event status strings
const QString KAEventPrivate::DISABLED_STATUS = QLatin1String("DISABLED");
// Displaying event ID identifier
const QString KAEventPrivate::DISP_DEFER = QLatin1String("DEFER");
const QString KAEventPrivate::DISP_EDIT = QLatin1String("EDIT");
// Command error strings
#ifdef KALARMCAL_USE_KRESOURCES
QString KAEventPrivate::mCmdErrConfigGroup = QLatin1String("CommandErrors");
#endif
const QString KAEventPrivate::CMD_ERROR_VALUE = QLatin1String("MAIN");
const QString KAEventPrivate::CMD_ERROR_PRE_VALUE = QLatin1String("PRE");
const QString KAEventPrivate::CMD_ERROR_POST_VALUE = QLatin1String("POST");
const QString KAEventPrivate::SC = QLatin1String(";");
QFont KAEventPrivate::mDefaultFont;
const KHolidays::HolidayRegion* KAEventPrivate::mHolidays = 0;
QBitArray KAEventPrivate::mWorkDays(7);
QTime KAEventPrivate::mWorkDayStart(9, 0, 0);
QTime KAEventPrivate::mWorkDayEnd(17, 0, 0);
int KAEventPrivate::mWorkTimeIndex = 1;
#ifndef KALARMCAL_USE_KRESOURCES
static void setProcedureAlarm(const Alarm::Ptr&, const QString& commandLine);
#else
static void setProcedureAlarm(Alarm*, const QString& commandLine);
#endif
static QString reminderToString(int minutes);
/*=============================================================================
= Class KAEvent
= Corresponds to a KCal::Event instance.
=============================================================================*/
inline void KAEventPrivate::set_deferral(DeferType type)
{
if (type)
{
if (mDeferral == NO_DEFERRAL)
++mAlarmCount;
}
else
{
if (mDeferral != NO_DEFERRAL)
--mAlarmCount;
}
mDeferral = type;
}
inline void KAEventPrivate::activate_reminder(bool activate)
{
if (activate && mReminderActive != ACTIVE_REMINDER && mReminderMinutes)
{
if (mReminderActive == NO_REMINDER)
++mAlarmCount;
mReminderActive = ACTIVE_REMINDER;
}
else if (!activate && mReminderActive != NO_REMINDER)
{
mReminderActive = NO_REMINDER;
mReminderAfterTime = DateTime();
--mAlarmCount;
}
}
K_GLOBAL_STATIC_WITH_ARGS(QSharedDataPointer<KAEventPrivate>,
emptyKAEventPrivate, (new KAEventPrivate))
KAEvent::KAEvent()
: d(*emptyKAEventPrivate)
{ }
KAEventPrivate::KAEventPrivate()
:
#ifdef KALARMCAL_USE_KRESOURCES
mResource(0),
#endif
mCommandError(KAEvent::CMD_NO_ERROR),
#ifndef KALARMCAL_USE_KRESOURCES
mItemId(-1),
mCollectionId(-1),
#endif
mReminderMinutes(0),
mReminderActive(NO_REMINDER),
mRevision(0),
mRecurrence(0),
mNextRepeat(0),
mAlarmCount(0),
mDeferral(NO_DEFERRAL),
mChangeCount(0),
mTriggerChanged(false),
mLateCancel(0),
mExcludeHolidays(0),
mWorkTimeOnly(0),
mCategory(CalEvent::EMPTY),
#ifndef KALARMCAL_USE_KRESOURCES
mCompatibility(KACalendar::Current),
mReadOnly(false),
#endif
mConfirmAck(false),
mEmailBcc(false),
mBeep(false),
mAutoClose(false),
mRepeatAtLogin(false),
mDisplaying(false)
{ }
KAEvent::KAEvent(const KDateTime& dt, const QString& message, const QColor& bg, const QColor& fg, const QFont& f,
SubAction action, int lateCancel, Flags flags, bool changesPending)
: d(new KAEventPrivate(dt, message, bg, fg, f, action, lateCancel, flags, changesPending))
{
}
KAEventPrivate::KAEventPrivate(const KDateTime& dt, const QString& message, const QColor& bg, const QColor& fg, const QFont& f,
KAEvent::SubAction action, int lateCancel, KAEvent::Flags flags, bool changesPending)
: mRevision(0),
mRecurrence(0)
{
set(dt, message, bg, fg, f, action, lateCancel, flags, changesPending);
}
#ifndef KALARMCAL_USE_KRESOURCES
KAEvent::KAEvent(const Event::Ptr& e)
#else
KAEvent::KAEvent(const Event* e)
#endif
: d(new KAEventPrivate(e))
{
}
#ifndef KALARMCAL_USE_KRESOURCES
KAEventPrivate::KAEventPrivate(const Event::Ptr& e)
#else
KAEventPrivate::KAEventPrivate(const Event* e)
#endif
: mRecurrence(0)
{
set(e);
}
KAEventPrivate::KAEventPrivate(const KAEventPrivate& e)
: QSharedData(e),
mRecurrence(0)
{
copy(e);
}
KAEvent::KAEvent(const KAEvent& other)
: d(other.d)
{ }
KAEvent::~KAEvent()
{ }
KAEvent& KAEvent::operator=(const KAEvent& other)
{
if (&other != this)
d = other.d;
return *this;
}
/******************************************************************************
* Copies the data from another instance.
*/
void KAEventPrivate::copy(const KAEventPrivate& event)
{
#ifdef KALARMCAL_USE_KRESOURCES
mResource = event.mResource;
#endif
mAllTrigger = event.mAllTrigger;
mMainTrigger = event.mMainTrigger;
mAllWorkTrigger = event.mAllWorkTrigger;
mMainWorkTrigger = event.mMainWorkTrigger;
mCommandError = event.mCommandError;
mEventID = event.mEventID;
mTemplateName = event.mTemplateName;
#ifndef KALARMCAL_USE_KRESOURCES
mCustomProperties = event.mCustomProperties;
mItemId = event.mItemId;
mCollectionId = event.mCollectionId;
#else
mOriginalResourceId = event.mOriginalResourceId;
#endif
mText = event.mText;
mAudioFile = event.mAudioFile;
mPreAction = event.mPreAction;
mPostAction = event.mPostAction;
mStartDateTime = event.mStartDateTime;
mCreatedDateTime = event.mCreatedDateTime;
mNextMainDateTime = event.mNextMainDateTime;
mAtLoginDateTime = event.mAtLoginDateTime;
mDeferralTime = event.mDeferralTime;
mDisplayingTime = event.mDisplayingTime;
mDisplayingFlags = event.mDisplayingFlags;
mReminderMinutes = event.mReminderMinutes;
mReminderAfterTime = event.mReminderAfterTime;
mReminderActive = event.mReminderActive;
mDeferDefaultMinutes = event.mDeferDefaultMinutes;
mDeferDefaultDateOnly = event.mDeferDefaultDateOnly;
mRevision = event.mRevision;
mRepetition = event.mRepetition;
mNextRepeat = event.mNextRepeat;
mAlarmCount = event.mAlarmCount;
mDeferral = event.mDeferral;
mKMailSerialNumber = event.mKMailSerialNumber;
mTemplateAfterTime = event.mTemplateAfterTime;
mBgColour = event.mBgColour;
mFgColour = event.mFgColour;
mFont = event.mFont;
mEmailFromIdentity = event.mEmailFromIdentity;
mEmailAddresses = event.mEmailAddresses;
mEmailSubject = event.mEmailSubject;
mEmailAttachments = event.mEmailAttachments;
mLogFile = event.mLogFile;
mSoundVolume = event.mSoundVolume;
mFadeVolume = event.mFadeVolume;
mFadeSeconds = event.mFadeSeconds;
mRepeatSoundPause = event.mRepeatSoundPause;
mLateCancel = event.mLateCancel;
mExcludeHolidays = event.mExcludeHolidays;
mWorkTimeOnly = event.mWorkTimeOnly;
mActionSubType = event.mActionSubType;
mCategory = event.mCategory;
mExtraActionOptions = event.mExtraActionOptions;
#ifndef KALARMCAL_USE_KRESOURCES
mCompatibility = event.mCompatibility;
mReadOnly = event.mReadOnly;
#endif
mConfirmAck = event.mConfirmAck;
mUseDefaultFont = event.mUseDefaultFont;
mCommandScript = event.mCommandScript;
mCommandXterm = event.mCommandXterm;
mCommandDisplay = event.mCommandDisplay;
mEmailBcc = event.mEmailBcc;
mBeep = event.mBeep;
mSpeak = event.mSpeak;
mCopyToKOrganizer = event.mCopyToKOrganizer;
mReminderOnceOnly = event.mReminderOnceOnly;
mAutoClose = event.mAutoClose;
mMainExpired = event.mMainExpired;
mRepeatAtLogin = event.mRepeatAtLogin;
mArchiveRepeatAtLogin = event.mArchiveRepeatAtLogin;
mArchive = event.mArchive;
mDisplaying = event.mDisplaying;
mDisplayingDefer = event.mDisplayingDefer;
mDisplayingEdit = event.mDisplayingEdit;
mEnabled = event.mEnabled;
mChangeCount = 0;
mTriggerChanged = event.mTriggerChanged;
delete mRecurrence;
if (event.mRecurrence)
mRecurrence = new KARecurrence(*event.mRecurrence);
else
mRecurrence = 0;
}
#ifndef KALARMCAL_USE_KRESOURCES
void KAEvent::set(const Event::Ptr& e)
#else
void KAEvent::set(const Event* e)
#endif
{
d->set(e);
}
/******************************************************************************
* Initialise the KAEventPrivate from a KCal::Event.
*/
#ifndef KALARMCAL_USE_KRESOURCES
void KAEventPrivate::set(const Event::Ptr& event)
#else
void KAEventPrivate::set(const Event* event)
#endif
{
startChanges();
// Extract status from the event
mCommandError = KAEvent::CMD_NO_ERROR;
#ifdef KALARMCAL_USE_KRESOURCES
mResource = 0;
#endif
mEventID = event->uid();
mRevision = event->revision();
mTemplateName.clear();
mLogFile.clear();
#ifndef KALARMCAL_USE_KRESOURCES
mItemId = -1;
mCollectionId = -1;
#else
mOriginalResourceId.clear();
#endif
mTemplateAfterTime = -1;
mBeep = false;
mSpeak = false;
mEmailBcc = false;
mCommandXterm = false;
mCommandDisplay = false;
mCopyToKOrganizer = false;
mConfirmAck = false;
mArchive = false;
mReminderOnceOnly = false;
mAutoClose = false;
mArchiveRepeatAtLogin = false;
mDisplayingDefer = false;
mDisplayingEdit = false;
mDeferDefaultDateOnly = false;
mReminderActive = NO_REMINDER;
mReminderMinutes = 0;
mDeferDefaultMinutes = 0;
mLateCancel = 0;
mKMailSerialNumber = 0;
mExcludeHolidays = 0;
mWorkTimeOnly = 0;
mChangeCount = 0;
mBgColour = QColor(255, 255, 255); // missing/invalid colour - return white background
mFgColour = QColor(0, 0, 0); // and black foreground
#ifndef KALARMCAL_USE_KRESOURCES
mCompatibility = KACalendar::Current;
mReadOnly = event->isReadOnly();
#endif
mUseDefaultFont = true;
mEnabled = true;
clearRecur();
QString param;
bool ok;
mCategory = CalEvent::status(event, &param);
if (mCategory == CalEvent::DISPLAYING)
{
// It's a displaying calendar event - set values specific to displaying alarms
const QStringList params = param.split(SC, QString::KeepEmptyParts);
int n = params.count();
if (n)
{
#ifndef KALARMCAL_USE_KRESOURCES
const qlonglong id = params[0].toLongLong(&ok);
if (ok)
mCollectionId = id; // original collection ID which contained the event
#else
mOriginalResourceId = params[0];
#endif
for (int i = 1; i < n; ++i)
{
if (params[i] == DISP_DEFER)
mDisplayingDefer = true;
if (params[i] == DISP_EDIT)
mDisplayingEdit = true;
}
}
}
#ifndef KALARMCAL_USE_KRESOURCES
// Store the non-KAlarm custom properties of the event
const QByteArray kalarmKey = "X-KDE-" + KACalendar::APPNAME + '-';
mCustomProperties = event->customProperties();
for (QMap<QByteArray, QString>::Iterator it = mCustomProperties.begin(); it != mCustomProperties.end(); )
{
if (it.key().startsWith(kalarmKey))
it = mCustomProperties.erase(it);
else
++it;
}
#endif
bool dateOnly = false;
QStringList flags = event->customProperty(KACalendar::APPNAME, FLAGS_PROPERTY).split(SC, QString::SkipEmptyParts);
flags << QString() << QString(); // to avoid having to check for end of list
for (int i = 0, end = flags.count() - 1; i < end; ++i)
{
QString flag = flags.at(i);
if (flag == DATE_ONLY_FLAG)
dateOnly = true;
else if (flag == CONFIRM_ACK_FLAG)
mConfirmAck = true;
else if (flag == EMAIL_BCC_FLAG)
mEmailBcc = true;
else if (flag == KORGANIZER_FLAG)
mCopyToKOrganizer = true;
else if (flag == EXCLUDE_HOLIDAYS_FLAG)
mExcludeHolidays = mHolidays;
else if (flag == WORK_TIME_ONLY_FLAG)
mWorkTimeOnly = 1;
else if (flag == KMAIL_SERNUM_FLAG)
{
const unsigned long n = flags.at(i + 1).toULong(&ok);
if (!ok)
continue;
mKMailSerialNumber = n;
++i;
}
else if (flag == KAEventPrivate::ARCHIVE_FLAG)
mArchive = true;
else if (flag == KAEventPrivate::AT_LOGIN_TYPE)
mArchiveRepeatAtLogin = true;
else if (flag == KAEventPrivate::REMINDER_TYPE)
{
flag = flags.at(++i);
if (flag == KAEventPrivate::REMINDER_ONCE_FLAG)
{
mReminderOnceOnly = true;
++i;
}
const int len = flag.length() - 1;
mReminderMinutes = -flag.left(len).toInt(); // -> 0 if conversion fails
switch (flag.at(len).toLatin1())
{
case 'M': break;
case 'H': mReminderMinutes *= 60; break;
case 'D': mReminderMinutes *= 1440; break;
default: mReminderMinutes = 0; break;
}
}
else if (flag == DEFER_FLAG)
{
QString mins = flags.at(i + 1);
if (mins.endsWith(QLatin1Char('D')))
{
mDeferDefaultDateOnly = true;
mins.truncate(mins.length() - 1);
}
const int n = static_cast<int>(mins.toUInt(&ok));
if (!ok)
continue;
mDeferDefaultMinutes = n;
++i;
}
else if (flag == TEMPL_AFTER_TIME_FLAG)
{
const int n = static_cast<int>(flags.at(i + 1).toUInt(&ok));
if (!ok)
continue;
mTemplateAfterTime = n;
++i;
}
else if (flag == LATE_CANCEL_FLAG)
{
mLateCancel = static_cast<int>(flags.at(i + 1).toUInt(&ok));
if (ok)
++i;
if (!ok || !mLateCancel)
mLateCancel = 1; // invalid parameter defaults to 1 minute
}
else if (flag == AUTO_CLOSE_FLAG)
{
mLateCancel = static_cast<int>(flags.at(i + 1).toUInt(&ok));
if (ok)
++i;
if (!ok || !mLateCancel)
mLateCancel = 1; // invalid parameter defaults to 1 minute
mAutoClose = true;
}
}
QString prop = event->customProperty(KACalendar::APPNAME, LOG_PROPERTY);
if (!prop.isEmpty())
{
if (prop == xtermURL)
mCommandXterm = true;
else if (prop == displayURL)
mCommandDisplay = true;
else
mLogFile = prop;
}
prop = event->customProperty(KACalendar::APPNAME, REPEAT_PROPERTY);
if (!prop.isEmpty())
{
// This property is used when the main alarm has expired
const QStringList list = prop.split(QLatin1Char(':'));
if (list.count() >= 2)
{
const int interval = static_cast<int>(list[0].toUInt());
const int count = static_cast<int>(list[1].toUInt());
if (interval && count)
{
if (interval % (24*60))
mRepetition.set(Duration(interval * 60, Duration::Seconds), count);
else
mRepetition.set(Duration(interval / (24*60), Duration::Days), count);
}
}
}
mNextMainDateTime = readDateTime(event, dateOnly, mStartDateTime);
mCreatedDateTime = event->created();
if (dateOnly && !mRepetition.isDaily())
mRepetition.set(Duration(mRepetition.intervalDays(), Duration::Days));
if (mCategory == CalEvent::TEMPLATE)
mTemplateName = event->summary();
#ifndef KALARMCAL_USE_KRESOURCES
if (event->customStatus() == DISABLED_STATUS)
#else
if (event->statusStr() == DISABLED_STATUS)
#endif
mEnabled = false;
// Extract status from the event's alarms.
// First set up defaults.
mActionSubType = KAEvent::MESSAGE;
mMainExpired = true;
mRepeatAtLogin = false;
mDisplaying = false;
mCommandScript = false;
mExtraActionOptions = 0;
mDeferral = NO_DEFERRAL;
mSoundVolume = -1;
mFadeVolume = -1;
mRepeatSoundPause = -1;
mFadeSeconds = 0;
mEmailFromIdentity = 0;
mReminderAfterTime = DateTime();
mText.clear();
mAudioFile.clear();
mPreAction.clear();
mPostAction.clear();
mEmailSubject.clear();
mEmailAddresses.clear();
mEmailAttachments.clear();
// Extract data from all the event's alarms and index the alarms by sequence number
AlarmMap alarmMap;
readAlarms(event, &alarmMap, mCommandDisplay);
// Incorporate the alarms' details into the overall event
mAlarmCount = 0; // initialise as invalid
DateTime alTime;
bool set = false;
bool isEmailText = false;
bool setDeferralTime = false;
Duration deferralOffset;
for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it)
{
const AlarmData& data = it.value();
const DateTime dateTime = data.alarm->hasStartOffset() ? data.alarm->startOffset().end(mNextMainDateTime.effectiveKDateTime()) : data.alarm->time();
switch (data.type)
{
case MAIN_ALARM:
mMainExpired = false;
alTime = dateTime;
alTime.setDateOnly(mStartDateTime.isDateOnly());
if (data.alarm->repeatCount() && data.alarm->snoozeTime())
{
mRepetition.set(data.alarm->snoozeTime(), data.alarm->repeatCount()); // values may be adjusted in setRecurrence()
mNextRepeat = data.nextRepeat;
}
if (data.action != KAAlarm::AUDIO)
break;
// Fall through to AUDIO_ALARM
case AUDIO_ALARM:
mAudioFile = data.cleanText;
mSpeak = data.speak && mAudioFile.isEmpty();
mBeep = !mSpeak && mAudioFile.isEmpty();
mSoundVolume = (!mBeep && !mSpeak) ? data.soundVolume : -1;
mFadeVolume = (mSoundVolume >= 0 && data.fadeSeconds > 0) ? data.fadeVolume : -1;
mFadeSeconds = (mFadeVolume >= 0) ? data.fadeSeconds : 0;
mRepeatSoundPause = (!mBeep && !mSpeak) ? data.repeatSoundPause : -1;
break;
case AT_LOGIN_ALARM:
mRepeatAtLogin = true;
mAtLoginDateTime = dateTime.kDateTime();
alTime = mAtLoginDateTime;
break;
case REMINDER_ALARM:
// N.B. there can be a start offset but no valid date/time (e.g. in template)
if (data.alarm->startOffset().asSeconds() / 60)
{
mReminderActive = ACTIVE_REMINDER;
if (mReminderMinutes < 0)
{
mReminderAfterTime = dateTime; // the reminder is AFTER the main alarm
mReminderAfterTime.setDateOnly(dateOnly);
if (data.hiddenReminder)
mReminderActive = HIDDEN_REMINDER;
}
}
break;
case DEFERRED_REMINDER_ALARM:
case DEFERRED_ALARM:
mDeferral = (data.type == DEFERRED_REMINDER_ALARM) ? REMINDER_DEFERRAL : NORMAL_DEFERRAL;
mDeferralTime = dateTime;
if (!data.timedDeferral)
mDeferralTime.setDateOnly(true);
if (data.alarm->hasStartOffset())
deferralOffset = data.alarm->startOffset();
break;
case DISPLAYING_ALARM:
{
mDisplaying = true;
mDisplayingFlags = data.displayingFlags;
const bool dateOnly = (mDisplayingFlags & DEFERRAL) ? !(mDisplayingFlags & TIMED_FLAG)
: mStartDateTime.isDateOnly();
mDisplayingTime = dateTime;
mDisplayingTime.setDateOnly(dateOnly);
alTime = mDisplayingTime;
break;
}
case PRE_ACTION_ALARM:
mPreAction = data.cleanText;
mExtraActionOptions = data.extraActionOptions;
break;
case POST_ACTION_ALARM:
mPostAction = data.cleanText;
break;
case INVALID_ALARM:
default:
break;
}
bool noSetNextTime = false;
switch (data.type)
{
case DEFERRED_REMINDER_ALARM:
case DEFERRED_ALARM:
if (!set)
{
// The recurrence has to be evaluated before we can
// calculate the time of a deferral alarm.
setDeferralTime = true;
noSetNextTime = true;
}
// fall through to REMINDER_ALARM
case REMINDER_ALARM:
case AT_LOGIN_ALARM:
case DISPLAYING_ALARM:
if (!set && !noSetNextTime)
mNextMainDateTime = alTime;
// fall through to MAIN_ALARM
case MAIN_ALARM:
// Ensure that the basic fields are set up even if there is no main
// alarm in the event (if it has expired and then been deferred)
if (!set)
{
mActionSubType = static_cast<KAEvent::SubAction>(data.action);
mText = (mActionSubType == KAEvent::COMMAND) ? data.cleanText.trimmed() : data.cleanText;
switch (data.action)
{
case KAAlarm::COMMAND:
mCommandScript = data.commandScript;
if (!mCommandDisplay)
break;
// fall through to MESSAGE
case KAAlarm::MESSAGE:
mFont = data.font;
mUseDefaultFont = data.defaultFont;
if (data.isEmailText)
isEmailText = true;
// fall through to FILE
case KAAlarm::FILE:
mBgColour = data.bgColour;
mFgColour = data.fgColour;
break;
case KAAlarm::EMAIL:
mEmailFromIdentity = data.emailFromId;
mEmailAddresses = data.alarm->mailAddresses();
mEmailSubject = data.alarm->mailSubject();
mEmailAttachments = data.alarm->mailAttachments();
break;
case KAAlarm::AUDIO:
// Already mostly handled above
mRepeatSoundPause = data.repeatSoundPause;
break;
default:
break;
}
set = true;
}
if (data.action == KAAlarm::FILE && mActionSubType == KAEvent::MESSAGE)
mActionSubType = KAEvent::FILE;
++mAlarmCount;
break;
case AUDIO_ALARM:
case PRE_ACTION_ALARM:
case POST_ACTION_ALARM:
case INVALID_ALARM:
default:
break;
}
}
if (!isEmailText)
mKMailSerialNumber = 0;
Recurrence* recur = event->recurrence();
if (recur && recur->recurs())
{
const int nextRepeat = mNextRepeat; // setRecurrence() clears mNextRepeat
setRecurrence(*recur);
if (nextRepeat <= mRepetition.count())
mNextRepeat = nextRepeat;
}
else if (mRepetition)
{
// Convert a repetition with no recurrence into a recurrence
if (mRepetition.isDaily())
setRecur(RecurrenceRule::rDaily, mRepetition.intervalDays(), mRepetition.count() + 1, QDate());
else
setRecur(RecurrenceRule::rMinutely, mRepetition.intervalMinutes(), mRepetition.count() + 1, KDateTime());
mRepetition.set(0, 0);
mTriggerChanged = true;
}
if (mRepeatAtLogin)
{
mArchiveRepeatAtLogin = false;
if (mReminderMinutes > 0)
{
mReminderMinutes = 0; // pre-alarm reminder not allowed for at-login alarm
mReminderActive = NO_REMINDER;
}
setRepeatAtLoginTrue(false); // clear other incompatible statuses
}
if (mMainExpired && deferralOffset && checkRecur() != KARecurrence::NO_RECUR)
{
// Adjust the deferral time for an expired recurrence, since the
// offset is relative to the first actual occurrence.
DateTime dt = mRecurrence->getNextDateTime(mStartDateTime.addDays(-1).kDateTime());
dt.setDateOnly(mStartDateTime.isDateOnly());
if (mDeferralTime.isDateOnly())
{
mDeferralTime = deferralOffset.end(dt.kDateTime());
mDeferralTime.setDateOnly(true);
}
else
mDeferralTime = deferralOffset.end(dt.effectiveKDateTime());
}
if (mDeferral != NO_DEFERRAL)
{
if (setDeferralTime)
mNextMainDateTime = mDeferralTime;
}
mTriggerChanged = true;
endChanges();
}
void KAEvent::set(const KDateTime& dt, const QString& message, const QColor& bg, const QColor& fg,
const QFont& f, SubAction act, int lateCancel, Flags flags, bool changesPending)
{
d->set(dt, message, bg, fg, f, act, lateCancel, flags, changesPending);
}
/******************************************************************************
* Initialise the instance with the specified parameters.
*/
void KAEventPrivate::set(const KDateTime& dateTime, const QString& text, const QColor& bg, const QColor& fg,
const QFont& font, KAEvent::SubAction action, int lateCancel, KAEvent::Flags flags,
bool changesPending)
{
clearRecur();
mStartDateTime = dateTime;
mStartDateTime.setDateOnly(flags & KAEvent::ANY_TIME);
mNextMainDateTime = mStartDateTime;
switch (action)
{
case KAEvent::MESSAGE:
case KAEvent::FILE:
case KAEvent::COMMAND:
case KAEvent::EMAIL:
case KAEvent::AUDIO:
mActionSubType = static_cast<KAEvent::SubAction>(action);
break;
default:
mActionSubType = KAEvent::MESSAGE;
break;
}
mEventID.clear();
mTemplateName.clear();
#ifndef KALARMCAL_USE_KRESOURCES
mItemId = -1;
mCollectionId = -1;
#else
mResource = 0;
mOriginalResourceId.clear();
#endif
mPreAction.clear();
mPostAction.clear();
mText = (mActionSubType == KAEvent::COMMAND) ? text.trimmed()
: (mActionSubType == KAEvent::AUDIO) ? QString() : text;
mCategory = CalEvent::ACTIVE;
mAudioFile = (mActionSubType == KAEvent::AUDIO) ? text : QString();
mSoundVolume = -1;
mFadeVolume = -1;
mTemplateAfterTime = -1;
mFadeSeconds = 0;
mBgColour = bg;
mFgColour = fg;
mFont = font;
mAlarmCount = 1;
mLateCancel = lateCancel; // do this before setting flags
mDeferral = NO_DEFERRAL; // do this before setting flags
mStartDateTime.setDateOnly(flags & KAEvent::ANY_TIME);
set_deferral((flags & DEFERRAL) ? NORMAL_DEFERRAL : NO_DEFERRAL);
mRepeatAtLogin = flags & KAEvent::REPEAT_AT_LOGIN;
mConfirmAck = flags & KAEvent::CONFIRM_ACK;
mUseDefaultFont = flags & KAEvent::DEFAULT_FONT;
mCommandScript = flags & KAEvent::SCRIPT;
mCommandXterm = flags & KAEvent::EXEC_IN_XTERM;
mCommandDisplay = flags & KAEvent::DISPLAY_COMMAND;
mCopyToKOrganizer = flags & KAEvent::COPY_KORGANIZER;
mExcludeHolidays = (flags & KAEvent::EXCL_HOLIDAYS) ? mHolidays : 0;
mWorkTimeOnly = flags & KAEvent::WORK_TIME_ONLY;
mEmailBcc = flags & KAEvent::EMAIL_BCC;
mEnabled = !(flags & KAEvent::DISABLED);
mDisplaying = flags & DISPLAYING_;
mReminderOnceOnly = flags & KAEvent::REMINDER_ONCE;
mAutoClose = (flags & KAEvent::AUTO_CLOSE) && mLateCancel;
mRepeatSoundPause = (flags & KAEvent::REPEAT_SOUND) ? 0 : -1;
mSpeak = (flags & KAEvent::SPEAK) && action != KAEvent::AUDIO;
mBeep = (flags & KAEvent::BEEP) && action != KAEvent::AUDIO && !mSpeak;
if (mRepeatAtLogin) // do this after setting other flags
{
++mAlarmCount;
setRepeatAtLoginTrue(false);
}
mKMailSerialNumber = 0;
mReminderMinutes = 0;
mDeferDefaultMinutes = 0;
mDeferDefaultDateOnly = false;
mArchiveRepeatAtLogin = false;
mReminderActive = NO_REMINDER;
mDisplaying = false;
mMainExpired = false;
mDisplayingDefer = false;
mDisplayingEdit = false;
mArchive = false;
mReminderAfterTime = DateTime();
mExtraActionOptions = 0;
#ifndef KALARMCAL_USE_KRESOURCES
mCompatibility = KACalendar::Current;
mReadOnly = false;
#endif
mCommandError = KAEvent::CMD_NO_ERROR;
mChangeCount = changesPending ? 1 : 0;
mTriggerChanged = true;
}
/******************************************************************************
* Update an existing KCal::Event with the KAEventPrivate data.
* If 'setCustomProperties' is true, all the KCal::Event's existing custom
* properties are cleared and replaced with the KAEvent's custom properties. If
* false, the KCal::Event's non-KAlarm custom properties are left untouched.
*/
#ifndef KALARMCAL_USE_KRESOURCES
bool KAEvent::updateKCalEvent(const KCalCore::Event::Ptr& e, UidAction u, bool setCustomProperties) const
{
return d->updateKCalEvent(e, u, setCustomProperties);
}
#else
bool KAEvent::updateKCalEvent(KCal::Event* e, UidAction u) const
{
return d->updateKCalEvent(e, u);
}
#endif
#ifndef KALARMCAL_USE_KRESOURCES
bool KAEventPrivate::updateKCalEvent(const Event::Ptr& ev, KAEvent::UidAction uidact, bool setCustomProperties) const
#else
bool KAEventPrivate::updateKCalEvent(Event* ev, KAEvent::UidAction uidact) const
#endif
{
// If it's an archived event, the event start date/time will be adjusted to its original
// value instead of its next occurrence, and the expired main alarm will be reinstated.
const bool archived = (mCategory == CalEvent::ARCHIVED);
if (!ev
|| (uidact == KAEvent::UID_CHECK && !mEventID.isEmpty() && mEventID != ev->uid())
|| (!mAlarmCount && (!archived || !mMainExpired)))
return false;
ev->startUpdates(); // prevent multiple update notifications
checkRecur(); // ensure recurrence/repetition data is consistent
const bool readOnly = ev->isReadOnly();
if (uidact == KAEvent::UID_SET)
ev->setUid(mEventID);
#ifndef KALARMCAL_USE_KRESOURCES
ev->setReadOnly(mReadOnly);
#else
ev->setReadOnly(false);
#endif
ev->setTransparency(Event::Transparent);
// Set up event-specific data
// Set up custom properties.
#ifndef KALARMCAL_USE_KRESOURCES
if (setCustomProperties)
ev->setCustomProperties(mCustomProperties);
#endif
ev->removeCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY);
ev->removeCustomProperty(KACalendar::APPNAME, NEXT_RECUR_PROPERTY);
ev->removeCustomProperty(KACalendar::APPNAME, REPEAT_PROPERTY);
ev->removeCustomProperty(KACalendar::APPNAME, LOG_PROPERTY);
QString param;
if (mCategory == CalEvent::DISPLAYING)
{
#ifndef KALARMCAL_USE_KRESOURCES
param = QString::number(mCollectionId); // original collection ID which contained the event
#else
param = mOriginalResourceId;
#endif
if (mDisplayingDefer)
param += SC + DISP_DEFER;
if (mDisplayingEdit)
param += SC + DISP_EDIT;
}
#ifndef KALARMCAL_USE_KRESOURCES
CalEvent::setStatus(ev, mCategory, param);
#else
CalEvent::setStatus(ev, mCategory, param);
#endif
QStringList flags;
if (mStartDateTime.isDateOnly())
flags += DATE_ONLY_FLAG;
if (mConfirmAck)
flags += CONFIRM_ACK_FLAG;
if (mEmailBcc)
flags += EMAIL_BCC_FLAG;
if (mCopyToKOrganizer)
flags += KORGANIZER_FLAG;
if (mExcludeHolidays)
flags += EXCLUDE_HOLIDAYS_FLAG;
if (mWorkTimeOnly)
flags += WORK_TIME_ONLY_FLAG;
if (mLateCancel)
(flags += (mAutoClose ? AUTO_CLOSE_FLAG : LATE_CANCEL_FLAG)) += QString::number(mLateCancel);
if (mReminderMinutes)
{
flags += REMINDER_TYPE;
if (mReminderOnceOnly)
flags += REMINDER_ONCE_FLAG;
flags += reminderToString(-mReminderMinutes);
}
if (mDeferDefaultMinutes)
{
QString param = QString::number(mDeferDefaultMinutes);
if (mDeferDefaultDateOnly)
param += QLatin1Char('D');
(flags += DEFER_FLAG) += param;
}
if (!mTemplateName.isEmpty() && mTemplateAfterTime >= 0)
(flags += TEMPL_AFTER_TIME_FLAG) += QString::number(mTemplateAfterTime);
if (mKMailSerialNumber)
(flags += KMAIL_SERNUM_FLAG) += QString::number(mKMailSerialNumber);
if (mArchive && !archived)
{
flags += ARCHIVE_FLAG;
if (mArchiveRepeatAtLogin)
flags += AT_LOGIN_TYPE;
}
if (!flags.isEmpty())
ev->setCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY, flags.join(SC));
if (mCommandXterm)
ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, xtermURL);
else if (mCommandDisplay)
ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, displayURL);
else if (!mLogFile.isEmpty())
ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, mLogFile);
ev->setCustomStatus(mEnabled ? QString() : DISABLED_STATUS);
ev->setRevision(mRevision);
ev->clearAlarms();
/* Always set DTSTART as date/time, and use the category "DATE" to indicate
* a date-only event, instead of calling setAllDay(). This is necessary to
* allow a time zone to be specified for a date-only event. Also, KAlarm
* allows the alarm to float within the 24-hour period defined by the
* start-of-day time (which is user-dependent and therefore can't be
* written into the calendar) rather than midnight to midnight, and there
* is no RFC2445 conformant way to specify this.
* RFC2445 states that alarm trigger times specified in absolute terms
* (rather than relative to DTSTART or DTEND) can only be specified as a
* UTC DATE-TIME value. So always use a time relative to DTSTART instead of
* an absolute time.
*/
ev->setDtStart(mStartDateTime.calendarKDateTime());
ev->setAllDay(false);
ev->setDtEnd(KDateTime());
const DateTime dtMain = archived ? mStartDateTime : mNextMainDateTime;
int ancillaryType = 0; // 0 = invalid, 1 = time, 2 = offset
DateTime ancillaryTime; // time for ancillary alarms (pre-action, extra audio, etc)
int ancillaryOffset = 0; // start offset for ancillary alarms
if (!mMainExpired || archived)
{
/* The alarm offset must always be zero for the main alarm. To determine
* which recurrence is due, the property X-KDE-KALARM_NEXTRECUR is used.
* If the alarm offset was non-zero, exception dates and rules would not
* work since they apply to the event time, not the alarm time.
*/
if (!archived && checkRecur() != KARecurrence::NO_RECUR)
{
QDateTime dt = mNextMainDateTime.kDateTime().toTimeSpec(mStartDateTime.timeSpec()).dateTime();
ev->setCustomProperty(KACalendar::APPNAME, NEXT_RECUR_PROPERTY,
dt.toString(mNextMainDateTime.isDateOnly() ? QLatin1String("yyyyMMdd") : QLatin1String("yyyyMMddThhmmss")));
}
// Add the main alarm
initKCalAlarm(ev, 0, QStringList(), MAIN_ALARM);
ancillaryOffset = 0;
ancillaryType = dtMain.isValid() ? 2 : 0;
}
else if (mRepetition)
{
// Alarm repetition is normally held in the main alarm, but since
// the main alarm has expired, store in a custom property.
const QString param = QString::fromLatin1("%1:%2").arg(mRepetition.intervalMinutes()).arg(mRepetition.count());
ev->setCustomProperty(KACalendar::APPNAME, REPEAT_PROPERTY, param);
}
// Add subsidiary alarms
if (mRepeatAtLogin || (mArchiveRepeatAtLogin && archived))
{
DateTime dtl;
if (mArchiveRepeatAtLogin)
dtl = mStartDateTime.calendarKDateTime().addDays(-1);
else if (mAtLoginDateTime.isValid())
dtl = mAtLoginDateTime;
else if (mStartDateTime.isDateOnly())
dtl = DateTime(KDateTime::currentLocalDate().addDays(-1), mStartDateTime.timeSpec());
else
dtl = KDateTime::currentUtcDateTime();
initKCalAlarm(ev, dtl, QStringList(AT_LOGIN_TYPE));
if (!ancillaryType && dtl.isValid())
{
ancillaryTime = dtl;
ancillaryType = 1;
}
}
// Find the base date/time for calculating alarm offsets
DateTime nextDateTime = mNextMainDateTime;
if (mMainExpired)
{
if (checkRecur() == KARecurrence::NO_RECUR)
nextDateTime = mStartDateTime;
else if (!archived)
{
// It's a deferral of an expired recurrence.
// Need to ensure that the alarm offset is to an occurrence
// which isn't excluded by an exception - otherwise, it will
// never be triggered. So choose the first recurrence which
// isn't an exception.
KDateTime dt = mRecurrence->getNextDateTime(mStartDateTime.addDays(-1).kDateTime());
dt.setDateOnly(mStartDateTime.isDateOnly());
nextDateTime = dt;
}
}
if (mReminderMinutes && (mReminderActive != NO_REMINDER || archived))
{
int startOffset;
if (mReminderMinutes < 0 && mReminderActive != NO_REMINDER)
{
// A reminder AFTER the main alarm is active or disabled
startOffset = nextDateTime.calendarKDateTime().secsTo(mReminderAfterTime.calendarKDateTime());
}
else
{
// A reminder BEFORE the main alarm is active
startOffset = -mReminderMinutes * 60;
}
initKCalAlarm(ev, startOffset, QStringList(REMINDER_TYPE));
// Don't set ancillary time if the reminder AFTER is hidden by a deferral
if (!ancillaryType && (mReminderActive == ACTIVE_REMINDER || archived))
{
ancillaryOffset = startOffset;
ancillaryType = 2;
}
}
if (mDeferral != NO_DEFERRAL)
{
int startOffset;
QStringList list;
if (mDeferralTime.isDateOnly())
{
startOffset = nextDateTime.secsTo(mDeferralTime.calendarKDateTime());
list += DATE_DEFERRAL_TYPE;
}
else
{
startOffset = nextDateTime.calendarKDateTime().secsTo(mDeferralTime.calendarKDateTime());
list += TIME_DEFERRAL_TYPE;
}
if (mDeferral == REMINDER_DEFERRAL)
list += REMINDER_TYPE;
initKCalAlarm(ev, startOffset, list);
if (!ancillaryType && mDeferralTime.isValid())
{
ancillaryOffset = startOffset;
ancillaryType = 2;
}
}
if (!mTemplateName.isEmpty())
ev->setSummary(mTemplateName);
else if (mDisplaying)
{
QStringList list(DISPLAYING_TYPE);
if (mDisplayingFlags & KAEvent::REPEAT_AT_LOGIN)
list += AT_LOGIN_TYPE;
else if (mDisplayingFlags & DEFERRAL)
{
if (mDisplayingFlags & TIMED_FLAG)
list += TIME_DEFERRAL_TYPE;
else
list += DATE_DEFERRAL_TYPE;
}
if (mDisplayingFlags & REMINDER)
list += REMINDER_TYPE;
initKCalAlarm(ev, mDisplayingTime, list);
if (!ancillaryType && mDisplayingTime.isValid())
{
ancillaryTime = mDisplayingTime;
ancillaryType = 1;
}
}
if ((mBeep || mSpeak || !mAudioFile.isEmpty()) && mActionSubType != KAEvent::AUDIO)
{
// A sound is specified
if (ancillaryType == 2)
initKCalAlarm(ev, ancillaryOffset, QStringList(), AUDIO_ALARM);
else
initKCalAlarm(ev, ancillaryTime, QStringList(), AUDIO_ALARM);
}
if (!mPreAction.isEmpty())
{
// A pre-display action is specified
if (ancillaryType == 2)
initKCalAlarm(ev, ancillaryOffset, QStringList(PRE_ACTION_TYPE), PRE_ACTION_ALARM);
else
initKCalAlarm(ev, ancillaryTime, QStringList(PRE_ACTION_TYPE), PRE_ACTION_ALARM);
}
if (!mPostAction.isEmpty())
{
// A post-display action is specified
if (ancillaryType == 2)
initKCalAlarm(ev, ancillaryOffset, QStringList(POST_ACTION_TYPE), POST_ACTION_ALARM);
else
initKCalAlarm(ev, ancillaryTime, QStringList(POST_ACTION_TYPE), POST_ACTION_ALARM);
}
if (mRecurrence)
mRecurrence->writeRecurrence(*ev->recurrence());
else
ev->clearRecurrence();
if (mCreatedDateTime.isValid())
ev->setCreated(mCreatedDateTime);
ev->setReadOnly(readOnly);
ev->endUpdates(); // finally issue an update notification
return true;
}
/******************************************************************************
* Create a new alarm for a libkcal event, and initialise it according to the
* alarm action. If 'types' is non-null, it is appended to the X-KDE-KALARM-TYPE
* property value list.
* NOTE: The variant taking a DateTime calculates the offset from mStartDateTime,
* which is not suitable for an alarm in a recurring event.
*/
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr KAEventPrivate::initKCalAlarm(const Event::Ptr& event, const DateTime& dt, const QStringList& types, AlarmType type) const
#else
Alarm* KAEventPrivate::initKCalAlarm(Event* event, const DateTime& dt, const QStringList& types, AlarmType type) const
#endif
{
const int startOffset = dt.isDateOnly() ? mStartDateTime.secsTo(dt)
: mStartDateTime.calendarKDateTime().secsTo(dt.calendarKDateTime());
return initKCalAlarm(event, startOffset, types, type);
}
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr KAEventPrivate::initKCalAlarm(const Event::Ptr& event, int startOffsetSecs, const QStringList& types, AlarmType type) const
#else
Alarm* KAEventPrivate::initKCalAlarm(Event* event, int startOffsetSecs, const QStringList& types, AlarmType type) const
#endif
{
QStringList alltypes;
QStringList flags;
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr alarm = event->newAlarm();
#else
Alarm* alarm = event->newAlarm();
#endif
alarm->setEnabled(true);
if (type != MAIN_ALARM)
{
// RFC2445 specifies that absolute alarm times must be stored as a UTC DATE-TIME value.
// Set the alarm time as an offset to DTSTART for the reasons described in updateKCalEvent().
alarm->setStartOffset(startOffsetSecs);
}
switch (type)
{
case AUDIO_ALARM:
setAudioAlarm(alarm);
if (mSpeak)
flags << KAEventPrivate::SPEAK_FLAG;
if (mRepeatSoundPause >= 0)
{
// Alarm::setSnoozeTime() sets 5 seconds if duration parameter is zero,
// so repeat count = -1 represents 0 pause, -2 represents non-zero pause.
alarm->setRepeatCount(mRepeatSoundPause ? -2 : -1);
alarm->setSnoozeTime(Duration(mRepeatSoundPause, Duration::Seconds));
}
break;
case PRE_ACTION_ALARM:
setProcedureAlarm(alarm, mPreAction);
if (mExtraActionOptions & KAEvent::ExecPreActOnDeferral)
flags << KAEventPrivate::EXEC_ON_DEFERRAL_FLAG;
if (mExtraActionOptions & KAEvent::CancelOnPreActError)
flags << KAEventPrivate::CANCEL_ON_ERROR_FLAG;
if (mExtraActionOptions & KAEvent::DontShowPreActError)
flags << KAEventPrivate::DONT_SHOW_ERROR_FLAG;
break;
case POST_ACTION_ALARM:
setProcedureAlarm(alarm, mPostAction);
break;
case MAIN_ALARM:
alarm->setSnoozeTime(mRepetition.interval());
alarm->setRepeatCount(mRepetition.count());
if (mRepetition)
alarm->setCustomProperty(KACalendar::APPNAME, NEXT_REPEAT_PROPERTY,
QString::number(mNextRepeat));
// fall through to INVALID_ALARM
case REMINDER_ALARM:
case INVALID_ALARM:
{
if (types == QStringList(REMINDER_TYPE)
&& mReminderMinutes < 0 && mReminderActive == HIDDEN_REMINDER)
{
// It's a reminder AFTER the alarm which is currently disabled
// due to the main alarm being deferred past it.
flags << HIDDEN_REMINDER_FLAG;
}
bool display = false;
switch (mActionSubType)
{
case KAEvent::FILE:
alltypes += FILE_TYPE;
// fall through to MESSAGE
case KAEvent::MESSAGE:
alarm->setDisplayAlarm(AlarmText::toCalendarText(mText));
display = true;
break;
case KAEvent::COMMAND:
if (mCommandScript)
alarm->setProcedureAlarm(QString(), mText);
else
setProcedureAlarm(alarm, mText);
display = mCommandDisplay;
break;
case KAEvent::EMAIL:
alarm->setEmailAlarm(mEmailSubject, mText, mEmailAddresses, mEmailAttachments);
if (mEmailFromIdentity)
flags << KAEventPrivate::EMAIL_ID_FLAG << QString::number(mEmailFromIdentity);
break;
case KAEvent::AUDIO:
setAudioAlarm(alarm);
if (mRepeatSoundPause >= 0)
{
alltypes += SOUND_REPEAT_TYPE;
if (type == MAIN_ALARM)
alltypes += QString::number(mRepeatSoundPause);
}
break;
}
if (display)
alarm->setCustomProperty(KACalendar::APPNAME, FONT_COLOUR_PROPERTY,
QString::fromLatin1("%1;%2;%3").arg(mBgColour.name())
.arg(mFgColour.name())
.arg(mUseDefaultFont ? QString() : mFont.toString()));
break;
}
case DEFERRED_ALARM:
case DEFERRED_REMINDER_ALARM:
case AT_LOGIN_ALARM:
case DISPLAYING_ALARM:
break;
}
alltypes += types;
if (!alltypes.isEmpty())
alarm->setCustomProperty(KACalendar::APPNAME, TYPE_PROPERTY, alltypes.join(QLatin1String(",")));
if (!flags.isEmpty())
alarm->setCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY, flags.join(SC));
return alarm;
}
bool KAEvent::isValid() const
{
return d->mAlarmCount && (d->mAlarmCount != 1 || !d->mRepeatAtLogin);
}
void KAEvent::setEnabled(bool enable)
{
d->mEnabled = enable;
}
bool KAEvent::enabled() const
{
return d->mEnabled;
}
#ifndef KALARMCAL_USE_KRESOURCES
void KAEvent::setReadOnly(bool ro)
{
d->mReadOnly = ro;
}
bool KAEvent::isReadOnly() const
{
return d->mReadOnly;
}
#endif
void KAEvent::setArchive()
{
d->mArchive = true;
}
bool KAEvent::toBeArchived() const
{
return d->mArchive;
}
bool KAEvent::mainExpired() const
{
return d->mMainExpired;
}
bool KAEvent::expired() const
{
return (d->mDisplaying && d->mMainExpired) || d->mCategory == CalEvent::ARCHIVED;
}
KAEvent::Flags KAEvent::flags() const
{
return d->flags();
}
KAEvent::Flags KAEventPrivate::flags() const
{
KAEvent::Flags result(0);
if (mBeep) result |= KAEvent::BEEP;
if (mRepeatSoundPause >= 0) result |= KAEvent::REPEAT_SOUND;
if (mEmailBcc) result |= KAEvent::EMAIL_BCC;
if (mStartDateTime.isDateOnly()) result |= KAEvent::ANY_TIME;
if (mSpeak) result |= KAEvent::SPEAK;
if (mRepeatAtLogin) result |= KAEvent::REPEAT_AT_LOGIN;
if (mConfirmAck) result |= KAEvent::CONFIRM_ACK;
if (mUseDefaultFont) result |= KAEvent::DEFAULT_FONT;
if (mCommandScript) result |= KAEvent::SCRIPT;
if (mCommandXterm) result |= KAEvent::EXEC_IN_XTERM;
if (mCommandDisplay) result |= KAEvent::DISPLAY_COMMAND;
if (mCopyToKOrganizer) result |= KAEvent::COPY_KORGANIZER;
if (mExcludeHolidays) result |= KAEvent::EXCL_HOLIDAYS;
if (mWorkTimeOnly) result |= KAEvent::WORK_TIME_ONLY;
if (mReminderOnceOnly) result |= KAEvent::REMINDER_ONCE;
if (mAutoClose) result |= KAEvent::AUTO_CLOSE;
if (!mEnabled) result |= KAEvent::DISABLED;
return result;
}
/******************************************************************************
* Change the type of an event.
* If it is being set to archived, set the archived indication in the event ID;
* otherwise, remove the archived indication from the event ID.
*/
void KAEvent::setCategory(CalEvent::Type s)
{
d->setCategory(s);
}
void KAEventPrivate::setCategory(CalEvent::Type s)
{
if (s == mCategory)
return;
mEventID = CalEvent::uid(mEventID, s);
mCategory = s;
mTriggerChanged = true; // templates and archived don't have trigger times
}
CalEvent::Type KAEvent::category() const
{
return d->mCategory;
}
void KAEvent::setEventId(const QString& id)
{
d->mEventID = id;
}
QString KAEvent::id() const
{
return d->mEventID;
}
void KAEvent::incrementRevision()
{
++d->mRevision;
}
int KAEvent::revision() const
{
return d->mRevision;
}
#ifndef KALARMCAL_USE_KRESOURCES
void KAEvent::setCollectionId(Akonadi::Collection::Id id)
{
d->mCollectionId = id;
}
void KAEvent::setCollectionId_const(Akonadi::Collection::Id id) const
{
d->mCollectionId = id;
}
Akonadi::Collection::Id KAEvent::collectionId() const
{
// A displaying alarm contains the event's original collection ID
return d->mDisplaying ? -1 : d->mCollectionId;
}
void KAEvent::setItemId(Akonadi::Item::Id id)
{
d->mItemId = id;
}
Akonadi::Item::Id KAEvent::itemId() const
{
return d->mItemId;
}
/******************************************************************************
* Initialise an Item with the event.
* Note that the event is not updated with the Item ID.
* Reply = true if successful,
* false if event's category does not match collection's mime types.
*/
bool KAEvent::setItemPayload(Akonadi::Item& item, const QStringList& collectionMimeTypes) const
{
QString mimetype;
switch (d->mCategory)
{
case CalEvent::ACTIVE: mimetype = MIME_ACTIVE; break;
case CalEvent::ARCHIVED: mimetype = MIME_ARCHIVED; break;
case CalEvent::TEMPLATE: mimetype = MIME_TEMPLATE; break;
default: Q_ASSERT(0); return false;
}
if (!collectionMimeTypes.contains(mimetype))
return false;
item.setMimeType(mimetype);
item.setPayload<KAEvent>(*this);
return true;
}
void KAEvent::setCompatibility(KACalendar::Compat c)
{
d->mCompatibility = c;
}
KACalendar::Compat KAEvent::compatibility() const
{
return d->mCompatibility;
}
QMap<QByteArray, QString> KAEvent::customProperties() const
{
return d->mCustomProperties;
}
#else
void KAEvent::setResource(AlarmResource* r)
{
d->mResource = r;
}
AlarmResource* KAEvent::resource() const
{
return d->mResource;
}
#endif
KAEvent::SubAction KAEvent::actionSubType() const
{
return d->mActionSubType;
}
KAEvent::Actions KAEvent::actionTypes() const
{
switch (d->mActionSubType)
{
case MESSAGE:
case FILE: return ACT_DISPLAY;
case COMMAND: return d->mCommandDisplay ? ACT_DISPLAY_COMMAND : ACT_COMMAND;
case EMAIL: return ACT_EMAIL;
case AUDIO: return ACT_AUDIO;
default: return ACT_NONE;
}
}
void KAEvent::setLateCancel(int minutes)
{
if (d->mRepeatAtLogin)
minutes = 0;
d->mLateCancel = minutes;
if (!minutes)
d->mAutoClose = false;
}
int KAEvent::lateCancel() const
{
return d->mLateCancel;
}
void KAEvent::setAutoClose(bool ac)
{
d->mAutoClose = ac;
}
bool KAEvent::autoClose() const
{
return d->mAutoClose;
}
void KAEvent::setKMailSerialNumber(unsigned long n)
{
d->mKMailSerialNumber = n;
}
unsigned long KAEvent::kmailSerialNumber() const
{
return d->mKMailSerialNumber;
}
QString KAEvent::cleanText() const
{
return d->mText;
}
QString KAEvent::message() const
{
return (d->mActionSubType == MESSAGE
|| d->mActionSubType == EMAIL) ? d->mText : QString();
}
QString KAEvent::displayMessage() const
{
return (d->mActionSubType == MESSAGE) ? d->mText : QString();
}
QString KAEvent::fileName() const
{
return (d->mActionSubType == FILE) ? d->mText : QString();
}
QColor KAEvent::bgColour() const
{
return d->mBgColour;
}
QColor KAEvent::fgColour() const
{
return d->mFgColour;
}
void KAEvent::setDefaultFont(const QFont& f)
{
KAEventPrivate::mDefaultFont = f;
}
bool KAEvent::useDefaultFont() const
{
return d->mUseDefaultFont;
}
QFont KAEvent::font() const
{
return d->mUseDefaultFont ? KAEventPrivate::mDefaultFont : d->mFont;
}
QString KAEvent::command() const
{
return (d->mActionSubType == COMMAND) ? d->mText : QString();
}
bool KAEvent::commandScript() const
{
return d->mCommandScript;
}
bool KAEvent::commandXterm() const
{
return d->mCommandXterm;
}
bool KAEvent::commandDisplay() const
{
return d->mCommandDisplay;
}
#ifndef KALARMCAL_USE_KRESOURCES
void KAEvent::setCommandError(CmdErrType t) const
{
d->mCommandError = t;
}
#else
/******************************************************************************
* Set the command last error status.
* If 'writeConfig' is true, the status is written to the config file.
*/
void KAEvent::setCommandError(CmdErrType t, bool writeConfig) const
{
d->setCommandError(t, writeConfig);
}
void KAEventPrivate::setCommandError(KAEvent::CmdErrType error, bool writeConfig) const
{
kDebug() << mEventID << "," << error;
if (error == mCommandError)
return;
mCommandError = error;
if (writeConfig)
{
KConfigGroup config(KGlobal::config(), mCmdErrConfigGroup);
if (mCommandError == KAEvent::CMD_NO_ERROR)
config.deleteEntry(mEventID);
else
{
QString errtext;
switch (mCommandError)
{
case KAEvent::CMD_ERROR: errtext = CMD_ERROR_VALUE; break;
case KAEvent::CMD_ERROR_PRE: errtext = CMD_ERROR_PRE_VALUE; break;
case KAEvent::CMD_ERROR_POST: errtext = CMD_ERROR_POST_VALUE; break;
case KAEvent::CMD_ERROR_PRE_POST:
errtext = CMD_ERROR_PRE_VALUE + ',' + CMD_ERROR_POST_VALUE;
break;
default:
break;
}
config.writeEntry(mEventID, errtext);
}
config.sync();
}
}
/******************************************************************************
* Initialise the command last error status of the alarm from the config file.
*/
void KAEvent::setCommandError(const QString& configString)
{
d->setCommandError(configString);
}
void KAEventPrivate::setCommandError(const QString& configString)
{
mCommandError = KAEvent::CMD_NO_ERROR;
const QStringList errs = configString.split(',');
if (errs.indexOf(CMD_ERROR_VALUE) >= 0)
mCommandError = KAEvent::CMD_ERROR;
else
{
if (errs.indexOf(CMD_ERROR_PRE_VALUE) >= 0)
mCommandError = KAEvent::CMD_ERROR_PRE;
if (errs.indexOf(CMD_ERROR_POST_VALUE) >= 0)
mCommandError = static_cast<KAEvent::CmdErrType>(mCommandError | KAEvent::CMD_ERROR_POST);
}
}
QString KAEvent::commandErrorConfigGroup()
{
return KAEventPrivate::mCmdErrConfigGroup;
}
#endif
KAEvent::CmdErrType KAEvent::commandError() const
{
return d->mCommandError;
}
void KAEvent::setLogFile(const QString& logfile)
{
d->mLogFile = logfile;
if (!logfile.isEmpty())
d->mCommandDisplay = d->mCommandXterm = false;
}
QString KAEvent::logFile() const
{
return d->mLogFile;
}
bool KAEvent::confirmAck() const
{
return d->mConfirmAck;
}
bool KAEvent::copyToKOrganizer() const
{
return d->mCopyToKOrganizer;
}
#ifndef KALARMCAL_USE_KRESOURCES
void KAEvent::setEmail(uint from, const KCalCore::Person::List& addresses, const QString& subject,
const QStringList& attachments)
#else
void KAEvent::setEmail(uint from, const QList<KCal::Person>& addresses, const QString& subject,
const QStringList& attachments)
#endif
{
d->mEmailFromIdentity = from;
d->mEmailAddresses = addresses;
d->mEmailSubject = subject;
d->mEmailAttachments = attachments;
}
QString KAEvent::emailMessage() const
{
return (d->mActionSubType == EMAIL) ? d->mText : QString();
}
uint KAEvent::emailFromId() const
{
return d->mEmailFromIdentity;
}
#ifndef KALARMCAL_USE_KRESOURCES
KCalCore::Person::List KAEvent::emailAddressees() const
#else
QList<KCal::Person> KAEvent::emailAddressees() const
#endif
{
return d->mEmailAddresses;
}
QStringList KAEvent::emailAddresses() const
{
return static_cast<QStringList>(d->mEmailAddresses);
}
QString KAEvent::emailAddresses(const QString& sep) const
{
return d->mEmailAddresses.join(sep);
}
#ifndef KALARMCAL_USE_KRESOURCES
QString KAEvent::joinEmailAddresses(const KCalCore::Person::List& addresses, const QString& separator)
#else
QString KAEvent::joinEmailAddresses(const QList<KCal::Person>& addresses, const QString& separator)
#endif
{
return EmailAddressList(addresses).join(separator);
}
QStringList KAEvent::emailPureAddresses() const
{
return d->mEmailAddresses.pureAddresses();
}
QString KAEvent::emailPureAddresses(const QString& sep) const
{
return d->mEmailAddresses.pureAddresses(sep);
}
QString KAEvent::emailSubject() const
{
return d->mEmailSubject;
}
QStringList KAEvent::emailAttachments() const
{
return d->mEmailAttachments;
}
QString KAEvent::emailAttachments(const QString& sep) const
{
return d->mEmailAttachments.join(sep);
}
bool KAEvent::emailBcc() const
{
return d->mEmailBcc;
}
void KAEvent::setAudioFile(const QString& filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile)
{
d->setAudioFile(filename, volume, fadeVolume, fadeSeconds, repeatPause, allowEmptyFile);
}
void KAEventPrivate::setAudioFile(const QString& filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile)
{
mAudioFile = filename;
mSoundVolume = (!allowEmptyFile && filename.isEmpty()) ? -1 : volume;
if (mSoundVolume >= 0)
{
mFadeVolume = (fadeSeconds > 0) ? fadeVolume : -1;
mFadeSeconds = (mFadeVolume >= 0) ? fadeSeconds : 0;
}
else
{
mFadeVolume = -1;
mFadeSeconds = 0;
}
mRepeatSoundPause = repeatPause;
}
QString KAEvent::audioFile() const
{
return d->mAudioFile;
}
float KAEvent::soundVolume() const
{
return d->mSoundVolume;
}
float KAEvent::fadeVolume() const
{
return d->mSoundVolume >= 0 && d->mFadeSeconds ? d->mFadeVolume : -1;
}
int KAEvent::fadeSeconds() const
{
return d->mSoundVolume >= 0 && d->mFadeVolume >= 0 ? d->mFadeSeconds : 0;
}
bool KAEvent::repeatSound() const
{
return d->mRepeatSoundPause >= 0;
}
int KAEvent::repeatSoundPause() const
{
return d->mRepeatSoundPause;
}
bool KAEvent::beep() const
{
return d->mBeep;
}
bool KAEvent::speak() const
{
return (d->mActionSubType == MESSAGE
|| (d->mActionSubType == COMMAND && d->mCommandDisplay))
&& d->mSpeak;
}
/******************************************************************************
* Set the event to be an alarm template.
*/
void KAEvent::setTemplate(const QString& name, int afterTime)
{
d->setCategory(CalEvent::TEMPLATE);
d->mTemplateName = name;
d->mTemplateAfterTime = afterTime;
d->mTriggerChanged = true; // templates and archived don't have trigger times
}
bool KAEvent::isTemplate() const
{
return !d->mTemplateName.isEmpty();
}
QString KAEvent::templateName() const
{
return d->mTemplateName;
}
bool KAEvent::usingDefaultTime() const
{
return d->mTemplateAfterTime == 0;
}
int KAEvent::templateAfterTime() const
{
return d->mTemplateAfterTime;
}
void KAEvent::setActions(const QString& pre, const QString& post, ExtraActionOptions options)
{
d->mPreAction = pre;
d->mPostAction = post;
d->mExtraActionOptions = options;
}
void KAEvent::setActions(const QString& pre, const QString& post, bool cancelOnError, bool dontShowError)
{
ExtraActionOptions opts(0);
if (cancelOnError)
opts |= CancelOnPreActError;
if (dontShowError)
opts |= DontShowPreActError;
setActions(pre, post, opts);
}
QString KAEvent::preAction() const
{
return d->mPreAction;
}
QString KAEvent::postAction() const
{
return d->mPostAction;
}
KAEvent::ExtraActionOptions KAEvent::extraActionOptions() const
{
return d->mExtraActionOptions;
}
bool KAEvent::cancelOnPreActionError() const
{
return d->mExtraActionOptions & CancelOnPreActError;
}
bool KAEvent::dontShowPreActionError() const
{
return d->mExtraActionOptions & DontShowPreActError;
}
/******************************************************************************
* Set a reminder.
* 'minutes' = number of minutes BEFORE the main alarm.
*/
void KAEvent::setReminder(int minutes, bool onceOnly)
{
d->setReminder(minutes, onceOnly);
}
void KAEventPrivate::setReminder(int minutes, bool onceOnly)
{
if (minutes > 0 && mRepeatAtLogin)
minutes = 0;
if (minutes != mReminderMinutes || (minutes && mReminderActive != ACTIVE_REMINDER))
{
if (minutes && mReminderActive == NO_REMINDER)
++mAlarmCount;
else if (!minutes && mReminderActive != NO_REMINDER)
--mAlarmCount;
mReminderMinutes = minutes;
mReminderActive = minutes ? ACTIVE_REMINDER : NO_REMINDER;
mReminderOnceOnly = onceOnly;
mReminderAfterTime = DateTime();
mTriggerChanged = true;
}
}
/******************************************************************************
* Activate the event's reminder which occurs AFTER the given main alarm time.
* Reply = true if successful (i.e. reminder falls before the next main alarm).
*/
void KAEvent::activateReminderAfter(const DateTime& mainAlarmTime)
{
d->activateReminderAfter(mainAlarmTime);
}
void KAEventPrivate::activateReminderAfter(const DateTime& mainAlarmTime)
{
if (mReminderMinutes >= 0 || mReminderActive == ACTIVE_REMINDER || !mainAlarmTime.isValid())
return;
// There is a reminder AFTER the main alarm.
if (checkRecur() != KARecurrence::NO_RECUR)
{
// For a recurring alarm, the given alarm time must be a recurrence, not a sub-repetition.
DateTime next;
//???? For some unknown reason, addSecs(-1) returns the recurrence after the next,
//???? so addSecs(-60) is used instead.
if (nextRecurrence(mainAlarmTime.addSecs(-60).effectiveKDateTime(), next) == KAEvent::NO_OCCURRENCE
|| mainAlarmTime != next)
return;
}
else if (!mRepeatAtLogin)
{
// For a non-recurring alarm, the given alarm time must be the main alarm time.
if (mainAlarmTime != mStartDateTime)
return;
}
const DateTime reminderTime = mainAlarmTime.addMins(-mReminderMinutes);
DateTime next;
if (nextOccurrence(mainAlarmTime.effectiveKDateTime(), next, KAEvent::RETURN_REPETITION) != KAEvent::NO_OCCURRENCE
&& reminderTime >= next)
return; // the reminder time is after the next occurrence of the main alarm
kDebug() << "Setting reminder at" << reminderTime.effectiveKDateTime().dateTime();
activate_reminder(true);
mReminderAfterTime = reminderTime;
}
int KAEvent::reminderMinutes() const
{
return d->mReminderMinutes;
}
bool KAEvent::reminderActive() const
{
return d->mReminderActive == KAEventPrivate::ACTIVE_REMINDER;
}
bool KAEvent::reminderOnceOnly() const
{
return d->mReminderOnceOnly;
}
bool KAEvent::reminderDeferral() const
{
return d->mDeferral == KAEventPrivate::REMINDER_DEFERRAL;
}
/******************************************************************************
* Defer the event to the specified time.
* If the main alarm time has passed, the main alarm is marked as expired.
* If 'adjustRecurrence' is true, ensure that the next scheduled recurrence is
* after the current time.
*/
void KAEvent::defer(const DateTime& dt, bool reminder, bool adjustRecurrence)
{
return d->defer(dt, reminder, adjustRecurrence);
}
void KAEventPrivate::defer(const DateTime& dateTime, bool reminder, bool adjustRecurrence)
{
startChanges(); // prevent multiple trigger time evaluation here
bool setNextRepetition = false;
bool checkRepetition = false;
bool checkReminderAfter = false;
if (checkRecur() == KARecurrence::NO_RECUR)
{
// Deferring a non-recurring alarm
if (mReminderMinutes)
{
bool deferReminder = false;
if (mReminderMinutes > 0)
{
// There's a reminder BEFORE the main alarm
if (dateTime < mNextMainDateTime.effectiveKDateTime())
deferReminder = true;
else if (mReminderActive == ACTIVE_REMINDER || mDeferral == REMINDER_DEFERRAL)
{
// Deferring past the main alarm time, so adjust any existing deferral
set_deferral(NO_DEFERRAL);
mTriggerChanged = true;
}
}
else if (mReminderMinutes < 0 && reminder)
deferReminder = true; // deferring a reminder AFTER the main alarm
if (deferReminder)
{
set_deferral(REMINDER_DEFERRAL); // defer reminder alarm
mDeferralTime = dateTime;
mTriggerChanged = true;
}
if (mReminderActive == ACTIVE_REMINDER)
{
activate_reminder(false);
mTriggerChanged = true;
}
}
if (mDeferral != REMINDER_DEFERRAL)
{
// We're deferring the main alarm.
// Main alarm has now expired.
mNextMainDateTime = mDeferralTime = dateTime;
set_deferral(NORMAL_DEFERRAL);
mTriggerChanged = true;
checkReminderAfter = true;
if (!mMainExpired)
{
// Mark the alarm as expired now
mMainExpired = true;
--mAlarmCount;
if (mRepeatAtLogin)
{
// Remove the repeat-at-login alarm, but keep a note of it for archiving purposes
mArchiveRepeatAtLogin = true;
mRepeatAtLogin = false;
--mAlarmCount;
}
}
}
}
else if (reminder)
{
// Deferring a reminder for a recurring alarm
if (dateTime >= mNextMainDateTime.effectiveKDateTime())
{
// Trying to defer it past the next main alarm (regardless of whether
// the reminder triggered before or after the main alarm).
set_deferral(NO_DEFERRAL); // (error)
}
else
{
set_deferral(REMINDER_DEFERRAL);
mDeferralTime = dateTime;
checkRepetition = true;
}
mTriggerChanged = true;
}
else
{
// Deferring a recurring alarm
mDeferralTime = dateTime;
if (mDeferral == NO_DEFERRAL)
set_deferral(NORMAL_DEFERRAL);
mTriggerChanged = true;
checkReminderAfter = true;
if (adjustRecurrence)
{
const KDateTime now = KDateTime::currentUtcDateTime();
if (mainEndRepeatTime() < now)
{
// The last repetition (if any) of the current recurrence has already passed.
// Adjust to the next scheduled recurrence after now.
if (!mMainExpired && setNextOccurrence(now) == KAEvent::NO_OCCURRENCE)
{
mMainExpired = true;
--mAlarmCount;
}
}
else
setNextRepetition = mRepetition;
}
else
checkRepetition = true;
}
if (checkReminderAfter && mReminderMinutes < 0 && mReminderActive != NO_REMINDER)
{
// Enable/disable the active reminder AFTER the main alarm,
// depending on whether the deferral is before or after the reminder.
mReminderActive = (mDeferralTime < mReminderAfterTime) ? ACTIVE_REMINDER : HIDDEN_REMINDER;
}
if (checkRepetition)
setNextRepetition = (mRepetition && mDeferralTime < mainEndRepeatTime());
if (setNextRepetition)
{
// The alarm is repeated, and we're deferring to a time before the last repetition.
// Set the next scheduled repetition to the one after the deferral.
if (mNextMainDateTime >= mDeferralTime)
mNextRepeat = 0;
else
mNextRepeat = mRepetition.nextRepeatCount(mNextMainDateTime.kDateTime(), mDeferralTime.kDateTime());
mTriggerChanged = true;
}
endChanges();
}
/******************************************************************************
* Cancel any deferral alarm.
*/
void KAEvent::cancelDefer()
{
d->cancelDefer();
}
void KAEventPrivate::cancelDefer()
{
if (mDeferral != NO_DEFERRAL)
{
mDeferralTime = DateTime();
set_deferral(NO_DEFERRAL);
mTriggerChanged = true;
}
}
void KAEvent::setDeferDefaultMinutes(int minutes, bool dateOnly)
{
d->mDeferDefaultMinutes = minutes;
d->mDeferDefaultDateOnly = dateOnly;
}
bool KAEvent::deferred() const
{
return d->mDeferral > 0;
}
DateTime KAEvent::deferDateTime() const
{
return d->mDeferralTime;
}
/******************************************************************************
* Find the latest time which the alarm can currently be deferred to.
*/
DateTime KAEvent::deferralLimit(DeferLimitType* limitType) const
{
return d->deferralLimit(limitType);
}
DateTime KAEventPrivate::deferralLimit(KAEvent::DeferLimitType* limitType) const
{
KAEvent::DeferLimitType ltype = KAEvent::LIMIT_NONE;
DateTime endTime;
if (checkRecur() != KARecurrence::NO_RECUR)
{
// It's a recurring alarm. Find the latest time it can be deferred to:
// it cannot be deferred past its next occurrence or sub-repetition,
// or any advance reminder before that.
DateTime reminderTime;
const KDateTime now = KDateTime::currentUtcDateTime();
const KAEvent::OccurType type = nextOccurrence(now, endTime, KAEvent::RETURN_REPETITION);
if (type & KAEvent::OCCURRENCE_REPEAT)
ltype = KAEvent::LIMIT_REPETITION;
else if (type == KAEvent::NO_OCCURRENCE)
ltype = KAEvent::LIMIT_NONE;
else if (mReminderActive == ACTIVE_REMINDER && mReminderMinutes > 0
&& (now < (reminderTime = endTime.addMins(-mReminderMinutes))))
{
endTime = reminderTime;
ltype = KAEvent::LIMIT_REMINDER;
}
else
ltype = KAEvent::LIMIT_RECURRENCE;
}
else if (mReminderMinutes < 0)
{
// There is a reminder alarm which occurs AFTER the main alarm.
// Don't allow the reminder to be deferred past the next main alarm time.
if (KDateTime::currentUtcDateTime() < mNextMainDateTime.effectiveKDateTime())
{
endTime = mNextMainDateTime;
ltype = KAEvent::LIMIT_MAIN;
}
}
else if (mReminderMinutes > 0
&& KDateTime::currentUtcDateTime() < mNextMainDateTime.effectiveKDateTime())
{
// It's a reminder BEFORE the main alarm.
// Don't allow it to be deferred past its main alarm time.
endTime = mNextMainDateTime;
ltype = KAEvent::LIMIT_MAIN;
}
if (ltype != KAEvent::LIMIT_NONE)
endTime = endTime.addMins(-1);
if (limitType)
*limitType = ltype;
return endTime;
}
int KAEvent::deferDefaultMinutes() const
{
return d->mDeferDefaultMinutes;
}
bool KAEvent::deferDefaultDateOnly() const
{
return d->mDeferDefaultDateOnly;
}
DateTime KAEvent::startDateTime() const
{
return d->mStartDateTime;
}
void KAEvent::setTime(const KDateTime& dt)
{
d->mNextMainDateTime = dt;
d->mTriggerChanged = true;
}
DateTime KAEvent::mainDateTime(bool withRepeats) const
{
return d->mainDateTime(withRepeats);
}
QTime KAEvent::mainTime() const
{
return d->mNextMainDateTime.effectiveTime();
}
DateTime KAEvent::mainEndRepeatTime() const
{
return d->mainEndRepeatTime();
}
/******************************************************************************
* Set the start-of-day time for date-only alarms.
*/
void KAEvent::setStartOfDay(const QTime& startOfDay)
{
DateTime::setStartOfDay(startOfDay);
#ifdef __GNUC__
#warning Does this need all trigger times for date-only alarms to be recalculated?
#endif
}
/******************************************************************************
* Called when the user changes the start-of-day time.
* Adjust the start time of the recurrence to match, for each date-only event in
* a list.
*/
void KAEvent::adjustStartOfDay(const KAEvent::List& events)
{
for (int i = 0, end = events.count(); i < end; ++i)
{
KAEventPrivate* const p = events[i]->d;
if (p->mStartDateTime.isDateOnly() && p->checkRecur() != KARecurrence::NO_RECUR)
p->mRecurrence->setStartDateTime(p->mStartDateTime.effectiveKDateTime(), true);
}
}
DateTime KAEvent::nextTrigger(TriggerType type) const
{
d->calcTriggerTimes();
switch (type)
{
case ALL_TRIGGER: return d->mAllTrigger;
case MAIN_TRIGGER: return d->mMainTrigger;
case ALL_WORK_TRIGGER: return d->mAllWorkTrigger;
case WORK_TRIGGER: return d->mMainWorkTrigger;
case DISPLAY_TRIGGER:
{
const bool reminderAfter = d->mMainExpired && d->mReminderActive && d->mReminderMinutes < 0;
return d->checkRecur() != KARecurrence::NO_RECUR && (d->mWorkTimeOnly || d->mExcludeHolidays)
? (reminderAfter ? d->mAllWorkTrigger : d->mMainWorkTrigger)
: (reminderAfter ? d->mAllTrigger : d->mMainTrigger);
}
default: return DateTime();
}
}
void KAEvent::setCreatedDateTime(const KDateTime& dt)
{
d->mCreatedDateTime = dt;
}
KDateTime KAEvent::createdDateTime() const
{
return d->mCreatedDateTime;
}
/******************************************************************************
* Set or clear repeat-at-login.
*/
void KAEvent::setRepeatAtLogin(bool rl)
{
d->setRepeatAtLogin(rl);
}
void KAEventPrivate::setRepeatAtLogin(bool rl)
{
if (rl && !mRepeatAtLogin)
{
setRepeatAtLoginTrue(true); // clear incompatible statuses
++mAlarmCount;
}
else if (!rl && mRepeatAtLogin)
--mAlarmCount;
mRepeatAtLogin = rl;
mTriggerChanged = true;
}
/******************************************************************************
* Clear incompatible statuses when repeat-at-login is set.
*/
void KAEventPrivate::setRepeatAtLoginTrue(bool clearReminder)
{
clearRecur(); // clear recurrences
if (mReminderMinutes >= 0 && clearReminder)
setReminder(0, false); // clear pre-alarm reminder
mLateCancel = 0;
mAutoClose = false;
mCopyToKOrganizer = false;
}
bool KAEvent::repeatAtLogin(bool includeArchived) const
{
return d->mRepeatAtLogin || (includeArchived && d->mArchiveRepeatAtLogin);
}
void KAEvent::setExcludeHolidays(bool ex)
{
d->mExcludeHolidays = ex ? KAEventPrivate::mHolidays : 0;
// Option only affects recurring alarms
d->mTriggerChanged = (d->checkRecur() != KARecurrence::NO_RECUR);
}
bool KAEvent::holidaysExcluded() const
{
return d->mExcludeHolidays;
}
/******************************************************************************
* Set a new holiday region.
* Alarms which exclude holidays record the pointer to the holiday definition
* at the time their next trigger times were last calculated. The change in
* holiday definition pointer will cause their next trigger times to be
* recalculated.
*/
void KAEvent::setHolidays(const HolidayRegion& h)
{
KAEventPrivate::mHolidays = &h;
}
void KAEvent::setWorkTimeOnly(bool wto)
{
d->mWorkTimeOnly = wto;
// Option only affects recurring alarms
d->mTriggerChanged = (d->checkRecur() != KARecurrence::NO_RECUR);
}
bool KAEvent::workTimeOnly() const
{
return d->mWorkTimeOnly;
}
/******************************************************************************
* Check whether a date/time is during working hours and/or holidays, depending
* on the flags set for the specified event.
*/
bool KAEvent::isWorkingTime(const KDateTime& dt) const
{
return d->isWorkingTime(dt);
}
bool KAEventPrivate::isWorkingTime(const KDateTime& dt) const
{
if ((mWorkTimeOnly && !mWorkDays.testBit(dt.date().dayOfWeek() - 1))
|| (mExcludeHolidays && mHolidays && mHolidays->isHoliday(dt.date())))
return false;
if (!mWorkTimeOnly)
return true;
return dt.isDateOnly()
|| (dt.time() >= mWorkDayStart && dt.time() < mWorkDayEnd);
}
/******************************************************************************
* Set new working days and times.
* Increment a counter so that working-time-only alarms can detect that they
* need to update their next trigger time.
*/
void KAEvent::setWorkTime(const QBitArray& days, const QTime& start, const QTime& end)
{
if (days != KAEventPrivate::mWorkDays || start != KAEventPrivate::mWorkDayStart || end != KAEventPrivate::mWorkDayEnd)
{
KAEventPrivate::mWorkDays = days;
KAEventPrivate::mWorkDayStart = start;
KAEventPrivate::mWorkDayEnd = end;
if (!++KAEventPrivate::mWorkTimeIndex)
++KAEventPrivate::mWorkTimeIndex;
}
}
/******************************************************************************
* Clear the event's recurrence and alarm repetition data.
*/
void KAEvent::setNoRecur()
{
d->clearRecur();
}
void KAEventPrivate::clearRecur()
{
if (mRecurrence || mRepetition)
{
delete mRecurrence;
mRecurrence = 0;
mRepetition.set(0, 0);
mTriggerChanged = true;
}
mNextRepeat = 0;
}
/******************************************************************************
* Initialise the event's recurrence from a KCal::Recurrence.
* The event's start date/time is not changed.
*/
void KAEvent::setRecurrence(const KARecurrence& recurrence)
{
d->setRecurrence(recurrence);
}
void KAEventPrivate::setRecurrence(const KARecurrence& recurrence)
{
startChanges(); // prevent multiple trigger time evaluation here
if (recurrence.recurs())
{
delete mRecurrence;
mRecurrence = new KARecurrence(recurrence);
mRecurrence->setStartDateTime(mStartDateTime.effectiveKDateTime(), mStartDateTime.isDateOnly());
mTriggerChanged = true;
// Adjust sub-repetition values to fit the recurrence.
setRepetition(mRepetition);
}
else
clearRecur();
endChanges();
}
/******************************************************************************
* Set the recurrence to recur at a minutes interval.
* Parameters:
* freq = how many minutes between recurrences.
* count = number of occurrences, including first and last.
* = -1 to recur indefinitely.
* = 0 to use 'end' instead.
* end = end date/time (invalid to use 'count' instead).
* Reply = false if no recurrence was set up.
*/
bool KAEvent::setRecurMinutely(int freq, int count, const KDateTime& end)
{
const bool success = d->setRecur(RecurrenceRule::rMinutely, freq, count, end);
d->mTriggerChanged = true;
return success;
}
/******************************************************************************
* Set the recurrence to recur daily.
* Parameters:
* freq = how many days between recurrences.
* days = which days of the week alarms are allowed to occur on.
* count = number of occurrences, including first and last.
* = -1 to recur indefinitely.
* = 0 to use 'end' instead.
* end = end date (invalid to use 'count' instead).
* Reply = false if no recurrence was set up.
*/
bool KAEvent::setRecurDaily(int freq, const QBitArray& days, int count, const QDate& end)
{
const bool success = d->setRecur(RecurrenceRule::rDaily, freq, count, end);
if (success)
{
int n = 0;
for (int i = 0; i < 7; ++i)
{
if (days.testBit(i))
++n;
}
if (n < 7)
d->mRecurrence->addWeeklyDays(days);
}
d->mTriggerChanged = true;
return success;
}
/******************************************************************************
* Set the recurrence to recur weekly, on the specified weekdays.
* Parameters:
* freq = how many weeks between recurrences.
* days = which days of the week alarms should occur on.
* count = number of occurrences, including first and last.
* = -1 to recur indefinitely.
* = 0 to use 'end' instead.
* end = end date (invalid to use 'count' instead).
* Reply = false if no recurrence was set up.
*/
bool KAEvent::setRecurWeekly(int freq, const QBitArray& days, int count, const QDate& end)
{
const bool success = d->setRecur(RecurrenceRule::rWeekly, freq, count, end);
if (success)
d->mRecurrence->addWeeklyDays(days);
d->mTriggerChanged = true;
return success;
}
/******************************************************************************
* Set the recurrence to recur monthly, on the specified days within the month.
* Parameters:
* freq = how many months between recurrences.
* days = which days of the month alarms should occur on.
* count = number of occurrences, including first and last.
* = -1 to recur indefinitely.
* = 0 to use 'end' instead.
* end = end date (invalid to use 'count' instead).
* Reply = false if no recurrence was set up.
*/
bool KAEvent::setRecurMonthlyByDate(int freq, const QVector<int>& days, int count, const QDate& end)
{
const bool success = d->setRecur(RecurrenceRule::rMonthly, freq, count, end);
if (success)
{
for (int i = 0, end = days.count(); i < end; ++i)
d->mRecurrence->addMonthlyDate(days[i]);
}
d->mTriggerChanged = true;
return success;
}
/******************************************************************************
* Set the recurrence to recur monthly, on the specified weekdays in the
* specified weeks of the month.
* Parameters:
* freq = how many months between recurrences.
* posns = which days of the week/weeks of the month alarms should occur on.
* count = number of occurrences, including first and last.
* = -1 to recur indefinitely.
* = 0 to use 'end' instead.
* end = end date (invalid to use 'count' instead).
* Reply = false if no recurrence was set up.
*/
bool KAEvent::setRecurMonthlyByPos(int freq, const QVector<MonthPos>& posns, int count, const QDate& end)
{
const bool success = d->setRecur(RecurrenceRule::rMonthly, freq, count, end);
if (success)
{
for (int i = 0, end = posns.count(); i < end; ++i)
d->mRecurrence->addMonthlyPos(posns[i].weeknum, posns[i].days);
}
d->mTriggerChanged = true;
return success;
}
/******************************************************************************
* Set the recurrence to recur annually, on the specified start date in each
* of the specified months.
* Parameters:
* freq = how many years between recurrences.
* months = which months of the year alarms should occur on.
* day = day of month, or 0 to use start date
* feb29 = when February 29th should recur in non-leap years.
* count = number of occurrences, including first and last.
* = -1 to recur indefinitely.
* = 0 to use 'end' instead.
* end = end date (invalid to use 'count' instead).
* Reply = false if no recurrence was set up.
*/
bool KAEvent::setRecurAnnualByDate(int freq, const QVector<int>& months, int day, KARecurrence::Feb29Type feb29, int count, const QDate& end)
{
const bool success = d->setRecur(RecurrenceRule::rYearly, freq, count, end, feb29);
if (success)
{
for (int i = 0, end = months.count(); i < end; ++i)
d->mRecurrence->addYearlyMonth(months[i]);
if (day)
d->mRecurrence->addMonthlyDate(day);
}
d->mTriggerChanged = true;
return success;
}
/******************************************************************************
* Set the recurrence to recur annually, on the specified weekdays in the
* specified weeks of the specified months.
* Parameters:
* freq = how many years between recurrences.
* posns = which days of the week/weeks of the month alarms should occur on.
* months = which months of the year alarms should occur on.
* count = number of occurrences, including first and last.
* = -1 to recur indefinitely.
* = 0 to use 'end' instead.
* end = end date (invalid to use 'count' instead).
* Reply = false if no recurrence was set up.
*/
bool KAEvent::setRecurAnnualByPos(int freq, const QVector<MonthPos>& posns, const QVector<int>& months, int count, const QDate& end)
{
const bool success = d->setRecur(RecurrenceRule::rYearly, freq, count, end);
if (success)
{
int i = 0;
int iend;
for (iend = months.count(); i < iend; ++i)
d->mRecurrence->addYearlyMonth(months[i]);
for (i = 0, iend = posns.count(); i < iend; ++i)
d->mRecurrence->addYearlyPos(posns[i].weeknum, posns[i].days);
}
d->mTriggerChanged = true;
return success;
}
/******************************************************************************
* Initialise the event's recurrence data.
* Parameters:
* freq = how many intervals between recurrences.
* count = number of occurrences, including first and last.
* = -1 to recur indefinitely.
* = 0 to use 'end' instead.
* end = end date/time (invalid to use 'count' instead).
* Reply = false if no recurrence was set up.
*/
bool KAEventPrivate::setRecur(RecurrenceRule::PeriodType recurType, int freq, int count, const QDate& end, KARecurrence::Feb29Type feb29)
{
KDateTime edt = mNextMainDateTime.kDateTime();
edt.setDate(end);
return setRecur(recurType, freq, count, edt, feb29);
}
bool KAEventPrivate::setRecur(RecurrenceRule::PeriodType recurType, int freq, int count, const KDateTime& end, KARecurrence::Feb29Type feb29)
{
if (count >= -1 && (count || end.date().isValid()))
{
if (!mRecurrence)
mRecurrence = new KARecurrence;
if (mRecurrence->init(recurType, freq, count, mNextMainDateTime.kDateTime(), end, feb29))
return true;
}
clearRecur();
return false;
}
bool KAEvent::recurs() const
{
return d->checkRecur() != KARecurrence::NO_RECUR;
}
KARecurrence::Type KAEvent::recurType() const
{
return d->checkRecur();
}
KARecurrence* KAEvent::recurrence() const
{
return d->mRecurrence;
}
/******************************************************************************
* Return the recurrence interval in units of the recurrence period type.
*/
int KAEvent::recurInterval() const
{
if (d->mRecurrence)
{
switch (d->mRecurrence->type())
{
case KARecurrence::MINUTELY:
case KARecurrence::DAILY:
case KARecurrence::WEEKLY:
case KARecurrence::MONTHLY_DAY:
case KARecurrence::MONTHLY_POS:
case KARecurrence::ANNUAL_DATE:
case KARecurrence::ANNUAL_POS:
return d->mRecurrence->frequency();
default:
break;
}
}
return 0;
}
Duration KAEvent::longestRecurrenceInterval() const
{
return d->mRecurrence ? d->mRecurrence->longestInterval() : Duration(0);
}
/******************************************************************************
* Adjust the event date/time to the first recurrence of the event, on or after
* start date/time. The event start date may not be a recurrence date, in which
* case a later date will be set.
*/
void KAEvent::setFirstRecurrence()
{
d->setFirstRecurrence();
}
void KAEventPrivate::setFirstRecurrence()
{
switch (checkRecur())
{
case KARecurrence::NO_RECUR:
case KARecurrence::MINUTELY:
return;
case KARecurrence::ANNUAL_DATE:
case KARecurrence::ANNUAL_POS:
if (mRecurrence->yearMonths().isEmpty())
return; // (presumably it's a template)
break;
case KARecurrence::DAILY:
case KARecurrence::WEEKLY:
case KARecurrence::MONTHLY_POS:
case KARecurrence::MONTHLY_DAY:
break;
}
const KDateTime recurStart = mRecurrence->startDateTime();
if (mRecurrence->recursOn(recurStart.date(), recurStart.timeSpec()))
return; // it already recurs on the start date
// Set the frequency to 1 to find the first possible occurrence
const int frequency = mRecurrence->frequency();
mRecurrence->setFrequency(1);
DateTime next;
nextRecurrence(mNextMainDateTime.effectiveKDateTime(), next);
if (!next.isValid())
mRecurrence->setStartDateTime(recurStart, mStartDateTime.isDateOnly()); // reinstate the old value
else
{
mRecurrence->setStartDateTime(next.effectiveKDateTime(), next.isDateOnly());
mStartDateTime = mNextMainDateTime = next;
mTriggerChanged = true;
}
mRecurrence->setFrequency(frequency); // restore the frequency
}
/******************************************************************************
* Return the recurrence interval as text suitable for display.
*/
QString KAEvent::recurrenceText(bool brief) const
{
if (d->mRepeatAtLogin)
return brief ? i18nc("@info/plain Brief form of 'At Login'", "Login") : i18nc("@info/plain", "At login");
if (d->mRecurrence)
{
const int frequency = d->mRecurrence->frequency();
switch (d->mRecurrence->defaultRRuleConst()->recurrenceType())
{
case RecurrenceRule::rMinutely:
if (frequency < 60)
return i18ncp("@info/plain", "1 Minute", "%1 Minutes", frequency);
else if (frequency % 60 == 0)
return i18ncp("@info/plain", "1 Hour", "%1 Hours", frequency/60);
else
{
QString mins;
return i18nc("@info/plain Hours and minutes", "%1h %2m", frequency/60, mins.sprintf("%02d", frequency%60));
}
case RecurrenceRule::rDaily:
return i18ncp("@info/plain", "1 Day", "%1 Days", frequency);
case RecurrenceRule::rWeekly:
return i18ncp("@info/plain", "1 Week", "%1 Weeks", frequency);
case RecurrenceRule::rMonthly:
return i18ncp("@info/plain", "1 Month", "%1 Months", frequency);
case RecurrenceRule::rYearly:
return i18ncp("@info/plain", "1 Year", "%1 Years", frequency);
case RecurrenceRule::rNone:
default:
break;
}
}
return brief ? QString() : i18nc("@info/plain No recurrence", "None");
}
/******************************************************************************
* Initialise the event's sub-repetition.
* The repetition length is adjusted if necessary to fit the recurrence interval.
* If the event doesn't recur, the sub-repetition is cleared.
* Reply = false if a non-daily interval was specified for a date-only recurrence.
*/
bool KAEvent::setRepetition(const Repetition& r)
{
return d->setRepetition(r);
}
bool KAEventPrivate::setRepetition(const Repetition& repetition)
{
// Don't set mRepetition to zero at the start of this function, in case the
// 'repetition' parameter passed in is a reference to mRepetition.
mNextRepeat = 0;
if (repetition && !mRepeatAtLogin)
{
Q_ASSERT(checkRecur() != KARecurrence::NO_RECUR);
if (!repetition.isDaily() && mStartDateTime.isDateOnly())
{
mRepetition.set(0, 0);
return false; // interval must be in units of days for date-only alarms
}
Duration longestInterval = mRecurrence->longestInterval();
if (repetition.duration() >= longestInterval)
{
const int count = mStartDateTime.isDateOnly()
? (longestInterval.asDays() - 1) / repetition.intervalDays()
: (longestInterval.asSeconds() - 1) / repetition.intervalSeconds();
mRepetition.set(repetition.interval(), count);
}
else
mRepetition = repetition;
mTriggerChanged = true;
}
else if (mRepetition)
{
mRepetition.set(0, 0);
mTriggerChanged = true;
}
return true;
}
Repetition KAEvent::repetition() const
{
return d->mRepetition;
}
int KAEvent::nextRepetition() const
{
return d->mNextRepeat;
}
/******************************************************************************
* Return the repetition interval as text suitable for display.
*/
QString KAEvent::repetitionText(bool brief) const
{
if (d->mRepetition)
{
if (!d->mRepetition.isDaily())
{
const int minutes = d->mRepetition.intervalMinutes();
if (minutes < 60)
return i18ncp("@info/plain", "1 Minute", "%1 Minutes", minutes);
if (minutes % 60 == 0)
return i18ncp("@info/plain", "1 Hour", "%1 Hours", minutes/60);
QString mins;
return i18nc("@info/plain Hours and minutes", "%1h %2m", minutes/60, mins.sprintf("%02d", minutes%60));
}
const int days = d->mRepetition.intervalDays();
if (days % 7)
return i18ncp("@info/plain", "1 Day", "%1 Days", days);
return i18ncp("@info/plain", "1 Week", "%1 Weeks", days / 7);
}
return brief ? QString() : i18nc("@info/plain No repetition", "None");
}
/******************************************************************************
* Determine whether the event will occur after the specified date/time.
* If 'includeRepetitions' is true and the alarm has a sub-repetition, it
* returns true if any repetitions occur after the specified date/time.
*/
bool KAEvent::occursAfter(const KDateTime& preDateTime, bool includeRepetitions) const
{
return d->occursAfter(preDateTime, includeRepetitions);
}
bool KAEventPrivate::occursAfter(const KDateTime& preDateTime, bool includeRepetitions) const
{
KDateTime dt;
if (checkRecur() != KARecurrence::NO_RECUR)
{
if (mRecurrence->duration() < 0)
return true; // infinite recurrence
dt = mRecurrence->endDateTime();
}
else
dt = mNextMainDateTime.effectiveKDateTime();
if (mStartDateTime.isDateOnly())
{
QDate pre = preDateTime.date();
if (preDateTime.toTimeSpec(mStartDateTime.timeSpec()).time() < DateTime::startOfDay())
pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come
if (pre < dt.date())
return true;
}
else if (preDateTime < dt)
return true;
if (includeRepetitions && mRepetition)
{
if (preDateTime < mRepetition.duration().end(dt))
return true;
}
return false;
}
/******************************************************************************
* Set the date/time of the event to the next scheduled occurrence after the
* specified date/time, provided that this is later than its current date/time.
* Any reminder alarm is adjusted accordingly.
* If the alarm has a sub-repetition, and a repetition of a previous recurrence
* occurs after the specified date/time, that repetition is set as the next
* occurrence.
*/
KAEvent::OccurType KAEvent::setNextOccurrence(const KDateTime& preDateTime)
{
return d->setNextOccurrence(preDateTime);
}
KAEvent::OccurType KAEventPrivate::setNextOccurrence(const KDateTime& preDateTime)
{
if (preDateTime < mNextMainDateTime.effectiveKDateTime())
return KAEvent::FIRST_OR_ONLY_OCCURRENCE; // it might not be the first recurrence - tant pis
KDateTime pre = preDateTime;
// If there are repetitions, adjust the comparison date/time so that
// we find the earliest recurrence which has a repetition falling after
// the specified preDateTime.
if (mRepetition)
pre = mRepetition.duration(-mRepetition.count()).end(preDateTime);
DateTime afterPre; // next recurrence after 'pre'
KAEvent::OccurType type;
if (pre < mNextMainDateTime.effectiveKDateTime())
{
afterPre = mNextMainDateTime;
type = KAEvent::FIRST_OR_ONLY_OCCURRENCE; // may not actually be the first occurrence
}
else if (checkRecur() != KARecurrence::NO_RECUR)
{
type = nextRecurrence(pre, afterPre);
if (type == KAEvent::NO_OCCURRENCE)
return KAEvent::NO_OCCURRENCE;
if (type != KAEvent::FIRST_OR_ONLY_OCCURRENCE && afterPre != mNextMainDateTime)
{
// Need to reschedule the next trigger date/time
mNextMainDateTime = afterPre;
if (mReminderMinutes > 0 && (mDeferral == REMINDER_DEFERRAL || mReminderActive != ACTIVE_REMINDER))
{
// Reinstate the advance reminder for the rescheduled recurrence.
// Note that a reminder AFTER the main alarm will be left active.
activate_reminder(!mReminderOnceOnly);
}
if (mDeferral == REMINDER_DEFERRAL)
set_deferral(NO_DEFERRAL);
mTriggerChanged = true;
}
}
else
return KAEvent::NO_OCCURRENCE;
if (mRepetition)
{
if (afterPre <= preDateTime)
{
// The next occurrence is a sub-repetition.
type = static_cast<KAEvent::OccurType>(type | KAEvent::OCCURRENCE_REPEAT);
mNextRepeat = mRepetition.nextRepeatCount(afterPre.effectiveKDateTime(), preDateTime);
// Repetitions can't have a reminder, so remove any.
activate_reminder(false);
if (mDeferral == REMINDER_DEFERRAL)
set_deferral(NO_DEFERRAL);
mTriggerChanged = true;
}
else if (mNextRepeat)
{
// The next occurrence is the main occurrence, not a repetition
mNextRepeat = 0;
mTriggerChanged = true;
}
}
return type;
}
/******************************************************************************
* Get the date/time of the next occurrence of the event, after the specified
* date/time.
* 'result' = date/time of next occurrence, or invalid date/time if none.
*/
KAEvent::OccurType KAEvent::nextOccurrence(const KDateTime& preDateTime, DateTime& result, OccurOption o) const
{
return d->nextOccurrence(preDateTime, result, o);
}
KAEvent::OccurType KAEventPrivate::nextOccurrence(const KDateTime& preDateTime, DateTime& result,
KAEvent::OccurOption includeRepetitions) const
{
KDateTime pre = preDateTime;
if (includeRepetitions != KAEvent::IGNORE_REPETITION)
{ // RETURN_REPETITION or ALLOW_FOR_REPETITION
if (!mRepetition)
includeRepetitions = KAEvent::IGNORE_REPETITION;
else
pre = mRepetition.duration(-mRepetition.count()).end(preDateTime);
}
KAEvent::OccurType type;
const bool recurs = (checkRecur() != KARecurrence::NO_RECUR);
if (recurs)
type = nextRecurrence(pre, result);
else if (pre < mNextMainDateTime.effectiveKDateTime())
{
result = mNextMainDateTime;
type = KAEvent::FIRST_OR_ONLY_OCCURRENCE;
}
else
{
result = DateTime();
type = KAEvent::NO_OCCURRENCE;
}
if (type != KAEvent::NO_OCCURRENCE && result <= preDateTime && includeRepetitions != KAEvent::IGNORE_REPETITION)
{ // RETURN_REPETITION or ALLOW_FOR_REPETITION
// The next occurrence is a sub-repetition
int repetition = mRepetition.nextRepeatCount(result.kDateTime(), preDateTime);
const DateTime repeatDT = mRepetition.duration(repetition).end(result.kDateTime());
if (recurs)
{
// We've found a recurrence before the specified date/time, which has
// a sub-repetition after the date/time.
// However, if the intervals between recurrences vary, we could possibly
// have missed a later recurrence which fits the criterion, so check again.
DateTime dt;
const KAEvent::OccurType newType = previousOccurrence(repeatDT.effectiveKDateTime(), dt, false);
if (dt > result)
{
type = newType;
result = dt;
if (includeRepetitions == KAEvent::RETURN_REPETITION && result <= preDateTime)
{
// The next occurrence is a sub-repetition
repetition = mRepetition.nextRepeatCount(result.kDateTime(), preDateTime);
result = mRepetition.duration(repetition).end(result.kDateTime());
type = static_cast<KAEvent::OccurType>(type | KAEvent::OCCURRENCE_REPEAT);
}
return type;
}
}
if (includeRepetitions == KAEvent::RETURN_REPETITION)
{
// The next occurrence is a sub-repetition
result = repeatDT;
type = static_cast<KAEvent::OccurType>(type | KAEvent::OCCURRENCE_REPEAT);
}
}
return type;
}
/******************************************************************************
* Get the date/time of the last previous occurrence of the event, before the
* specified date/time.
* If 'includeRepetitions' is true and the alarm has a sub-repetition, the
* last previous repetition is returned if appropriate.
* 'result' = date/time of previous occurrence, or invalid date/time if none.
*/
KAEvent::OccurType KAEvent::previousOccurrence(const KDateTime& afterDateTime, DateTime& result, bool includeRepetitions) const
{
return d->previousOccurrence(afterDateTime, result, includeRepetitions);
}
KAEvent::OccurType KAEventPrivate::previousOccurrence(const KDateTime& afterDateTime, DateTime& result,
bool includeRepetitions) const
{
Q_ASSERT(!afterDateTime.isDateOnly());
if (mStartDateTime >= afterDateTime)
{
result = KDateTime();
return KAEvent::NO_OCCURRENCE; // the event starts after the specified date/time
}
// Find the latest recurrence of the event
KAEvent::OccurType type;
if (checkRecur() == KARecurrence::NO_RECUR)
{
result = mStartDateTime;
type = KAEvent::FIRST_OR_ONLY_OCCURRENCE;
}
else
{
const KDateTime recurStart = mRecurrence->startDateTime();
KDateTime after = afterDateTime.toTimeSpec(mStartDateTime.timeSpec());
if (mStartDateTime.isDateOnly() && afterDateTime.time() > DateTime::startOfDay())
after = after.addDays(1); // today's recurrence (if today recurs) has passed
const KDateTime dt = mRecurrence->getPreviousDateTime(after);
result = dt;
result.setDateOnly(mStartDateTime.isDateOnly());
if (!dt.isValid())
return KAEvent::NO_OCCURRENCE;
if (dt == recurStart)
type = KAEvent::FIRST_OR_ONLY_OCCURRENCE;
else if (mRecurrence->getNextDateTime(dt).isValid())
type = result.isDateOnly() ? KAEvent::RECURRENCE_DATE : KAEvent::RECURRENCE_DATE_TIME;
else
type = KAEvent::LAST_RECURRENCE;
}
if (includeRepetitions && mRepetition)
{
// Find the latest repetition which is before the specified time.
const int repetition = mRepetition.previousRepeatCount(result.effectiveKDateTime(), afterDateTime);
if (repetition > 0)
{
result = mRepetition.duration(qMin(repetition, mRepetition.count())).end(result.kDateTime());
return static_cast<KAEvent::OccurType>(type | KAEvent::OCCURRENCE_REPEAT);
}
}
return type;
}
/******************************************************************************
* Set the event to be a copy of the specified event, making the specified
* alarm the 'displaying' alarm.
* The purpose of setting up a 'displaying' alarm is to be able to reinstate
* the alarm message in case of a crash, or to reinstate it should the user
* choose to defer the alarm. Note that even repeat-at-login alarms need to be
* saved in case their end time expires before the next login.
* Reply = true if successful, false if alarm was not copied.
*/
#ifndef KALARMCAL_USE_KRESOURCES
bool KAEvent::setDisplaying(const KAEvent& e, KAAlarm::Type t, Akonadi::Collection::Id id, const KDateTime& dt, bool showEdit, bool showDefer)
#else
bool KAEvent::setDisplaying(const KAEvent& e, KAAlarm::Type t, const QString& id, const KDateTime& dt, bool showEdit, bool showDefer)
#endif
{
return d->setDisplaying(*e.d, t, id, dt, showEdit, showDefer);
}
#ifndef KALARMCAL_USE_KRESOURCES
bool KAEventPrivate::setDisplaying(const KAEventPrivate& event, KAAlarm::Type alarmType, Akonadi::Collection::Id collectionId,
const KDateTime& repeatAtLoginTime, bool showEdit, bool showDefer)
#else
bool KAEventPrivate::setDisplaying(const KAEventPrivate& event, KAAlarm::Type alarmType, const QString& resourceID,
const KDateTime& repeatAtLoginTime, bool showEdit, bool showDefer)
#endif
{
if (!mDisplaying
&& (alarmType == KAAlarm::MAIN_ALARM
|| alarmType == KAAlarm::REMINDER_ALARM
|| alarmType == KAAlarm::DEFERRED_REMINDER_ALARM
|| alarmType == KAAlarm::DEFERRED_ALARM
|| alarmType == KAAlarm::AT_LOGIN_ALARM))
{
//kDebug()<<event.id()<<","<<(alarmType==KAAlarm::MAIN_ALARM?"MAIN":alarmType==KAAlarm::REMINDER_ALARM?"REMINDER":alarmType==KAAlarm::DEFERRED_REMINDER_ALARM?"REMINDER_DEFERRAL":alarmType==KAAlarm::DEFERRED_ALARM?"DEFERRAL":"LOGIN")<<"): time="<<repeatAtLoginTime.toString();
KAAlarm al = event.alarm(alarmType);
if (al.isValid())
{
*this = event;
// Change the event ID to avoid duplicating the same unique ID as the original event
setCategory(CalEvent::DISPLAYING);
#ifndef KALARMCAL_USE_KRESOURCES
mItemId = -1; // the display event doesn't have an associated Item
mCollectionId = collectionId; // original collection ID which contained the event
#else
mOriginalResourceId = resourceID;
#endif
mDisplayingDefer = showDefer;
mDisplayingEdit = showEdit;
mDisplaying = true;
mDisplayingTime = (alarmType == KAAlarm::AT_LOGIN_ALARM) ? repeatAtLoginTime : al.dateTime().kDateTime();
switch (al.type())
{
case KAAlarm::AT_LOGIN_ALARM: mDisplayingFlags = KAEvent::REPEAT_AT_LOGIN; break;
case KAAlarm::REMINDER_ALARM: mDisplayingFlags = REMINDER; break;
case KAAlarm::DEFERRED_REMINDER_ALARM: mDisplayingFlags = al.timedDeferral() ? (REMINDER | TIME_DEFERRAL) : (REMINDER | DATE_DEFERRAL); break;
case KAAlarm::DEFERRED_ALARM: mDisplayingFlags = al.timedDeferral() ? TIME_DEFERRAL : DATE_DEFERRAL; break;
default: mDisplayingFlags = 0; break;
}
++mAlarmCount;
return true;
}
}
return false;
}
/******************************************************************************
* Reinstate the original event from the 'displaying' event.
*/
#ifndef KALARMCAL_USE_KRESOURCES
void KAEvent::reinstateFromDisplaying(const KCalCore::Event::Ptr& e, Akonadi::Collection::Id& id, bool& showEdit, bool& showDefer)
#else
void KAEvent::reinstateFromDisplaying(const KCal::Event* e, QString& id, bool& showEdit, bool& showDefer)
#endif
{
d->reinstateFromDisplaying(e, id, showEdit, showDefer);
}
#ifndef KALARMCAL_USE_KRESOURCES
void KAEventPrivate::reinstateFromDisplaying(const Event::Ptr& kcalEvent, Akonadi::Collection::Id& collectionId, bool& showEdit, bool& showDefer)
#else
void KAEventPrivate::reinstateFromDisplaying(const Event* kcalEvent, QString& resourceID, bool& showEdit, bool& showDefer)
#endif
{
set(kcalEvent);
if (mDisplaying)
{
// Retrieve the original event's unique ID
setCategory(CalEvent::ACTIVE);
#ifndef KALARMCAL_USE_KRESOURCES
collectionId = mCollectionId;
mCollectionId = -1;
#else
resourceID = mOriginalResourceId;
mOriginalResourceId.clear();
#endif
showDefer = mDisplayingDefer;
showEdit = mDisplayingEdit;
mDisplaying = false;
--mAlarmCount;
}
}
/******************************************************************************
* Return the original alarm which the displaying alarm refers to.
* Note that the caller is responsible for ensuring that the event was a
* displaying event, since this is normally called after
* reinstateFromDisplaying(), which clears mDisplaying.
*/
KAAlarm KAEvent::convertDisplayingAlarm() const
{
KAAlarm al = alarm(KAAlarm::DISPLAYING_ALARM);
KAAlarm::Private* const al_d = al.d;
const int displayingFlags = d->mDisplayingFlags;
if (displayingFlags & REPEAT_AT_LOGIN)
{
al_d->mRepeatAtLogin = true;
al_d->mType = KAAlarm::AT_LOGIN_ALARM;
}
else if (displayingFlags & KAEventPrivate::DEFERRAL)
{
al_d->mDeferred = true;
al_d->mTimedDeferral = (displayingFlags & KAEventPrivate::TIMED_FLAG);
al_d->mType = (displayingFlags & KAEventPrivate::REMINDER) ? KAAlarm::DEFERRED_REMINDER_ALARM : KAAlarm::DEFERRED_ALARM;
}
else if (displayingFlags & KAEventPrivate::REMINDER)
al_d->mType = KAAlarm::REMINDER_ALARM;
else
al_d->mType = KAAlarm::MAIN_ALARM;
return al;
}
bool KAEvent::displaying() const
{
return d->mDisplaying;
}
/******************************************************************************
* Return the alarm of the specified type.
*/
KAAlarm KAEvent::alarm(KAAlarm::Type t) const
{
return d->alarm(t);
}
KAAlarm KAEventPrivate::alarm(KAAlarm::Type type) const
{
checkRecur(); // ensure recurrence/repetition data is consistent
KAAlarm al; // this sets type to INVALID_ALARM
KAAlarm::Private* const al_d = al.d;
if (mAlarmCount)
{
al_d->mActionType = static_cast<KAAlarm::Action>(mActionSubType);
al_d->mRepeatAtLogin = false;
al_d->mDeferred = false;
switch (type)
{
case KAAlarm::MAIN_ALARM:
if (!mMainExpired)
{
al_d->mType = KAAlarm::MAIN_ALARM;
al_d->mNextMainDateTime = mNextMainDateTime;
al_d->mRepetition = mRepetition;
al_d->mNextRepeat = mNextRepeat;
}
break;
case KAAlarm::REMINDER_ALARM:
if (mReminderActive == ACTIVE_REMINDER)
{
al_d->mType = KAAlarm::REMINDER_ALARM;
if (mReminderMinutes < 0)
al_d->mNextMainDateTime = mReminderAfterTime;
else if (mReminderOnceOnly)
al_d->mNextMainDateTime = mStartDateTime.addMins(-mReminderMinutes);
else
al_d->mNextMainDateTime = mNextMainDateTime.addMins(-mReminderMinutes);
}
break;
case KAAlarm::DEFERRED_REMINDER_ALARM:
if (mDeferral != REMINDER_DEFERRAL)
break;
// fall through to DEFERRED_ALARM
case KAAlarm::DEFERRED_ALARM:
if (mDeferral != NO_DEFERRAL)
{
al_d->mType = (mDeferral == REMINDER_DEFERRAL) ? KAAlarm::DEFERRED_REMINDER_ALARM : KAAlarm::DEFERRED_ALARM;
al_d->mNextMainDateTime = mDeferralTime;
al_d->mDeferred = true;
al_d->mTimedDeferral = !mDeferralTime.isDateOnly();
}
break;
case KAAlarm::AT_LOGIN_ALARM:
if (mRepeatAtLogin)
{
al_d->mType = KAAlarm::AT_LOGIN_ALARM;
al_d->mNextMainDateTime = mAtLoginDateTime;
al_d->mRepeatAtLogin = true;
}
break;
case KAAlarm::DISPLAYING_ALARM:
if (mDisplaying)
{
al_d->mType = KAAlarm::DISPLAYING_ALARM;
al_d->mNextMainDateTime = mDisplayingTime;
}
break;
case KAAlarm::INVALID_ALARM:
default:
break;
}
}
return al;
}
/******************************************************************************
* Return the main alarm for the event.
* If the main alarm does not exist, one of the subsidiary ones is returned if
* possible.
* N.B. a repeat-at-login alarm can only be returned if it has been read from/
* written to the calendar file.
*/
KAAlarm KAEvent::firstAlarm() const
{
return d->firstAlarm();
}
KAAlarm KAEventPrivate::firstAlarm() const
{
if (mAlarmCount)
{
if (!mMainExpired)
return alarm(KAAlarm::MAIN_ALARM);
return nextAlarm(KAAlarm::MAIN_ALARM);
}
return KAAlarm();
}
/******************************************************************************
* Return the next alarm for the event, after the specified alarm.
* N.B. a repeat-at-login alarm can only be returned if it has been read from/
* written to the calendar file.
*/
KAAlarm KAEvent::nextAlarm(const KAAlarm& previousAlarm) const
{
return d->nextAlarm(previousAlarm.type());
}
KAAlarm KAEvent::nextAlarm(KAAlarm::Type previousType) const
{
return d->nextAlarm(previousType);
}
KAAlarm KAEventPrivate::nextAlarm(KAAlarm::Type previousType) const
{
switch (previousType)
{
case KAAlarm::MAIN_ALARM:
if (mReminderActive == ACTIVE_REMINDER)
return alarm(KAAlarm::REMINDER_ALARM);
// fall through to REMINDER_ALARM
case KAAlarm::REMINDER_ALARM:
// There can only be one deferral alarm
if (mDeferral == REMINDER_DEFERRAL)
return alarm(KAAlarm::DEFERRED_REMINDER_ALARM);
if (mDeferral == NORMAL_DEFERRAL)
return alarm(KAAlarm::DEFERRED_ALARM);
// fall through to DEFERRED_ALARM
case KAAlarm::DEFERRED_REMINDER_ALARM:
case KAAlarm::DEFERRED_ALARM:
if (mRepeatAtLogin)
return alarm(KAAlarm::AT_LOGIN_ALARM);
// fall through to AT_LOGIN_ALARM
case KAAlarm::AT_LOGIN_ALARM:
if (mDisplaying)
return alarm(KAAlarm::DISPLAYING_ALARM);
// fall through to DISPLAYING_ALARM
case KAAlarm::DISPLAYING_ALARM:
// fall through to default
case KAAlarm::INVALID_ALARM:
default:
break;
}
return KAAlarm();
}
int KAEvent::alarmCount() const
{
return d->mAlarmCount;
}
/******************************************************************************
* Remove the alarm of the specified type from the event.
* This must only be called to remove an alarm which has expired, not to
* reconfigure the event.
*/
void KAEvent::removeExpiredAlarm(KAAlarm::Type type)
{
d->removeExpiredAlarm(type);
}
void KAEventPrivate::removeExpiredAlarm(KAAlarm::Type type)
{
const int count = mAlarmCount;
switch (type)
{
case KAAlarm::MAIN_ALARM:
if (!mReminderActive || mReminderMinutes > 0)
{
mAlarmCount = 0; // removing main alarm - also remove subsidiary alarms
break;
}
// There is a reminder after the main alarm - retain the
// reminder and remove other subsidiary alarms.
mMainExpired = true; // mark the alarm as expired now
--mAlarmCount;
set_deferral(NO_DEFERRAL);
if (mDisplaying)
{
mDisplaying = false;
--mAlarmCount;
}
// fall through to AT_LOGIN_ALARM
case KAAlarm::AT_LOGIN_ALARM:
if (mRepeatAtLogin)
{
// Remove the at-login alarm, but keep a note of it for archiving purposes
mArchiveRepeatAtLogin = true;
mRepeatAtLogin = false;
--mAlarmCount;
}
break;
case KAAlarm::REMINDER_ALARM:
// Remove any reminder alarm, but keep a note of it for archiving purposes
// and for restoration after the next recurrence.
activate_reminder(false);
break;
case KAAlarm::DEFERRED_REMINDER_ALARM:
case KAAlarm::DEFERRED_ALARM:
set_deferral(NO_DEFERRAL);
break;
case KAAlarm::DISPLAYING_ALARM:
if (mDisplaying)
{
mDisplaying = false;
--mAlarmCount;
}
break;
case KAAlarm::INVALID_ALARM:
default:
break;
}
if (mAlarmCount != count)
mTriggerChanged = true;
}
void KAEvent::startChanges()
{
d->startChanges();
}
/******************************************************************************
* Indicate that changes to the instance are complete.
* This allows trigger times to be recalculated if any changes have occurred.
*/
void KAEvent::endChanges()
{
d->endChanges();
}
void KAEventPrivate::endChanges()
{
if (mChangeCount > 0)
--mChangeCount;
}
#ifndef KALARMCAL_USE_KRESOURCES
/******************************************************************************
* Return a list of pointers to KAEvent objects.
*/
KAEvent::List KAEvent::ptrList(QVector<KAEvent>& objList)
{
KAEvent::List ptrs;
for (int i = 0, count = objList.count(); i < count; ++i)
ptrs += &objList[i];
return ptrs;
}
#endif
void KAEvent::dumpDebug() const
{
#ifndef KDE_NO_DEBUG_OUTPUT
d->dumpDebug();
#endif
}
#ifndef KDE_NO_DEBUG_OUTPUT
void KAEventPrivate::dumpDebug() const
{
kDebug() << "KAEvent dump:";
#ifdef KALARMCAL_USE_KRESOURCES
if (mResource) { kDebug() << "-- mResource:" << (void*)mResource; }
#endif
kDebug() << "-- mEventID:" << mEventID;
kDebug() << "-- mActionSubType:" << (mActionSubType == KAEvent::MESSAGE ? "MESSAGE" : mActionSubType == KAEvent::FILE ? "FILE" : mActionSubType == KAEvent::COMMAND ? "COMMAND" : mActionSubType == KAEvent::EMAIL ? "EMAIL" : mActionSubType == KAEvent::AUDIO ? "AUDIO" : "??");
kDebug() << "-- mNextMainDateTime:" << mNextMainDateTime.toString();
kDebug() << "-- mCommandError:" << mCommandError;
kDebug() << "-- mAllTrigger:" << mAllTrigger.toString();
kDebug() << "-- mMainTrigger:" << mMainTrigger.toString();
kDebug() << "-- mAllWorkTrigger:" << mAllWorkTrigger.toString();
kDebug() << "-- mMainWorkTrigger:" << mMainWorkTrigger.toString();
kDebug() << "-- mCategory:" << mCategory;
if (!mTemplateName.isEmpty())
{
kDebug() << "-- mTemplateName:" << mTemplateName;
kDebug() << "-- mTemplateAfterTime:" << mTemplateAfterTime;
}
kDebug() << "-- mText:" << mText;
if (mActionSubType == KAEvent::MESSAGE || mActionSubType == KAEvent::FILE)
{
kDebug() << "-- mBgColour:" << mBgColour.name();
kDebug() << "-- mFgColour:" << mFgColour.name();
kDebug() << "-- mUseDefaultFont:" << mUseDefaultFont;
if (!mUseDefaultFont)
kDebug() << "-- mFont:" << mFont.toString();
kDebug() << "-- mSpeak:" << mSpeak;
kDebug() << "-- mAudioFile:" << mAudioFile;
kDebug() << "-- mPreAction:" << mPreAction;
kDebug() << "-- mExecPreActOnDeferral:" << (mExtraActionOptions & KAEvent::ExecPreActOnDeferral);
kDebug() << "-- mCancelOnPreActErr:" << (mExtraActionOptions & KAEvent::CancelOnPreActError);
kDebug() << "-- mDontShowPreActErr:" << (mExtraActionOptions & KAEvent::DontShowPreActError);
kDebug() << "-- mPostAction:" << mPostAction;
kDebug() << "-- mLateCancel:" << mLateCancel;
kDebug() << "-- mAutoClose:" << mAutoClose;
}
else if (mActionSubType == KAEvent::COMMAND)
{
kDebug() << "-- mCommandScript:" << mCommandScript;
kDebug() << "-- mCommandXterm:" << mCommandXterm;
kDebug() << "-- mCommandDisplay:" << mCommandDisplay;
kDebug() << "-- mLogFile:" << mLogFile;
}
else if (mActionSubType == KAEvent::EMAIL)
{
kDebug() << "-- mEmail: FromKMail:" << mEmailFromIdentity;
kDebug() << "-- Addresses:" << mEmailAddresses.join(QLatin1String(","));
kDebug() << "-- Subject:" << mEmailSubject;
kDebug() << "-- Attachments:" << mEmailAttachments.join(QLatin1String(","));
kDebug() << "-- Bcc:" << mEmailBcc;
}
else if (mActionSubType == KAEvent::AUDIO)
kDebug() << "-- mAudioFile:" << mAudioFile;
kDebug() << "-- mBeep:" << mBeep;
if (mActionSubType == KAEvent::AUDIO || !mAudioFile.isEmpty())
{
if (mSoundVolume >= 0)
{
kDebug() << "-- mSoundVolume:" << mSoundVolume;
if (mFadeVolume >= 0)
{
kDebug() << "-- mFadeVolume:" << mFadeVolume;
kDebug() << "-- mFadeSeconds:" << mFadeSeconds;
}
else
kDebug() << "-- mFadeVolume:-:";
}
else
kDebug() << "-- mSoundVolume:-:";
kDebug() << "-- mRepeatSoundPause:" << mRepeatSoundPause;
}
kDebug() << "-- mKMailSerialNumber:" << mKMailSerialNumber;
kDebug() << "-- mCopyToKOrganizer:" << mCopyToKOrganizer;
kDebug() << "-- mExcludeHolidays:" << (bool)mExcludeHolidays;
kDebug() << "-- mWorkTimeOnly:" << mWorkTimeOnly;
kDebug() << "-- mStartDateTime:" << mStartDateTime.toString();
kDebug() << "-- mCreatedDateTime:" << mCreatedDateTime;
kDebug() << "-- mRepeatAtLogin:" << mRepeatAtLogin;
if (mRepeatAtLogin)
kDebug() << "-- mAtLoginDateTime:" << mAtLoginDateTime;
kDebug() << "-- mArchiveRepeatAtLogin:" << mArchiveRepeatAtLogin;
kDebug() << "-- mConfirmAck:" << mConfirmAck;
kDebug() << "-- mEnabled:" << mEnabled;
#ifndef KALARMCAL_USE_KRESOURCES
kDebug() << "-- mItemId:" << mItemId;
kDebug() << "-- mCollectionId:" << mCollectionId;
kDebug() << "-- mCompatibility:" << mCompatibility;
kDebug() << "-- mReadOnly:" << mReadOnly;
#endif
if (mReminderMinutes)
{
kDebug() << "-- mReminderMinutes:" << mReminderMinutes;
kDebug() << "-- mReminderActive:" << (mReminderActive == ACTIVE_REMINDER ? "active" : mReminderActive == HIDDEN_REMINDER ? "hidden" : "no");
kDebug() << "-- mReminderOnceOnly:" << mReminderOnceOnly;
}
else if (mDeferral > 0)
{
kDebug() << "-- mDeferral:" << (mDeferral == NORMAL_DEFERRAL ? "normal" : "reminder");
kDebug() << "-- mDeferralTime:" << mDeferralTime.toString();
}
kDebug() << "-- mDeferDefaultMinutes:" << mDeferDefaultMinutes;
if (mDeferDefaultMinutes)
kDebug() << "-- mDeferDefaultDateOnly:" << mDeferDefaultDateOnly;
if (mDisplaying)
{
kDebug() << "-- mDisplayingTime:" << mDisplayingTime.toString();
kDebug() << "-- mDisplayingFlags:" << mDisplayingFlags;
kDebug() << "-- mDisplayingDefer:" << mDisplayingDefer;
kDebug() << "-- mDisplayingEdit:" << mDisplayingEdit;
}
kDebug() << "-- mRevision:" << mRevision;
kDebug() << "-- mRecurrence:" << mRecurrence;
if (!mRepetition)
kDebug() << "-- mRepetition: 0";
else if (mRepetition.isDaily())
kDebug() << "-- mRepetition: count:" << mRepetition.count() << ", interval:" << mRepetition.intervalDays() << "days";
else
kDebug() << "-- mRepetition: count:" << mRepetition.count() << ", interval:" << mRepetition.intervalMinutes() << "minutes";
kDebug() << "-- mNextRepeat:" << mNextRepeat;
kDebug() << "-- mAlarmCount:" << mAlarmCount;
kDebug() << "-- mMainExpired:" << mMainExpired;
kDebug() << "-- mDisplaying:" << mDisplaying;
kDebug() << "KAEvent dump end";
}
#endif
/******************************************************************************
* Fetch the start and next date/time for a KCal::Event.
* Reply = next main date/time.
*/
#ifndef KALARMCAL_USE_KRESOURCES
DateTime KAEventPrivate::readDateTime(const Event::Ptr& event, bool dateOnly, DateTime& start)
#else
DateTime KAEventPrivate::readDateTime(const Event* event, bool dateOnly, DateTime& start)
#endif
{
start = event->dtStart();
if (dateOnly)
{
// A date-only event is indicated by the X-KDE-KALARM-FLAGS:DATE property, not
// by a date-only start date/time (for the reasons given in updateKCalEvent()).
start.setDateOnly(true);
}
DateTime next = start;
const int SZ_YEAR = 4; // number of digits in year value
const int SZ_MONTH = 2; // number of digits in month value
const int SZ_DAY = 2; // number of digits in day value
const int SZ_DATE = SZ_YEAR + SZ_MONTH + SZ_DAY; // total size of date value
const int IX_TIME = SZ_DATE + 1; // offset to time value
const int SZ_HOUR = 2; // number of digits in hour value
const int SZ_MIN = 2; // number of digits in minute value
const int SZ_SEC = 2; // number of digits in second value
const int SZ_TIME = SZ_HOUR + SZ_MIN + SZ_SEC; // total size of time value
const QString prop = event->customProperty(KACalendar::APPNAME, KAEventPrivate::NEXT_RECUR_PROPERTY);
if (prop.length() >= SZ_DATE)
{
// The next due recurrence time is specified
const QDate d(prop.left(SZ_YEAR).toInt(),
prop.mid(SZ_YEAR, SZ_MONTH).toInt(),
prop.mid(SZ_YEAR + SZ_MONTH, SZ_DAY).toInt());
if (d.isValid())
{
if (dateOnly && prop.length() == SZ_DATE)
next.setDate(d);
else if (!dateOnly && prop.length() == IX_TIME + SZ_TIME && prop[SZ_DATE] == QLatin1Char('T'))
{
const QTime t(prop.mid(IX_TIME, SZ_HOUR).toInt(),
prop.mid(IX_TIME + SZ_HOUR, SZ_MIN).toInt(),
prop.mid(IX_TIME + SZ_HOUR + SZ_MIN, SZ_SEC).toInt());
if (t.isValid())
{
next.setDate(d);
next.setTime(t);
}
}
if (next < start)
next = start; // ensure next recurrence time is valid
}
}
return next;
}
/******************************************************************************
* Parse the alarms for a KCal::Event.
* Reply = map of alarm data, indexed by KAAlarm::Type
*/
#ifndef KALARMCAL_USE_KRESOURCES
void KAEventPrivate::readAlarms(const Event::Ptr& event, AlarmMap* alarmMap, bool cmdDisplay)
#else
void KAEventPrivate::readAlarms(const Event* event, AlarmMap* alarmMap, bool cmdDisplay)
#endif
{
const Alarm::List alarms = event->alarms();
// Check if it's an audio event with no display alarm
bool audioOnly = false;
for (int i = 0, end = alarms.count(); i < end; ++i)
{
switch (alarms[i]->type())
{
case Alarm::Display:
case Alarm::Procedure:
audioOnly = false;
i = end; // exit from the 'for' loop
break;
case Alarm::Audio:
audioOnly = true;
break;
default:
break;
}
}
for (int i = 0, end = alarms.count(); i < end; ++i)
{
// Parse the next alarm's text
AlarmData data;
readAlarm(alarms[i], data, audioOnly, cmdDisplay);
if (data.type != INVALID_ALARM)
alarmMap->insert(data.type, data);
}
}
/******************************************************************************
* Parse a KCal::Alarm.
* If 'audioMain' is true, the event contains an audio alarm but no display alarm.
* Reply = alarm ID (sequence number)
*/
#ifndef KALARMCAL_USE_KRESOURCES
void KAEventPrivate::readAlarm(const Alarm::Ptr& alarm, AlarmData& data, bool audioMain, bool cmdDisplay)
#else
void KAEventPrivate::readAlarm(const Alarm* alarm, AlarmData& data, bool audioMain, bool cmdDisplay)
#endif
{
// Parse the next alarm's text
data.alarm = alarm;
data.displayingFlags = 0;
data.isEmailText = false;
data.speak = false;
data.hiddenReminder = false;
data.timedDeferral = false;
data.nextRepeat = 0;
data.repeatSoundPause = -1;
if (alarm->repeatCount())
{
bool ok;
const QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::NEXT_REPEAT_PROPERTY);
int n = static_cast<int>(property.toUInt(&ok));
if (ok)
data.nextRepeat = n;
}
QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY);
const QStringList flags = property.split(KAEventPrivate::SC, QString::SkipEmptyParts);
switch (alarm->type())
{
case Alarm::Procedure:
data.action = KAAlarm::COMMAND;
data.cleanText = alarm->programFile();
data.commandScript = data.cleanText.isEmpty(); // blank command indicates a script
if (!alarm->programArguments().isEmpty())
{
if (!data.commandScript)
data.cleanText += QLatin1Char(' ');
data.cleanText += alarm->programArguments();
}
data.extraActionOptions = 0;
if (flags.contains(KAEventPrivate::EXEC_ON_DEFERRAL_FLAG))
data.extraActionOptions |= KAEvent::ExecPreActOnDeferral;
if (flags.contains(KAEventPrivate::CANCEL_ON_ERROR_FLAG))
data.extraActionOptions |= KAEvent::CancelOnPreActError;
if (flags.contains(KAEventPrivate::DONT_SHOW_ERROR_FLAG))
data.extraActionOptions |= KAEvent::DontShowPreActError;
if (!cmdDisplay)
break;
// fall through to Display
case Alarm::Display:
{
if (alarm->type() == Alarm::Display)
{
data.action = KAAlarm::MESSAGE;
data.cleanText = AlarmText::fromCalendarText(alarm->text(), data.isEmailText);
}
const QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::FONT_COLOUR_PROPERTY);
const QStringList list = property.split(QLatin1Char(';'), QString::KeepEmptyParts);
data.bgColour = QColor(255, 255, 255); // white
data.fgColour = QColor(0, 0, 0); // black
const int n = list.count();
if (n > 0)
{
if (!list[0].isEmpty())
{
QColor c(list[0]);
if (c.isValid())
data.bgColour = c;
}
if (n > 1 && !list[1].isEmpty())
{
QColor c(list[1]);
if (c.isValid())
data.fgColour = c;
}
}
data.defaultFont = (n <= 2 || list[2].isEmpty());
if (!data.defaultFont)
data.font.fromString(list[2]);
break;
}
case Alarm::Email:
{
data.action = KAAlarm::EMAIL;
data.cleanText = alarm->mailText();
const int i = flags.indexOf(KAEventPrivate::EMAIL_ID_FLAG);
data.emailFromId = (i >= 0 && i + 1 < flags.count()) ? flags[i + 1].toUInt() : 0;
break;
}
case Alarm::Audio:
{
data.action = KAAlarm::AUDIO;
data.cleanText = alarm->audioFile();
data.repeatSoundPause = (alarm->repeatCount() == -2) ? alarm->snoozeTime().asSeconds()
: (alarm->repeatCount() == -1) ? 0 : -1;
data.soundVolume = -1;
data.fadeVolume = -1;
data.fadeSeconds = 0;
QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::VOLUME_PROPERTY);
if (!property.isEmpty())
{
bool ok;
float fadeVolume;
int fadeSecs = 0;
const QStringList list = property.split(QLatin1Char(';'), QString::KeepEmptyParts);
data.soundVolume = list[0].toFloat(&ok);
if (!ok || data.soundVolume > 1.0f)
data.soundVolume = -1;
if (data.soundVolume >= 0 && list.count() >= 3)
{
fadeVolume = list[1].toFloat(&ok);
if (ok)
fadeSecs = static_cast<int>(list[2].toUInt(&ok));
if (ok && fadeVolume >= 0 && fadeVolume <= 1.0f && fadeSecs > 0)
{
data.fadeVolume = fadeVolume;
data.fadeSeconds = fadeSecs;
}
}
}
if (!audioMain)
{
data.type = AUDIO_ALARM;
data.speak = flags.contains(KAEventPrivate::SPEAK_FLAG);
return;
}
break;
}
case Alarm::Invalid:
data.type = INVALID_ALARM;
return;
}
bool atLogin = false;
bool reminder = false;
bool deferral = false;
bool dateDeferral = false;
bool repeatSound = false;
data.type = MAIN_ALARM;
property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY);
const QStringList types = property.split(QLatin1Char(','), QString::SkipEmptyParts);
for (int i = 0, end = types.count(); i < end; ++i)
{
const QString type = types[i];
if (type == KAEventPrivate::AT_LOGIN_TYPE)
atLogin = true;
else if (type == KAEventPrivate::FILE_TYPE && data.action == KAAlarm::MESSAGE)
data.action = KAAlarm::FILE;
else if (type == KAEventPrivate::REMINDER_TYPE)
reminder = true;
else if (type == KAEventPrivate::TIME_DEFERRAL_TYPE)
deferral = true;
else if (type == KAEventPrivate::DATE_DEFERRAL_TYPE)
dateDeferral = deferral = true;
else if (type == KAEventPrivate::DISPLAYING_TYPE)
data.type = DISPLAYING_ALARM;
else if (type == KAEventPrivate::PRE_ACTION_TYPE && data.action == KAAlarm::COMMAND)
data.type = PRE_ACTION_ALARM;
else if (type == KAEventPrivate::POST_ACTION_TYPE && data.action == KAAlarm::COMMAND)
data.type = POST_ACTION_ALARM;
else if (type == KAEventPrivate::SOUND_REPEAT_TYPE && data.action == KAAlarm::AUDIO)
{
repeatSound = true;
if (i + 1 < end)
{
bool ok;
uint n = types[i + 1].toUInt(&ok);
if (ok)
{
data.repeatSoundPause = n;
++i;
}
}
}
}
if (repeatSound && data.repeatSoundPause < 0)
data.repeatSoundPause = 0;
else if (!repeatSound)
data.repeatSoundPause = -1;
if (reminder)
{
if (data.type == MAIN_ALARM)
{
data.type = deferral ? DEFERRED_REMINDER_ALARM : REMINDER_ALARM;
data.timedDeferral = (deferral && !dateDeferral);
}
else if (data.type == DISPLAYING_ALARM)
data.displayingFlags = dateDeferral ? REMINDER | DATE_DEFERRAL
: deferral ? REMINDER | TIME_DEFERRAL : REMINDER;
else if (data.type == REMINDER_ALARM
&& flags.contains(KAEventPrivate::HIDDEN_REMINDER_FLAG))
data.hiddenReminder = true;
}
else if (deferral)
{
if (data.type == MAIN_ALARM)
{
data.type = DEFERRED_ALARM;
data.timedDeferral = !dateDeferral;
}
else if (data.type == DISPLAYING_ALARM)
data.displayingFlags = dateDeferral ? DATE_DEFERRAL : TIME_DEFERRAL;
}
if (atLogin)
{
if (data.type == MAIN_ALARM)
data.type = AT_LOGIN_ALARM;
else if (data.type == DISPLAYING_ALARM)
data.displayingFlags = KAEvent::REPEAT_AT_LOGIN;
}
//kDebug()<<"text="<<alarm->text()<<", time="<<alarm->time().toString()<<", valid time="<<alarm->time().isValid();
}
/******************************************************************************
* Calculate the next trigger times of the alarm.
* This should only be called when changes have actually occurred which might
* affect the event's trigger times.
* mMainTrigger is set to the next scheduled recurrence/sub-repetition, or the
* deferral time if a deferral is pending.
* mAllTrigger is the same as mMainTrigger, but takes account of reminders.
* mMainWorkTrigger is set to the next scheduled recurrence/sub-repetition
* which occurs in working hours, if working-time-only is set.
* mAllWorkTrigger is the same as mMainWorkTrigger, but takes account of reminders.
*/
void KAEventPrivate::calcTriggerTimes() const
{
if (mChangeCount)
return;
#ifdef __GNUC__
#warning May need to set date-only alarms to after start-of-day time in working-time checks
#endif
bool recurs = (checkRecur() != KARecurrence::NO_RECUR);
if ((recurs && mWorkTimeOnly && mWorkTimeOnly != mWorkTimeIndex)
|| (recurs && mExcludeHolidays && mExcludeHolidays != mHolidays))
{
// It's a work time alarm, and work days/times have changed, or
// it excludes holidays, and the holidays definition has changed.
mTriggerChanged = true;
}
else if (!mTriggerChanged)
return;
mTriggerChanged = false;
if (recurs && mWorkTimeOnly)
mWorkTimeOnly = mWorkTimeIndex; // note which work time definition was used in calculation
if (recurs && mExcludeHolidays)
mExcludeHolidays = mHolidays; // note which holiday definition was used in calculation
if (mCategory == CalEvent::ARCHIVED || mCategory == CalEvent::TEMPLATE)
{
// It's a template or archived
mAllTrigger = mMainTrigger = mAllWorkTrigger = mMainWorkTrigger = KDateTime();
}
else if (mDeferral == NORMAL_DEFERRAL)
{
// For a deferred alarm, working time setting is ignored
mAllTrigger = mMainTrigger = mAllWorkTrigger = mMainWorkTrigger = mDeferralTime;
}
else
{
mMainTrigger = mainDateTime(true); // next recurrence or sub-repetition
mAllTrigger = (mDeferral == REMINDER_DEFERRAL) ? mDeferralTime
: (mReminderActive != ACTIVE_REMINDER) ? mMainTrigger
: (mReminderMinutes < 0) ? mReminderAfterTime
: mMainTrigger.addMins(-mReminderMinutes);
// It's not deferred.
// If only-during-working-time is set and it recurs, it won't actually trigger
// unless it falls during working hours.
if ((!mWorkTimeOnly && !mExcludeHolidays)
|| !recurs
|| isWorkingTime(mMainTrigger.kDateTime()))
{
// It only occurs once, or it complies with any working hours/holiday
// restrictions.
mMainWorkTrigger = mMainTrigger;
mAllWorkTrigger = mAllTrigger;
}
else if (mWorkTimeOnly)
{
// The alarm is restricted to working hours.
// Finding the next occurrence during working hours can sometimes take a long time,
// so mark the next actual trigger as invalid until the calculation completes.
// Note that reminders are only triggered if the main alarm is during working time.
if (!mExcludeHolidays)
{
// There are no holiday restrictions.
calcNextWorkingTime(mMainTrigger);
}
else if (mHolidays)
{
// Holidays are excluded.
DateTime nextTrigger = mMainTrigger;
KDateTime kdt;
for (int i = 0; i < 20; ++i)
{
calcNextWorkingTime(nextTrigger);
if (!mHolidays->isHoliday(mMainWorkTrigger.date()))
return; // found a non-holiday occurrence
kdt = mMainWorkTrigger.effectiveKDateTime();
kdt.setTime(QTime(23,59,59));
const KAEvent::OccurType type = nextOccurrence(kdt, nextTrigger, KAEvent::RETURN_REPETITION);
if (!nextTrigger.isValid())
break;
if (isWorkingTime(nextTrigger.kDateTime()))
{
const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm
mMainWorkTrigger = nextTrigger;
mAllWorkTrigger = (type & KAEvent::OCCURRENCE_REPEAT) ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder);
return; // found a non-holiday occurrence
}
}
mMainWorkTrigger = mAllWorkTrigger = DateTime();
}
}
else if (mExcludeHolidays && mHolidays)
{
// Holidays are excluded.
DateTime nextTrigger = mMainTrigger;
KDateTime kdt;
for (int i = 0; i < 20; ++i)
{
kdt = nextTrigger.effectiveKDateTime();
kdt.setTime(QTime(23,59,59));
const KAEvent::OccurType type = nextOccurrence(kdt, nextTrigger, KAEvent::RETURN_REPETITION);
if (!nextTrigger.isValid())
break;
if (!mHolidays->isHoliday(nextTrigger.date()))
{
const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm
mMainWorkTrigger = nextTrigger;
mAllWorkTrigger = (type & KAEvent::OCCURRENCE_REPEAT) ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder);
return; // found a non-holiday occurrence
}
}
mMainWorkTrigger = mAllWorkTrigger = DateTime();
}
}
}
/******************************************************************************
* Return the time of the next scheduled occurrence of the event during working
* hours, for an alarm which is restricted to working hours.
* On entry, 'nextTrigger' = the next recurrence or repetition (as returned by
* mainDateTime(true) ).
*/
void KAEventPrivate::calcNextWorkingTime(const DateTime& nextTrigger) const
{
kDebug() << "next=" << nextTrigger.kDateTime().dateTime();
mMainWorkTrigger = mAllWorkTrigger = DateTime();
for (int i = 0; ; ++i)
{
if (i >= 7)
return; // no working days are defined
if (mWorkDays.testBit(i))
break;
}
const KARecurrence::Type recurType = checkRecur();
KDateTime kdt = nextTrigger.effectiveKDateTime();
const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm
// Check if it always falls on the same day(s) of the week.
const RecurrenceRule* rrule = mRecurrence->defaultRRuleConst();
if (!rrule)
return; // no recurrence rule!
unsigned allDaysMask = 0x7F; // mask bits for all days of week
bool noWorkPos = false; // true if no recurrence day position is working day
const QList<RecurrenceRule::WDayPos> pos = rrule->byDays();
const int nDayPos = pos.count(); // number of day positions
if (nDayPos)
{
noWorkPos = true;
allDaysMask = 0;
for (int i = 0; i < nDayPos; ++i)
{
const int day = pos[i].day() - 1; // Monday = 0
if (mWorkDays.testBit(day))
noWorkPos = false; // found a working day occurrence
allDaysMask |= 1 << day;
}
if (noWorkPos && !mRepetition)
return; // never occurs on a working day
}
DateTime newdt;
if (mStartDateTime.isDateOnly())
{
// It's a date-only alarm.
// Sub-repetitions also have to be date-only.
const int repeatFreq = mRepetition.intervalDays();
const bool weeklyRepeat = mRepetition && !(repeatFreq % 7);
const Duration interval = mRecurrence->regularInterval();
if ((interval && !(interval.asDays() % 7))
|| nDayPos == 1)
{
// It recurs on the same day each week
if (!mRepetition || weeklyRepeat)
return; // any repetitions are also weekly
// It's a weekly recurrence with a non-weekly sub-repetition.
// Check one cycle of repetitions for the next one that lands
// on a working day.
KDateTime dt(nextTrigger.kDateTime().addDays(1));
dt.setTime(QTime(0,0,0));
previousOccurrence(dt, newdt, false);
if (!newdt.isValid())
return; // this should never happen
kdt = newdt.effectiveKDateTime();
const int day = kdt.date().dayOfWeek() - 1; // Monday = 0
for (int repeatNum = mNextRepeat + 1; ; ++repeatNum)
{
if (repeatNum > mRepetition.count())
repeatNum = 0;
if (repeatNum == mNextRepeat)
break;
if (!repeatNum)
{
nextOccurrence(newdt.kDateTime(), newdt, KAEvent::IGNORE_REPETITION);
if (mWorkDays.testBit(day))
{
mMainWorkTrigger = newdt;
mAllWorkTrigger = mMainWorkTrigger.addMins(-reminder);
return;
}
kdt = newdt.effectiveKDateTime();
}
else
{
const int inc = repeatFreq * repeatNum;
if (mWorkDays.testBit((day + inc) % 7))
{
kdt = kdt.addDays(inc);
kdt.setDateOnly(true);
mMainWorkTrigger = mAllWorkTrigger = kdt;
return;
}
}
}
return;
}
if (!mRepetition || weeklyRepeat)
{
// It's a date-only alarm with either no sub-repetition or a
// sub-repetition which always falls on the same day of the week
// as the recurrence (if any).
unsigned days = 0;
for ( ; ; )
{
kdt.setTime(QTime(23,59,59));
nextOccurrence(kdt, newdt, KAEvent::IGNORE_REPETITION);
if (!newdt.isValid())
return;
kdt = newdt.effectiveKDateTime();
const int day = kdt.date().dayOfWeek() - 1;
if (mWorkDays.testBit(day))
break; // found a working day occurrence
// Prevent indefinite looping (which should never happen anyway)
if ((days & allDaysMask) == allDaysMask)
return; // found a recurrence on every possible day of the week!?!
days |= 1 << day;
}
kdt.setDateOnly(true);
mMainWorkTrigger = kdt;
mAllWorkTrigger = kdt.addSecs(-60 * reminder);
return;
}
// It's a date-only alarm which recurs on different days of the week,
// as does the sub-repetition.
// Find the previous recurrence (as opposed to sub-repetition)
unsigned days = 1 << (kdt.date().dayOfWeek() - 1);
KDateTime dt(nextTrigger.kDateTime().addDays(1));
dt.setTime(QTime(0,0,0));
previousOccurrence(dt, newdt, false);
if (!newdt.isValid())
return; // this should never happen
kdt = newdt.effectiveKDateTime();
int day = kdt.date().dayOfWeek() - 1; // Monday = 0
for (int repeatNum = mNextRepeat; ; repeatNum = 0)
{
while (++repeatNum <= mRepetition.count())
{
const int inc = repeatFreq * repeatNum;
if (mWorkDays.testBit((day + inc) % 7))
{
kdt = kdt.addDays(inc);
kdt.setDateOnly(true);
mMainWorkTrigger = mAllWorkTrigger = kdt;
return;
}
if ((days & allDaysMask) == allDaysMask)
return; // found an occurrence on every possible day of the week!?!
days |= 1 << day;
}
nextOccurrence(kdt, newdt, KAEvent::IGNORE_REPETITION);
if (!newdt.isValid())
return;
kdt = newdt.effectiveKDateTime();
day = kdt.date().dayOfWeek() - 1;
if (mWorkDays.testBit(day))
{
kdt.setDateOnly(true);
mMainWorkTrigger = kdt;
mAllWorkTrigger = kdt.addSecs(-60 * reminder);
return;
}
if ((days & allDaysMask) == allDaysMask)
return; // found an occurrence on every possible day of the week!?!
days |= 1 << day;
}
return;
}
// It's a date-time alarm.
/* Check whether the recurrence or sub-repetition occurs at the same time
* every day. Note that because of seasonal time changes, a recurrence
* defined in terms of minutes will vary its time of day even if its value
* is a multiple of a day (24*60 minutes). Sub-repetitions are considered
* to repeat at the same time of day regardless of time changes if they
* are multiples of a day, which doesn't strictly conform to the iCalendar
* format because this only allows their interval to be recorded in seconds.
*/
const bool recurTimeVaries = (recurType == KARecurrence::MINUTELY);
const bool repeatTimeVaries = (mRepetition && !mRepetition.isDaily());
if (!recurTimeVaries && !repeatTimeVaries)
{
// The alarm always occurs at the same time of day.
// Check whether it can ever occur during working hours.
if (!mayOccurDailyDuringWork(kdt))
return; // never occurs during working hours
// Find the next working day it occurs on
bool repetition = false;
unsigned days = 0;
for ( ; ; )
{
KAEvent::OccurType type = nextOccurrence(kdt, newdt, KAEvent::RETURN_REPETITION);
if (!newdt.isValid())
return;
repetition = (type & KAEvent::OCCURRENCE_REPEAT);
kdt = newdt.effectiveKDateTime();
const int day = kdt.date().dayOfWeek() - 1;
if (mWorkDays.testBit(day))
break; // found a working day occurrence
// Prevent indefinite looping (which should never happen anyway)
if (!repetition)
{
if ((days & allDaysMask) == allDaysMask)
return; // found a recurrence on every possible day of the week!?!
days |= 1 << day;
}
}
mMainWorkTrigger = nextTrigger;
mMainWorkTrigger.setDate(kdt.date());
mAllWorkTrigger = repetition ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder);
return;
}
// The alarm occurs at different times of day.
// We may need to check for a full annual cycle of seasonal time changes, in
// case it only occurs during working hours after a time change.
KTimeZone tz = kdt.timeZone();
if (tz.isValid() && tz.type() == "KSystemTimeZone")
{
// It's a system time zone, so fetch full transition information
const KTimeZone ktz = KSystemTimeZones::readZone(tz.name());
if (ktz.isValid())
tz = ktz;
}
const QList<KTimeZone::Transition> tzTransitions = tz.transitions();
if (recurTimeVaries)
{
/* The alarm recurs at regular clock intervals, at different times of day.
* Note that for this type of recurrence, it's necessary to avoid the
* performance overhead of Recurrence class calls since these can in the
* worst case cause the program to hang for a significant length of time.
* In this case, we can calculate the next recurrence by simply adding the
* recurrence interval, since KAlarm offers no facility to regularly miss
* recurrences. (But exception dates/times need to be taken into account.)
*/
KDateTime kdtRecur;
int repeatFreq = 0;
int repeatNum = 0;
if (mRepetition)
{
// It's a repetition inside a recurrence, each of which occurs
// at different times of day (bearing in mind that the repetition
// may occur at daily intervals after each recurrence).
// Find the previous recurrence (as opposed to sub-repetition)
repeatFreq = mRepetition.intervalSeconds();
previousOccurrence(kdt.addSecs(1), newdt, false);
if (!newdt.isValid())
return; // this should never happen
kdtRecur = newdt.effectiveKDateTime();
repeatNum = kdtRecur.secsTo(kdt) / repeatFreq;
kdt = kdtRecur.addSecs(repeatNum * repeatFreq);
}
else
{
// There is no sub-repetition.
// (N.B. Sub-repetitions can't exist without a recurrence.)
// Check until the original time wraps round, but ensure that
// if there are seasonal time changes, that all other subsequent
// time offsets within the next year are checked.
// This does not guarantee to find the next working time,
// particularly if there are exceptions, but it's a
// reasonable try.
kdtRecur = kdt;
}
QTime firstTime = kdtRecur.time();
int firstOffset = kdtRecur.utcOffset();
int currentOffset = firstOffset;
int dayRecur = kdtRecur.date().dayOfWeek() - 1; // Monday = 0
int firstDay = dayRecur;
QDate finalDate;
const bool subdaily = (repeatFreq < 24*3600);
// int period = mRecurrence->frequency() % (24*60); // it is by definition a MINUTELY recurrence
// int limit = (24*60 + period - 1) / period; // number of times until recurrence wraps round
int transitionIndex = -1;
for (int n = 0; n < 7*24*60; ++n)
{
if (mRepetition)
{
// Check the sub-repetitions for this recurrence
for ( ; ; )
{
// Find the repeat count to the next start of the working day
const int inc = subdaily ? nextWorkRepetition(kdt) : 1;
repeatNum += inc;
if (repeatNum > mRepetition.count())
break;
kdt = kdt.addSecs(inc * repeatFreq);
const QTime t = kdt.time();
if (t >= mWorkDayStart && t < mWorkDayEnd)
{
if (mWorkDays.testBit(kdt.date().dayOfWeek() - 1))
{
mMainWorkTrigger = mAllWorkTrigger = kdt;
return;
}
}
}
repeatNum = 0;
}
nextOccurrence(kdtRecur, newdt, KAEvent::IGNORE_REPETITION);
if (!newdt.isValid())
return;
kdtRecur = newdt.effectiveKDateTime();
dayRecur = kdtRecur.date().dayOfWeek() - 1; // Monday = 0
const QTime t = kdtRecur.time();
if (t >= mWorkDayStart && t < mWorkDayEnd)
{
if (mWorkDays.testBit(dayRecur))
{
mMainWorkTrigger = kdtRecur;
mAllWorkTrigger = kdtRecur.addSecs(-60 * reminder);
return;
}
}
if (kdtRecur.utcOffset() != currentOffset)
currentOffset = kdtRecur.utcOffset();
if (t == firstTime && dayRecur == firstDay && currentOffset == firstOffset)
{
// We've wrapped round to the starting day and time.
// If there are seasonal time changes, check for up
// to the next year in other time offsets in case the
// alarm occurs inside working hours then.
if (!finalDate.isValid())
finalDate = kdtRecur.date();
const int i = tz.transitionIndex(kdtRecur.toUtc().dateTime());
if (i < 0)
return;
if (i > transitionIndex)
transitionIndex = i;
if (++transitionIndex >= static_cast<int>(tzTransitions.count()))
return;
previousOccurrence(KDateTime(tzTransitions[transitionIndex].time(), KDateTime::UTC), newdt, KAEvent::IGNORE_REPETITION);
kdtRecur = newdt.effectiveKDateTime();
if (finalDate.daysTo(kdtRecur.date()) > 365)
return;
firstTime = kdtRecur.time();
firstOffset = kdtRecur.utcOffset();
currentOffset = firstOffset;
firstDay = kdtRecur.date().dayOfWeek() - 1;
}
kdt = kdtRecur;
}
//kDebug()<<"-----exit loop: count="<<limit<<endl;
return; // too many iterations
}
if (repeatTimeVaries)
{
/* There's a sub-repetition which occurs at different times of
* day, inside a recurrence which occurs at the same time of day.
* We potentially need to check recurrences starting on each day.
* Then, it is still possible that a working time sub-repetition
* could occur immediately after a seasonal time change.
*/
// Find the previous recurrence (as opposed to sub-repetition)
const int repeatFreq = mRepetition.intervalSeconds();
previousOccurrence(kdt.addSecs(1), newdt, false);
if (!newdt.isValid())
return; // this should never happen
KDateTime kdtRecur = newdt.effectiveKDateTime();
const bool recurDuringWork = (kdtRecur.time() >= mWorkDayStart && kdtRecur.time() < mWorkDayEnd);
// Use the previous recurrence as a base for checking whether
// our tests have wrapped round to the same time/day of week.
const bool subdaily = (repeatFreq < 24*3600);
unsigned days = 0;
bool checkTimeChangeOnly = false;
int transitionIndex = -1;
for (int limit = 10; --limit >= 0; )
{
// Check the next seasonal time change (for an arbitrary 10 times,
// even though that might not guarantee the correct result)
QDate dateRecur = kdtRecur.date();
int dayRecur = dateRecur.dayOfWeek() - 1; // Monday = 0
int repeatNum = kdtRecur.secsTo(kdt) / repeatFreq;
kdt = kdtRecur.addSecs(repeatNum * repeatFreq);
// Find the next recurrence, which sets the limit on possible sub-repetitions.
// Note that for a monthly recurrence, for example, a sub-repetition could
// be defined which is longer than the recurrence interval in short months.
// In these cases, the sub-repetition is truncated by the following
// recurrence.
nextOccurrence(kdtRecur, newdt, KAEvent::IGNORE_REPETITION);
KDateTime kdtNextRecur = newdt.effectiveKDateTime();
int repeatsToCheck = mRepetition.count();
int repeatsDuringWork = 0; // 0=unknown, 1=does, -1=never
for ( ; ; )
{
// Check the sub-repetitions for this recurrence
if (repeatsDuringWork >= 0)
{
for ( ; ; )
{
// Find the repeat count to the next start of the working day
int inc = subdaily ? nextWorkRepetition(kdt) : 1;
repeatNum += inc;
const bool pastEnd = (repeatNum > mRepetition.count());
if (pastEnd)
inc -= repeatNum - mRepetition.count();
repeatsToCheck -= inc;
kdt = kdt.addSecs(inc * repeatFreq);
if (kdtNextRecur.isValid() && kdt >= kdtNextRecur)
{
// This sub-repetition is past the next recurrence,
// so start the check again from the next recurrence.
repeatsToCheck = mRepetition.count();
break;
}
if (pastEnd)
break;
const QTime t = kdt.time();
if (t >= mWorkDayStart && t < mWorkDayEnd)
{
if (mWorkDays.testBit(kdt.date().dayOfWeek() - 1))
{
mMainWorkTrigger = mAllWorkTrigger = kdt;
return;
}
repeatsDuringWork = 1;
}
else if (!repeatsDuringWork && repeatsToCheck <= 0)
{
// Sub-repetitions never occur during working hours
repeatsDuringWork = -1;
break;
}
}
}
repeatNum = 0;
if (repeatsDuringWork < 0 && !recurDuringWork)
break; // it never occurs during working hours
// Check the next recurrence
if (!kdtNextRecur.isValid())
return;
if (checkTimeChangeOnly || (days & allDaysMask) == allDaysMask)
break; // found a recurrence on every possible day of the week!?!
kdtRecur = kdtNextRecur;
nextOccurrence(kdtRecur, newdt, KAEvent::IGNORE_REPETITION);
kdtNextRecur = newdt.effectiveKDateTime();
dateRecur = kdtRecur.date();
dayRecur = dateRecur.dayOfWeek() - 1;
if (recurDuringWork && mWorkDays.testBit(dayRecur))
{
mMainWorkTrigger = kdtRecur;
mAllWorkTrigger = kdtRecur.addSecs(-60 * reminder);
return;
}
days |= 1 << dayRecur;
kdt = kdtRecur;
}
// Find the next recurrence before a seasonal time change,
// and ensure the time change is after the last one processed.
checkTimeChangeOnly = true;
const int i = tz.transitionIndex(kdtRecur.toUtc().dateTime());
if (i < 0)
return;
if (i > transitionIndex)
transitionIndex = i;
if (++transitionIndex >= static_cast<int>(tzTransitions.count()))
return;
kdt = KDateTime(tzTransitions[transitionIndex].time(), KDateTime::UTC);
previousOccurrence(kdt, newdt, KAEvent::IGNORE_REPETITION);
kdtRecur = newdt.effectiveKDateTime();
}
return; // not found - give up
}
}
/******************************************************************************
* Find the repeat count to the next start of a working day.
* This allows for possible daylight saving time changes during the repetition.
* Use for repetitions which occur at different times of day.
*/
int KAEventPrivate::nextWorkRepetition(const KDateTime& pre) const
{
KDateTime nextWork(pre);
if (pre.time() < mWorkDayStart)
nextWork.setTime(mWorkDayStart);
else
{
const int preDay = pre.date().dayOfWeek() - 1; // Monday = 0
for (int n = 1; ; ++n)
{
if (n >= 7)
return mRepetition.count() + 1; // should never happen
if (mWorkDays.testBit((preDay + n) % 7))
{
nextWork = nextWork.addDays(n);
nextWork.setTime(mWorkDayStart);
break;
}
}
}
return (pre.secsTo(nextWork) - 1) / mRepetition.intervalSeconds() + 1;
}
/******************************************************************************
* Check whether an alarm which recurs at the same time of day can possibly
* occur during working hours.
* This does not determine whether it actually does, but rather whether it could
* potentially given enough repetitions.
* Reply = false if it can never occur during working hours, true if it might.
*/
bool KAEventPrivate::mayOccurDailyDuringWork(const KDateTime& kdt) const
{
if (!kdt.isDateOnly()
&& (kdt.time() < mWorkDayStart || kdt.time() >= mWorkDayEnd))
return false; // its time is outside working hours
// Check if it always occurs on the same day of the week
const Duration interval = mRecurrence->regularInterval();
if (interval && interval.isDaily() && !(interval.asDays() % 7))
{
// It recurs weekly
if (!mRepetition || (mRepetition.isDaily() && !(mRepetition.intervalDays() % 7)))
return false; // any repetitions are also weekly
// Repetitions are daily. Check if any occur on working days
// by checking the first recurrence and up to 6 repetitions.
int day = mRecurrence->startDateTime().date().dayOfWeek() - 1; // Monday = 0
const int repeatDays = mRepetition.intervalDays();
const int maxRepeat = (mRepetition.count() < 6) ? mRepetition.count() : 6;
for (int i = 0; !mWorkDays.testBit(day); ++i, day = (day + repeatDays) % 7)
{
if (i >= maxRepeat)
return false; // no working day occurrences
}
}
return true;
}
/******************************************************************************
* Set the specified alarm to be an audio alarm with the given file name.
*/
#ifndef KALARMCAL_USE_KRESOURCES
void KAEventPrivate::setAudioAlarm(const Alarm::Ptr& alarm) const
#else
void KAEventPrivate::setAudioAlarm(Alarm* alarm) const
#endif
{
alarm->setAudioAlarm(mAudioFile); // empty for a beep or for speaking
if (mSoundVolume >= 0)
alarm->setCustomProperty(KACalendar::APPNAME, VOLUME_PROPERTY,
QString::fromLatin1("%1;%2;%3;%4").arg(QString::number(mSoundVolume, 'f', 2))
.arg(QString::number(mFadeVolume, 'f', 2))
.arg(mFadeSeconds));
}
/******************************************************************************
* Get the date/time of the next recurrence of the event, after the specified
* date/time.
* 'result' = date/time of next occurrence, or invalid date/time if none.
*/
KAEvent::OccurType KAEventPrivate::nextRecurrence(const KDateTime& preDateTime, DateTime& result) const
{
const KDateTime recurStart = mRecurrence->startDateTime();
KDateTime pre = preDateTime.toTimeSpec(mStartDateTime.timeSpec());
if (mStartDateTime.isDateOnly() && !pre.isDateOnly() && pre.time() < DateTime::startOfDay())
{
pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come
pre.setTime(DateTime::startOfDay());
}
const KDateTime dt = mRecurrence->getNextDateTime(pre);
result = dt;
result.setDateOnly(mStartDateTime.isDateOnly());
if (!dt.isValid())
return KAEvent::NO_OCCURRENCE;
if (dt == recurStart)
return KAEvent::FIRST_OR_ONLY_OCCURRENCE;
if (mRecurrence->duration() >= 0 && dt == mRecurrence->endDateTime())
return KAEvent::LAST_RECURRENCE;
return result.isDateOnly() ? KAEvent::RECURRENCE_DATE : KAEvent::RECURRENCE_DATE_TIME;
}
/******************************************************************************
* Validate the event's recurrence data, correcting any inconsistencies (which
* should never occur!).
* Reply = recurrence period type.
*/
KARecurrence::Type KAEventPrivate::checkRecur() const
{
if (mRecurrence)
{
KARecurrence::Type type = mRecurrence->type();
switch (type)
{
case KARecurrence::MINUTELY: // hourly
case KARecurrence::DAILY: // daily
case KARecurrence::WEEKLY: // weekly on multiple days of week
case KARecurrence::MONTHLY_DAY: // monthly on multiple dates in month
case KARecurrence::MONTHLY_POS: // monthly on multiple nth day of week
case KARecurrence::ANNUAL_DATE: // annually on multiple months (day of month = start date)
case KARecurrence::ANNUAL_POS: // annually on multiple nth day of week in multiple months
return type;
default:
if (mRecurrence)
const_cast<KAEventPrivate*>(this)->clearRecur(); // this shouldn't ever be necessary!!
break;
}
}
if (mRepetition) // can't have a repetition without a recurrence
const_cast<KAEventPrivate*>(this)->clearRecur(); // this shouldn't ever be necessary!!
return KARecurrence::NO_RECUR;
}
/******************************************************************************
* If the calendar was written by a previous version of KAlarm, do any
* necessary format conversions on the events to ensure that when the calendar
* is saved, no information is lost or corrupted.
* Reply = true if any conversions were done.
*/
#ifndef KALARMCAL_USE_KRESOURCES
bool KAEvent::convertKCalEvents(const Calendar::Ptr& calendar, int calendarVersion)
#else
bool KAEvent::convertKCalEvents(CalendarLocal& calendar, int calendarVersion)
#endif
{
// KAlarm pre-0.9 codes held in the alarm's DESCRIPTION property
static const QChar SEPARATOR = QLatin1Char(';');
static const QChar LATE_CANCEL_CODE = QLatin1Char('C');
static const QChar AT_LOGIN_CODE = QLatin1Char('L'); // subsidiary alarm at every login
static const QChar DEFERRAL_CODE = QLatin1Char('D'); // extra deferred alarm
static const QString TEXT_PREFIX = QLatin1String("TEXT:");
static const QString FILE_PREFIX = QLatin1String("FILE:");
static const QString COMMAND_PREFIX = QLatin1String("CMD:");
// KAlarm pre-0.9.2 codes held in the event's CATEGORY property
static const QString BEEP_CATEGORY = QLatin1String("BEEP");
// KAlarm pre-1.1.1 LATECANCEL category with no parameter
static const QString LATE_CANCEL_CAT = QLatin1String("LATECANCEL");
// KAlarm pre-1.3.0 TMPLDEFTIME category with no parameter
static const QString TEMPL_DEF_TIME_CAT = QLatin1String("TMPLDEFTIME");
// KAlarm pre-1.3.1 XTERM category
static const QString EXEC_IN_XTERM_CAT = QLatin1String("XTERM");
// KAlarm pre-1.9.0 categories
static const QString DATE_ONLY_CATEGORY = QLatin1String("DATE");
static const QString EMAIL_BCC_CATEGORY = QLatin1String("BCC");
static const QString CONFIRM_ACK_CATEGORY = QLatin1String("ACKCONF");
static const QString KORGANIZER_CATEGORY = QLatin1String("KORG");
static const QString DEFER_CATEGORY = QLatin1String("DEFER;");
static const QString ARCHIVE_CATEGORY = QLatin1String("SAVE");
static const QString ARCHIVE_CATEGORIES = QLatin1String("SAVE:");
static const QString LATE_CANCEL_CATEGORY = QLatin1String("LATECANCEL;");
static const QString AUTO_CLOSE_CATEGORY = QLatin1String("LATECLOSE;");
static const QString TEMPL_AFTER_TIME_CATEGORY = QLatin1String("TMPLAFTTIME;");
static const QString KMAIL_SERNUM_CATEGORY = QLatin1String("KMAIL:");
static const QString LOG_CATEGORY = QLatin1String("LOG:");
// KAlarm pre-1.5.0/1.9.9 properties
static const QByteArray KMAIL_ID_PROPERTY("KMAILID"); // X-KDE-KALARM-KMAILID property
// KAlarm pre-2.6.0 properties
static const QByteArray ARCHIVE_PROPERTY("ARCHIVE"); // X-KDE-KALARM-ARCHIVE property
static const QString ARCHIVE_REMINDER_ONCE_TYPE = QLatin1String("ONCE");
static const QString REMINDER_ONCE_TYPE = QLatin1String("REMINDER_ONCE");
static const QByteArray EMAIL_ID_PROPERTY("EMAILID"); // X-KDE-KALARM-EMAILID property
static const QByteArray SPEAK_PROPERTY("SPEAK"); // X-KDE-KALARM-SPEAK property
static const QByteArray CANCEL_ON_ERROR_PROPERTY("ERRCANCEL");// X-KDE-KALARM-ERRCANCEL property
static const QByteArray DONT_SHOW_ERROR_PROPERTY("ERRNOSHOW");// X-KDE-KALARM-ERRNOSHOW property
bool adjustSummerTime = false;
if (calendarVersion == -Version(0,5,7))
{
// The calendar file was written by the KDE 3.0.0 version of KAlarm 0.5.7.
// Summer time was ignored when converting to UTC.
calendarVersion = -calendarVersion;
adjustSummerTime = true;
}
if (calendarVersion >= currentCalendarVersion())
return false;
kDebug() << "Adjusting version" << calendarVersion;
const bool pre_0_7 = (calendarVersion < Version(0,7,0));
const bool pre_0_9 = (calendarVersion < Version(0,9,0));
const bool pre_0_9_2 = (calendarVersion < Version(0,9,2));
const bool pre_1_1_1 = (calendarVersion < Version(1,1,1));
const bool pre_1_2_1 = (calendarVersion < Version(1,2,1));
const bool pre_1_3_0 = (calendarVersion < Version(1,3,0));
const bool pre_1_3_1 = (calendarVersion < Version(1,3,1));
const bool pre_1_4_14 = (calendarVersion < Version(1,4,14));
const bool pre_1_5_0 = (calendarVersion < Version(1,5,0));
const bool pre_1_9_0 = (calendarVersion < Version(1,9,0));
const bool pre_1_9_2 = (calendarVersion < Version(1,9,2));
const bool pre_1_9_7 = (calendarVersion < Version(1,9,7));
const bool pre_1_9_9 = (calendarVersion < Version(1,9,9));
const bool pre_1_9_10 = (calendarVersion < Version(1,9,10));
const bool pre_2_2_9 = (calendarVersion < Version(2,2,9));
const bool pre_2_3_0 = (calendarVersion < Version(2,3,0));
const bool pre_2_3_2 = (calendarVersion < Version(2,3,2));
const bool pre_2_7_0 = (calendarVersion < Version(2,7,0));
Q_ASSERT(currentCalendarVersion() == Version(2,7,0));
KTimeZone localZone;
if (pre_1_9_2)
localZone = KSystemTimeZones::local();
bool converted = false;
#ifndef KALARMCAL_USE_KRESOURCES
const Event::List events = calendar->rawEvents();
#else
const Event::List events = calendar.rawEvents();
#endif
for (int ei = 0, eend = events.count(); ei < eend; ++ei)
{
#ifndef KALARMCAL_USE_KRESOURCES
Event::Ptr event = events[ei];
#else
Event* event = events[ei];
#endif
const Alarm::List alarms = event->alarms();
if (alarms.isEmpty())
continue; // KAlarm isn't interested in events without alarms
event->startUpdates(); // prevent multiple update notifications
const bool readOnly = event->isReadOnly();
if (readOnly)
event->setReadOnly(false);
QStringList cats = event->categories();
bool addLateCancel = false;
QStringList flags;
if (pre_0_7 && event->allDay())
{
// It's a KAlarm pre-0.7 calendar file.
// Ensure that when the calendar is saved, the alarm time isn't lost.
event->setAllDay(false);
}
if (pre_0_9)
{
/*
* It's a KAlarm pre-0.9 calendar file.
* All alarms were of type DISPLAY. Instead of the X-KDE-KALARM-TYPE
* alarm property, characteristics were stored as a prefix to the
* alarm DESCRIPTION property, as follows:
* SEQNO;[FLAGS];TYPE:TEXT
* where
* SEQNO = sequence number of alarm within the event
* FLAGS = C for late-cancel, L for repeat-at-login, D for deferral
* TYPE = TEXT or FILE or CMD
* TEXT = message text, file name/URL or command
*/
for (int ai = 0, aend = alarms.count(); ai < aend; ++ai)
{
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr alarm = alarms[ai];
#else
Alarm* alarm = alarms[ai];
#endif
bool atLogin = false;
bool deferral = false;
bool lateCancel = false;
KAAlarm::Action action = KAAlarm::MESSAGE;
const QString txt = alarm->text();
const int length = txt.length();
int i = 0;
if (txt[0].isDigit())
{
while (++i < length && txt[i].isDigit()) ;
if (i < length && txt[i++] == SEPARATOR)
{
while (i < length)
{
const QChar ch = txt[i++];
if (ch == SEPARATOR)
break;
if (ch == LATE_CANCEL_CODE)
lateCancel = true;
else if (ch == AT_LOGIN_CODE)
atLogin = true;
else if (ch == DEFERRAL_CODE)
deferral = true;
}
}
else
i = 0; // invalid prefix
}
if (txt.indexOf(TEXT_PREFIX, i) == i)
i += TEXT_PREFIX.length();
else if (txt.indexOf(FILE_PREFIX, i) == i)
{
action = KAAlarm::FILE;
i += FILE_PREFIX.length();
}
else if (txt.indexOf(COMMAND_PREFIX, i) == i)
{
action = KAAlarm::COMMAND;
i += COMMAND_PREFIX.length();
}
else
i = 0;
const QString altxt = txt.mid(i);
QStringList types;
switch (action)
{
case KAAlarm::FILE:
types += KAEventPrivate::FILE_TYPE;
// fall through to MESSAGE
case KAAlarm::MESSAGE:
alarm->setDisplayAlarm(altxt);
break;
case KAAlarm::COMMAND:
setProcedureAlarm(alarm, altxt);
break;
case KAAlarm::EMAIL: // email alarms were introduced in KAlarm 0.9
case KAAlarm::AUDIO: // audio alarms (with no display) were introduced in KAlarm 2.3.2
break;
}
if (atLogin)
{
types += KAEventPrivate::AT_LOGIN_TYPE;
lateCancel = false;
}
else if (deferral)
types += KAEventPrivate::TIME_DEFERRAL_TYPE;
if (lateCancel)
addLateCancel = true;
if (types.count() > 0)
alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY, types.join(QLatin1String(",")));
if (pre_0_7 && alarm->repeatCount() > 0 && alarm->snoozeTime().value() > 0)
{
// It's a KAlarm pre-0.7 calendar file.
// Minutely recurrences were stored differently.
Recurrence* recur = event->recurrence();
if (recur && recur->recurs())
{
recur->setMinutely(alarm->snoozeTime().asSeconds() / 60);
recur->setDuration(alarm->repeatCount() + 1);
alarm->setRepeatCount(0);
alarm->setSnoozeTime(0);
}
}
if (adjustSummerTime)
{
// The calendar file was written by the KDE 3.0.0 version of KAlarm 0.5.7.
// Summer time was ignored when converting to UTC.
KDateTime dt = alarm->time();
const time_t t = dt.toTime_t();
const struct tm* dtm = localtime(&t);
if (dtm->tm_isdst)
{
dt = dt.addSecs(-3600);
alarm->setTime(dt);
}
}
}
}
if (pre_0_9_2)
{
/*
* It's a KAlarm pre-0.9.2 calendar file.
* For the archive calendar, set the CREATED time to the DTEND value.
* Convert date-only DTSTART to date/time, and add category "DATE".
* Set the DTEND time to the DTSTART time.
* Convert all alarm times to DTSTART offsets.
* For display alarms, convert the first unlabelled category to an
* X-KDE-KALARM-FONTCOLOUR property.
* Convert BEEP category into an audio alarm with no audio file.
*/
if (CalEvent::status(event) == CalEvent::ARCHIVED)
event->setCreated(event->dtEnd());
KDateTime start = event->dtStart();
if (event->allDay())
{
event->setAllDay(false);
start.setTime(QTime(0, 0));
flags += KAEventPrivate::DATE_ONLY_FLAG;
}
event->setDtEnd(KDateTime());
for (int ai = 0, aend = alarms.count(); ai < aend; ++ai)
{
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr alarm = alarms[ai];
#else
Alarm* alarm = alarms[ai];
#endif
alarm->setStartOffset(start.secsTo(alarm->time()));
}
if (!cats.isEmpty())
{
for (int ai = 0, aend = alarms.count(); ai < aend; ++ai)
{
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr alarm = alarms[ai];
#else
Alarm* alarm = alarms[ai];
#endif
if (alarm->type() == Alarm::Display)
alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FONT_COLOUR_PROPERTY,
QString::fromLatin1("%1;;").arg(cats.at(0)));
}
cats.removeAt(0);
}
for (int i = 0, end = cats.count(); i < end; ++i)
{
if (cats.at(i) == BEEP_CATEGORY)
{
cats.removeAt(i);
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr alarm = event->newAlarm();
#else
Alarm* alarm = event->newAlarm();
#endif
alarm->setEnabled(true);
alarm->setAudioAlarm();
KDateTime dt = event->dtStart(); // default
// Parse and order the alarms to know which one's date/time to use
KAEventPrivate::AlarmMap alarmMap;
KAEventPrivate::readAlarms(event, &alarmMap);
KAEventPrivate::AlarmMap::ConstIterator it = alarmMap.constBegin();
if (it != alarmMap.constEnd())
{
dt = it.value().alarm->time();
break;
}
alarm->setStartOffset(start.secsTo(dt));
break;
}
}
}
if (pre_1_1_1)
{
/*
* It's a KAlarm pre-1.1.1 calendar file.
* Convert simple LATECANCEL category to LATECANCEL:n where n = minutes late.
*/
int i;
while ((i = cats.indexOf(LATE_CANCEL_CAT)) >= 0)
{
cats.removeAt(i);
addLateCancel = true;
}
}
if (pre_1_2_1)
{
/*
* It's a KAlarm pre-1.2.1 calendar file.
* Convert email display alarms from translated to untranslated header prefixes.
*/
for (int ai = 0, aend = alarms.count(); ai < aend; ++ai)
{
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr alarm = alarms[ai];
#else
Alarm* alarm = alarms[ai];
#endif
if (alarm->type() == Alarm::Display)
{
const QString oldtext = alarm->text();
const QString newtext = AlarmText::toCalendarText(oldtext);
if (oldtext != newtext)
alarm->setDisplayAlarm(newtext);
}
}
}
if (pre_1_3_0)
{
/*
* It's a KAlarm pre-1.3.0 calendar file.
* Convert simple TMPLDEFTIME category to TMPLAFTTIME:n where n = minutes after.
*/
int i;
while ((i = cats.indexOf(TEMPL_DEF_TIME_CAT)) >= 0)
{
cats.removeAt(i);
(flags += KAEventPrivate::TEMPL_AFTER_TIME_FLAG) += QLatin1String("0");
}
}
if (pre_1_3_1)
{
/*
* It's a KAlarm pre-1.3.1 calendar file.
* Convert simple XTERM category to LOG:xterm:
*/
int i;
while ((i = cats.indexOf(EXEC_IN_XTERM_CAT)) >= 0)
{
cats.removeAt(i);
event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::LOG_PROPERTY, KAEventPrivate::xtermURL);
}
}
if (pre_1_9_0)
{
/*
* It's a KAlarm pre-1.9 calendar file.
* Add the X-KDE-KALARM-STATUS custom property.
* Convert KAlarm categories to custom fields.
*/
CalEvent::setStatus(event, CalEvent::status(event));
for (int i = 0; i < cats.count(); )
{
const QString cat = cats.at(i);
if (cat == DATE_ONLY_CATEGORY)
flags += KAEventPrivate::DATE_ONLY_FLAG;
else if (cat == CONFIRM_ACK_CATEGORY)
flags += KAEventPrivate::CONFIRM_ACK_FLAG;
else if (cat == EMAIL_BCC_CATEGORY)
flags += KAEventPrivate::EMAIL_BCC_FLAG;
else if (cat == KORGANIZER_CATEGORY)
flags += KAEventPrivate::KORGANIZER_FLAG;
else if (cat.startsWith(DEFER_CATEGORY))
(flags += KAEventPrivate::DEFER_FLAG) += cat.mid(DEFER_CATEGORY.length());
else if (cat.startsWith(TEMPL_AFTER_TIME_CATEGORY))
(flags += KAEventPrivate::TEMPL_AFTER_TIME_FLAG) += cat.mid(TEMPL_AFTER_TIME_CATEGORY.length());
else if (cat.startsWith(LATE_CANCEL_CATEGORY))
(flags += KAEventPrivate::LATE_CANCEL_FLAG) += cat.mid(LATE_CANCEL_CATEGORY.length());
else if (cat.startsWith(AUTO_CLOSE_CATEGORY))
(flags += KAEventPrivate::AUTO_CLOSE_FLAG) += cat.mid(AUTO_CLOSE_CATEGORY.length());
else if (cat.startsWith(KMAIL_SERNUM_CATEGORY))
(flags += KAEventPrivate::KMAIL_SERNUM_FLAG) += cat.mid(KMAIL_SERNUM_CATEGORY.length());
else if (cat == ARCHIVE_CATEGORY)
event->setCustomProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY, QLatin1String("0"));
else if (cat.startsWith(ARCHIVE_CATEGORIES))
event->setCustomProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY, cat.mid(ARCHIVE_CATEGORIES.length()));
else if (cat.startsWith(LOG_CATEGORY))
event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::LOG_PROPERTY, cat.mid(LOG_CATEGORY.length()));
else
{
++i; // Not a KAlarm category, so leave it
continue;
}
cats.removeAt(i);
}
}
if (pre_1_9_2)
{
/*
* It's a KAlarm pre-1.9.2 calendar file.
* Convert from clock time to the local system time zone.
*/
event->shiftTimes(KDateTime::ClockTime, localZone);
converted = true;
}
if (addLateCancel)
(flags += KAEventPrivate::LATE_CANCEL_FLAG) += QLatin1String("1");
if (!flags.isEmpty())
event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY, flags.join(KAEventPrivate::SC));
event->setCategories(cats);
if ((pre_1_4_14 || (pre_1_9_7 && !pre_1_9_0))
&& event->recurrence() && event->recurrence()->recurs())
{
/*
* It's a KAlarm pre-1.4.14 or KAlarm 1.9 series pre-1.9.7 calendar file.
* For recurring events, convert the main alarm offset to an absolute
* time in the X-KDE-KALARM-NEXTRECUR property, and set main alarm
* offsets to zero, and convert deferral alarm offsets to be relative to
* the next recurrence.
*/
const QStringList flags = event->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY).split(KAEventPrivate::SC, QString::SkipEmptyParts);
const bool dateOnly = flags.contains(KAEventPrivate::DATE_ONLY_FLAG);
KDateTime startDateTime = event->dtStart();
if (dateOnly)
startDateTime.setDateOnly(true);
// Convert the main alarm and get the next main trigger time from it
KDateTime nextMainDateTime;
bool mainExpired = true;
for (int i = 0, alend = alarms.count(); i < alend; ++i)
{
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr alarm = alarms[i];
#else
Alarm* alarm = alarms[i];
#endif
if (!alarm->hasStartOffset())
continue;
// Find whether the alarm triggers at the same time as the main
// alarm, in which case its offset needs to be set to 0. The
// following trigger with the main alarm:
// - Additional audio alarm
// - PRE_ACTION_TYPE
// - POST_ACTION_TYPE
// - DISPLAYING_TYPE
bool mainAlarm = true;
QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY);
const QStringList types = property.split(QLatin1Char(','), QString::SkipEmptyParts);
for (int t = 0; t < types.count(); ++t)
{
QString type = types[t];
if (type == KAEventPrivate::AT_LOGIN_TYPE
|| type == KAEventPrivate::TIME_DEFERRAL_TYPE
|| type == KAEventPrivate::DATE_DEFERRAL_TYPE
|| type == KAEventPrivate::REMINDER_TYPE
|| type == REMINDER_ONCE_TYPE)
{
mainAlarm = false;
break;
}
}
if (mainAlarm)
{
if (mainExpired)
{
// All main alarms are supposed to be at the same time, so
// don't readjust the event's time for subsequent main alarms.
mainExpired = false;
nextMainDateTime = alarm->time();
nextMainDateTime.setDateOnly(dateOnly);
nextMainDateTime = nextMainDateTime.toTimeSpec(startDateTime);
if (nextMainDateTime != startDateTime)
{
QDateTime dt = nextMainDateTime.dateTime();
event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::NEXT_RECUR_PROPERTY,
dt.toString(dateOnly ? QLatin1String("yyyyMMdd") : QLatin1String("yyyyMMddThhmmss")));
}
}
alarm->setStartOffset(0);
converted = true;
}
}
int adjustment;
if (mainExpired)
{
// It's an expired recurrence.
// Set the alarm offset relative to the first actual occurrence
// (taking account of possible exceptions).
KDateTime dt = event->recurrence()->getNextDateTime(startDateTime.addDays(-1));
dt.setDateOnly(dateOnly);
adjustment = startDateTime.secsTo(dt);
}
else
adjustment = startDateTime.secsTo(nextMainDateTime);
if (adjustment)
{
// Convert deferred alarms
for (int i = 0, alend = alarms.count(); i < alend; ++i)
{
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr alarm = alarms[i];
#else
Alarm* alarm = alarms[i];
#endif
if (!alarm->hasStartOffset())
continue;
const QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY);
const QStringList types = property.split(QLatin1Char(','), QString::SkipEmptyParts);
for (int t = 0; t < types.count(); ++t)
{
const QString type = types[t];
if (type == KAEventPrivate::TIME_DEFERRAL_TYPE
|| type == KAEventPrivate::DATE_DEFERRAL_TYPE)
{
alarm->setStartOffset(alarm->startOffset().asSeconds() - adjustment);
converted = true;
break;
}
}
}
}
}
if (pre_1_5_0 || (pre_1_9_9 && !pre_1_9_0))
{
/*
* It's a KAlarm pre-1.5.0 or KAlarm 1.9 series pre-1.9.9 calendar file.
* Convert email identity names to uoids.
*/
for (int i = 0, alend = alarms.count(); i < alend; ++i)
{
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr alarm = alarms[i];
#else
Alarm* alarm = alarms[i];
#endif
const QString name = alarm->customProperty(KACalendar::APPNAME, KMAIL_ID_PROPERTY);
if (name.isEmpty())
continue;
const uint id = Identities::identityUoid(name);
if (id)
alarm->setCustomProperty(KACalendar::APPNAME, EMAIL_ID_PROPERTY, QString::number(id));
alarm->removeCustomProperty(KACalendar::APPNAME, KMAIL_ID_PROPERTY);
converted = true;
}
}
if (pre_1_9_10)
{
/*
* It's a KAlarm pre-1.9.10 calendar file.
* Convert simple repetitions without a recurrence, to a recurrence.
*/
if (KAEventPrivate::convertRepetition(event))
converted = true;
}
if (pre_2_2_9 || (pre_2_3_2 && !pre_2_3_0))
{
/*
* It's a KAlarm pre-2.2.9 or KAlarm 2.3 series pre-2.3.2 calendar file.
* Set the time in the calendar for all date-only alarms to 00:00.
*/
if (KAEventPrivate::convertStartOfDay(event))
converted = true;
}
if (pre_2_7_0)
{
/*
* It's a KAlarm pre-2.7.0 calendar file.
* Archive and at-login flags were stored in event's ARCHIVE property when the main alarm had expired.
* Reminder parameters were stored in event's ARCHIVE property when no reminder was pending.
* Negative reminder periods (i.e. alarm offset > 0) were invalid, so convert to 0.
* Now store reminder information in FLAGS property, whether reminder is pending or not.
* Move EMAILID, SPEAK, ERRCANCEL and ERRNOSHOW alarm properties into new FLAGS property.
*/
bool flagsValid = false;
QStringList flags;
QString reminder;
bool reminderOnce = false;
const QString prop = event->customProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY);
if (!prop.isEmpty())
{
// Convert the event's ARCHIVE property to parameters in the FLAGS property
flags = event->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY).split(KAEventPrivate::SC, QString::SkipEmptyParts);
flags << KAEventPrivate::ARCHIVE_FLAG;
flagsValid = true;
if (prop != QLatin1String("0")) // "0" was a dummy parameter if no others were present
{
// It's the archive property containing a reminder time and/or repeat-at-login flag.
// This was present when no reminder/at-login alarm was pending.
const QStringList list = prop.split(KAEventPrivate::SC, QString::SkipEmptyParts);
for (int i = 0; i < list.count(); ++i)
{
if (list[i] == KAEventPrivate::AT_LOGIN_TYPE)
flags << KAEventPrivate::AT_LOGIN_TYPE;
else if (list[i] == ARCHIVE_REMINDER_ONCE_TYPE)
reminderOnce = true;
else if (!list[i].isEmpty() && !list[i].startsWith(QChar::fromLatin1('-')))
reminder = list[i];
}
}
event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY, flags.join(KAEventPrivate::SC));
event->removeCustomProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY);
}
for (int i = 0, alend = alarms.count(); i < alend; ++i)
{
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr alarm = alarms[i];
#else
Alarm* alarm = alarms[i];
#endif
// Convert EMAILID, SPEAK, ERRCANCEL, ERRNOSHOW properties
QStringList flags;
QString property = alarm->customProperty(KACalendar::APPNAME, EMAIL_ID_PROPERTY);
if (!property.isEmpty())
{
flags << KAEventPrivate::EMAIL_ID_FLAG << property;
alarm->removeCustomProperty(KACalendar::APPNAME, EMAIL_ID_PROPERTY);
}
if (!alarm->customProperty(KACalendar::APPNAME, SPEAK_PROPERTY).isEmpty())
{
flags << KAEventPrivate::SPEAK_FLAG;
alarm->removeCustomProperty(KACalendar::APPNAME, SPEAK_PROPERTY);
}
if (!alarm->customProperty(KACalendar::APPNAME, CANCEL_ON_ERROR_PROPERTY).isEmpty())
{
flags << KAEventPrivate::CANCEL_ON_ERROR_FLAG;
alarm->removeCustomProperty(KACalendar::APPNAME, CANCEL_ON_ERROR_PROPERTY);
}
if (!alarm->customProperty(KACalendar::APPNAME, DONT_SHOW_ERROR_PROPERTY).isEmpty())
{
flags << KAEventPrivate::DONT_SHOW_ERROR_FLAG;
alarm->removeCustomProperty(KACalendar::APPNAME, DONT_SHOW_ERROR_PROPERTY);
}
if (!flags.isEmpty())
alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY, flags.join(KAEventPrivate::SC));
// Invalidate negative reminder periods in alarms
if (!alarm->hasStartOffset())
continue;
property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY);
QStringList types = property.split(QChar::fromLatin1(','), QString::SkipEmptyParts);
const int r = types.indexOf(REMINDER_ONCE_TYPE);
if (r >= 0)
{
// Move reminder-once indicator from the alarm to the event's FLAGS property
types[r] = KAEventPrivate::REMINDER_TYPE;
alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY, types.join(QChar::fromLatin1(',')));
reminderOnce = true;
}
if (r >= 0 || types.contains(KAEventPrivate::REMINDER_TYPE))
{
// The alarm is a reminder alarm
const int offset = alarm->startOffset().asSeconds();
if (offset > 0)
{
alarm->setStartOffset(0);
converted = true;
}
else if (offset < 0)
reminder = reminderToString(offset / 60);
}
}
if (!reminder.isEmpty())
{
// Write reminder parameters into the event's FLAGS property
if (!flagsValid)
flags = event->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY).split(KAEventPrivate::SC, QString::SkipEmptyParts);
if (flags.indexOf(KAEventPrivate::REMINDER_TYPE) < 0)
{
flags += KAEventPrivate::REMINDER_TYPE;
if (reminderOnce)
flags += KAEventPrivate::REMINDER_ONCE_FLAG;
flags += reminder;
}
}
}
if (readOnly)
event->setReadOnly(true);
event->endUpdates(); // finally issue an update notification
}
return converted;
}
/******************************************************************************
* Set the time for a date-only event to 00:00.
* Reply = true if the event was updated.
*/
#ifndef KALARMCAL_USE_KRESOURCES
bool KAEventPrivate::convertStartOfDay(const Event::Ptr& event)
#else
bool KAEventPrivate::convertStartOfDay(Event* event)
#endif
{
bool changed = false;
const QTime midnight(0, 0);
const QStringList flags = event->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY).split(KAEventPrivate::SC, QString::SkipEmptyParts);
if (flags.indexOf(KAEventPrivate::DATE_ONLY_FLAG) >= 0)
{
// It's an untimed event, so fix it
const KDateTime oldDt = event->dtStart();
const int adjustment = oldDt.time().secsTo(midnight);
if (adjustment)
{
event->setDtStart(KDateTime(oldDt.date(), midnight, oldDt.timeSpec()));
int deferralOffset = 0;
AlarmMap alarmMap;
readAlarms(event, &alarmMap);
for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it)
{
const AlarmData& data = it.value();
if (!data.alarm->hasStartOffset())
continue;
if (data.timedDeferral)
{
// Found a timed deferral alarm, so adjust the offset
deferralOffset = data.alarm->startOffset().asSeconds();
#ifndef KALARMCAL_USE_KRESOURCES
const_cast<Alarm*>(data.alarm.data())->setStartOffset(deferralOffset - adjustment);
#else
const_cast<Alarm*>(data.alarm)->setStartOffset(deferralOffset - adjustment);
#endif
}
else if (data.type == AUDIO_ALARM
&& data.alarm->startOffset().asSeconds() == deferralOffset)
{
// Audio alarm is set for the same time as the above deferral alarm
#ifndef KALARMCAL_USE_KRESOURCES
const_cast<Alarm*>(data.alarm.data())->setStartOffset(deferralOffset - adjustment);
#else
const_cast<Alarm*>(data.alarm)->setStartOffset(deferralOffset - adjustment);
#endif
}
}
changed = true;
}
}
else
{
// It's a timed event. Fix any untimed alarms.
bool foundDeferral = false;
int deferralOffset = 0;
int newDeferralOffset = 0;
DateTime start;
const KDateTime nextMainDateTime = readDateTime(event, false, start).kDateTime();
AlarmMap alarmMap;
readAlarms(event, &alarmMap);
for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it)
{
const AlarmData& data = it.value();
if (!data.alarm->hasStartOffset())
continue;
if ((data.type & DEFERRED_ALARM) && !data.timedDeferral)
{
// Found a date-only deferral alarm, so adjust its time
KDateTime altime = data.alarm->startOffset().end(nextMainDateTime);
altime.setTime(midnight);
deferralOffset = data.alarm->startOffset().asSeconds();
newDeferralOffset = event->dtStart().secsTo(altime);
#ifndef KALARMCAL_USE_KRESOURCES
const_cast<Alarm*>(data.alarm.data())->setStartOffset(newDeferralOffset);
#else
const_cast<Alarm*>(data.alarm)->setStartOffset(newDeferralOffset);
#endif
foundDeferral = true;
changed = true;
}
else if (foundDeferral
&& data.type == AUDIO_ALARM
&& data.alarm->startOffset().asSeconds() == deferralOffset)
{
// Audio alarm is set for the same time as the above deferral alarm
#ifndef KALARMCAL_USE_KRESOURCES
const_cast<Alarm*>(data.alarm.data())->setStartOffset(newDeferralOffset);
#else
const_cast<Alarm*>(data.alarm)->setStartOffset(newDeferralOffset);
#endif
changed = true;
}
}
}
return changed;
}
/******************************************************************************
* Convert simple repetitions in an event without a recurrence, to a
* recurrence. Repetitions which are an exact multiple of 24 hours are converted
* to daily recurrences; else they are converted to minutely recurrences. Note
* that daily and minutely recurrences produce different results when they span
* a daylight saving time change.
* Reply = true if any conversions were done.
*/
#ifndef KALARMCAL_USE_KRESOURCES
bool KAEventPrivate::convertRepetition(const Event::Ptr& event)
#else
bool KAEventPrivate::convertRepetition(Event* event)
#endif
{
const Alarm::List alarms = event->alarms();
if (alarms.isEmpty())
return false;
Recurrence* recur = event->recurrence(); // guaranteed to return non-null
if (recur->recurs())
return false;
bool converted = false;
const bool readOnly = event->isReadOnly();
for (int ai = 0, aend = alarms.count(); ai < aend; ++ai)
{
#ifndef KALARMCAL_USE_KRESOURCES
Alarm::Ptr alarm = alarms[ai];
#else
Alarm* alarm = alarms[ai];
#endif
if (alarm->repeatCount() > 0 && alarm->snoozeTime().value() > 0)
{
if (!converted)
{
event->startUpdates(); // prevent multiple update notifications
if (readOnly)
event->setReadOnly(false);
if ((alarm->snoozeTime().asSeconds() % (24*3600)) != 0)
recur->setMinutely(alarm->snoozeTime().asSeconds() / 60);
else
recur->setDaily(alarm->snoozeTime().asDays());
recur->setDuration(alarm->repeatCount() + 1);
converted = true;
}
alarm->setRepeatCount(0);
alarm->setSnoozeTime(0);
}
}
if (converted)
{
if (readOnly)
event->setReadOnly(true);
event->endUpdates(); // finally issue an update notification
}
return converted;
}
/*=============================================================================
= Class KAAlarm
= Corresponds to a single KCal::Alarm instance.
=============================================================================*/
KAAlarm::KAAlarm()
: d(new Private)
{
}
KAAlarm::Private::Private()
: mType(INVALID_ALARM),
mNextRepeat(0),
mRepeatAtLogin(false),
mDeferred(false)
{
}
KAAlarm::KAAlarm(const KAAlarm& other)
: d(new Private(*other.d))
{
}
KAAlarm::~KAAlarm()
{
delete d;
}
KAAlarm& KAAlarm::operator=(const KAAlarm& other)
{
if (&other != this)
*d = *other.d;
return *this;
}
KAAlarm::Action KAAlarm::action() const
{
return d->mActionType;
}
bool KAAlarm::isValid() const
{
return d->mType != INVALID_ALARM;
}
KAAlarm::Type KAAlarm::type() const
{
return d->mType;
}
DateTime KAAlarm::dateTime(bool withRepeats) const
{
return (withRepeats && d->mNextRepeat && d->mRepetition)
? d->mRepetition.duration(d->mNextRepeat).end(d->mNextMainDateTime.kDateTime())
: d->mNextMainDateTime;
}
QDate KAAlarm::date() const
{
return d->mNextMainDateTime.date();
}
QTime KAAlarm::time() const
{
return d->mNextMainDateTime.effectiveTime();
}
bool KAAlarm::repeatAtLogin() const
{
return d->mRepeatAtLogin;
}
bool KAAlarm::isReminder() const
{
return d->mType == REMINDER_ALARM;
}
bool KAAlarm::deferred() const
{
return d->mDeferred;
}
bool KAAlarm::timedDeferral() const
{
return d->mDeferred && d->mTimedDeferral;
}
void KAAlarm::setTime(const DateTime& dt)
{
d->mNextMainDateTime = dt;
}
void KAAlarm::setTime(const KDateTime& dt)
{
d->mNextMainDateTime = dt;
}
#ifdef KDE_NO_DEBUG_OUTPUT
const char* KAAlarm::debugType(Type) { return ""; }
#else
const char* KAAlarm::debugType(Type type)
{
switch (type)
{
case MAIN_ALARM: return "MAIN";
case REMINDER_ALARM: return "REMINDER";
case DEFERRED_ALARM: return "DEFERRED";
case DEFERRED_REMINDER_ALARM: return "DEFERRED_REMINDER";
case AT_LOGIN_ALARM: return "LOGIN";
case DISPLAYING_ALARM: return "DISPLAYING";
default: return "INVALID";
}
}
#endif
/*=============================================================================
= Class EmailAddressList
=============================================================================*/
/******************************************************************************
* Sets the list of email addresses, removing any empty addresses.
* Reply = false if empty addresses were found.
*/
#ifndef KALARMCAL_USE_KRESOURCES
EmailAddressList& EmailAddressList::operator=(const Person::List& addresses)
#else
EmailAddressList& EmailAddressList::operator=(const QList<Person>& addresses)
#endif
{
clear();
for (int p = 0, end = addresses.count(); p < end; ++p)
{
#ifndef KALARMCAL_USE_KRESOURCES
if (!addresses[p]->email().isEmpty())
#else
if (!addresses[p].email().isEmpty())
#endif
append(addresses[p]);
}
return *this;
}
/******************************************************************************
* Return the email address list as a string list of email addresses.
*/
EmailAddressList::operator QStringList() const
{
QStringList list;
for (int p = 0, end = count(); p < end; ++p)
list += address(p);
return list;
}
/******************************************************************************
* Return the email address list as a string, each address being delimited by
* the specified separator string.
*/
QString EmailAddressList::join(const QString& separator) const
{
QString result;
bool first = true;
for (int p = 0, end = count(); p < end; ++p)
{
if (first)
first = false;
else
result += separator;
result += address(p);
}
return result;
}
/******************************************************************************
* Convert one item into an email address, including name.
*/
QString EmailAddressList::address(int index) const
{
if (index < 0 || index > count())
return QString();
QString result;
bool quote = false;
#ifndef KALARMCAL_USE_KRESOURCES
const Person::Ptr person = (*this)[index];
const QString name = person->name();
#else
const Person person = (*this)[index];
const QString name = person.name();
#endif
if (!name.isEmpty())
{
// Need to enclose the name in quotes if it has any special characters
for (int i = 0, len = name.length(); i < len; ++i)
{
const QChar ch = name[i];
if (!ch.isLetterOrNumber())
{
quote = true;
result += QLatin1Char('\"');
break;
}
}
#ifndef KALARMCAL_USE_KRESOURCES
result += (*this)[index]->name();
#else
result += (*this)[index].name();
#endif
result += (quote ? QLatin1String("\" <") : QLatin1String(" <"));
quote = true; // need angle brackets round email address
}
#ifndef KALARMCAL_USE_KRESOURCES
result += person->email();
#else
result += person.email();
#endif
if (quote)
result += QLatin1Char('>');
return result;
}
/******************************************************************************
* Return a list of the pure email addresses, excluding names.
*/
QStringList EmailAddressList::pureAddresses() const
{
QStringList list;
for (int p = 0, end = count(); p < end; ++p)
#ifndef KALARMCAL_USE_KRESOURCES
list += at(p)->email();
#else
list += at(p).email();
#endif
return list;
}
/******************************************************************************
* Return a list of the pure email addresses, excluding names, as a string.
*/
QString EmailAddressList::pureAddresses(const QString& separator) const
{
QString result;
bool first = true;
for (int p = 0, end = count(); p < end; ++p)
{
if (first)
first = false;
else
result += separator;
#ifndef KALARMCAL_USE_KRESOURCES
result += at(p)->email();
#else
result += at(p).email();
#endif
}
return result;
}
/*=============================================================================
= Static functions
=============================================================================*/
/******************************************************************************
* Set the specified alarm to be a procedure alarm with the given command line.
* The command line is first split into its program file and arguments before
* initialising the alarm.
*/
#ifndef KALARMCAL_USE_KRESOURCES
static void setProcedureAlarm(const Alarm::Ptr& alarm, const QString& commandLine)
#else
static void setProcedureAlarm(Alarm* alarm, const QString& commandLine)
#endif
{
QString command;
QString arguments;
QChar quoteChar;
bool quoted = false;
const uint posMax = commandLine.length();
uint pos;
for (pos = 0; pos < posMax; ++pos)
{
const QChar ch = commandLine[pos];
if (quoted)
{
if (ch == quoteChar)
{
++pos; // omit the quote character
break;
}
command += ch;
}
else
{
bool done = false;
switch (ch.toLatin1())
{
case ' ':
case ';':
case '|':
case '<':
case '>':
done = !command.isEmpty();
break;
case '\'':
case '"':
if (command.isEmpty())
{
// Start of a quoted string. Omit the quote character.
quoted = true;
quoteChar = ch;
break;
}
// fall through to default
default:
command += ch;
break;
}
if (done)
break;
}
}
// Skip any spaces after the command
for ( ; pos < posMax && commandLine[pos] == QLatin1Char(' '); ++pos) ;
arguments = commandLine.mid(pos);
alarm->setProcedureAlarm(command, arguments);
}
/******************************************************************************
* Converts a reminder interval into a parameter string for the
* X-KDE-KALARM-FLAGS property.
*/
QString reminderToString(int minutes)
{
char unit = 'M';
int count = abs(minutes);
if (count % 1440 == 0)
{
unit = 'D';
count /= 1440;
}
else if (count % 60 == 0)
{
unit = 'H';
count /= 60;
}
if (minutes < 0)
count = -count;
return QString::fromLatin1("%1%2").arg(count).arg(unit);
}
} // namespace KAlarmCal
// vim: et sw=4: