kaevent.cpp 239 KB
Newer Older
1
/*
2
 *  kaevent.cpp  -  represents KAlarm calendar events
David Jarvie's avatar
David Jarvie committed
3
4
 *  This file is part of kalarmcal library, which provides access to KAlarm
 *  calendar data.
David Jarvie's avatar
David Jarvie committed
5
 *  SPDX-FileCopyrightText: 2001-2021 David Jarvie <djarvie@kde.org>
6
 *
7
 *  SPDX-License-Identifier: LGPL-2.0-or-later
8
9
10
11
 */

#include "kaevent.h"

12
#include "akonadi.h"   // for deprecated setItemPayload() only
13
14
15
#include "alarmtext.h"
#include "identities.h"
#include "version.h"
16

Laurent Montel's avatar
Laurent Montel committed
17
#include <KCalendarCore/MemoryCalendar>
18
19
#include <KHolidays/Holiday>
#include <KHolidays/HolidayRegion>
20

21
#include <klocalizedstring.h>
22

Laurent Montel's avatar
Laurent Montel committed
23
#include "kalarmcal_debug.h"
24

25
using namespace KCalendarCore;
26
27
using namespace KHolidays;

28
namespace KAlarmCal
29
30
{

David Jarvie's avatar
David Jarvie committed
31
32
//=============================================================================

Laurent Montel's avatar
Laurent Montel committed
33
using EmailAddress = KCalendarCore::Person;
34
class EmailAddressList : public KCalendarCore::Person::List
35
{
Laurent Montel's avatar
Laurent Montel committed
36
public:
37
38
    EmailAddressList() : KCalendarCore::Person::List() { }
    EmailAddressList(const KCalendarCore::Person::List &list)
Laurent Montel's avatar
Laurent Montel committed
39
40
41
    {
        operator=(list);
    }
42
    EmailAddressList &operator=(const KCalendarCore::Person::List &);
Laurent Montel's avatar
Laurent Montel committed
43
44
45
46
47
48
    operator QStringList() const;
    QString     join(const QString &separator) const;
    QStringList pureAddresses() const;
    QString     pureAddresses(const QString &separator) const;
private:
    QString     address(int index) const;
49
50
};

David Jarvie's avatar
David Jarvie committed
51
//=============================================================================
52

53
class Q_DECL_HIDDEN KAAlarm::Private
54
{
Laurent Montel's avatar
Laurent Montel committed
55
56
57
public:
    Private();

58
59
60
61
62
63
64
65
66
    Action      mActionType;           // alarm action type
    Type        mType{INVALID_ALARM};  // alarm type
    DateTime    mNextMainDateTime;     // next time to display the alarm, excluding repetitions
    Repetition  mRepetition;           // sub-repetition count and interval
    int         mNextRepeat{0};        // repetition count of next due sub-repetition
    bool        mRepeatAtLogin{false}; // whether to repeat the alarm at every login
    bool        mRecurs;               // there is a recurrence rule for the alarm
    bool        mDeferred{false};      // 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
67
68
};

David Jarvie's avatar
David Jarvie committed
69
//=============================================================================
70

71
class Q_DECL_HIDDEN KAEventPrivate : public QSharedData
72
{
Laurent Montel's avatar
Laurent Montel committed
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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 {
        Alarm::Ptr                  alarm;
        QString                     cleanText;       // text or audio file name
        QFont                       font;
        QColor                      bgColour, fgColour;
        float                       soundVolume;
        float                       fadeVolume;
        int                         fadeSeconds;
        int                         repeatSoundPause;
        int                         nextRepeat;
125
        uint                        emailFromId;
Laurent Montel's avatar
Laurent Montel committed
126
127
128
129
        KAEventPrivate::AlarmType   type;
        KAAlarm::Action             action;
        int                         displayingFlags;
        KAEvent::ExtraActionOptions extraActionOptions;
130
        bool                        speak;
Laurent Montel's avatar
Laurent Montel committed
131
132
133
134
135
136
137
138
139
        bool                        defaultFont;
        bool                        isEmailText;
        bool                        commandScript;
        bool                        timedDeferral;
        bool                        hiddenReminder;
    };
    typedef QMap<AlarmType, AlarmData> AlarmMap;

    KAEventPrivate();
David Jarvie's avatar
David Jarvie committed
140
141
142
    KAEventPrivate(const KADateTime &, const QString &name, const QString &message,
                   const QColor &bg, const QColor &fg, const QFont &f,
                   KAEvent::SubAction, int lateCancel, KAEvent::Flags flags,
Laurent Montel's avatar
Laurent Montel committed
143
                   bool changesPending = false);
144
    explicit KAEventPrivate(const KCalendarCore::Event::Ptr &);
Laurent Montel's avatar
Laurent Montel committed
145
146
147
148
149
    KAEventPrivate(const KAEventPrivate &);
    ~KAEventPrivate()
    {
        delete mRecurrence;
    }
150
    KAEventPrivate &operator=(const KAEventPrivate &e)
Laurent Montel's avatar
Laurent Montel committed
151
152
153
    {
        if (&e != this) {
            copy(e);
154
155
        }
        return *this;
Laurent Montel's avatar
Laurent Montel committed
156
157
    }
    void               setAudioFile(const QString &filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile);
David Jarvie's avatar
David Jarvie committed
158
    KAEvent::OccurType setNextOccurrence(const KADateTime &preDateTime);
Laurent Montel's avatar
Laurent Montel committed
159
160
161
162
163
164
165
166
    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();
167
168
    bool               setDisplaying(const KAEventPrivate &, KAAlarm::Type, ResourceId, const KADateTime &dt, bool showEdit, bool showDefer);
    void               reinstateFromDisplaying(const KCalendarCore::Event::Ptr &, ResourceId &, bool &showEdit, bool &showDefer);
Laurent Montel's avatar
Laurent Montel committed
169
170
171
172
173
174
    void               startChanges()
    {
        ++mChangeCount;
    }
    void               endChanges();
    void               removeExpiredAlarm(KAAlarm::Type);
David Jarvie's avatar
David Jarvie committed
175
    bool               compare(const KAEventPrivate&, KAEvent::Comparison) const;
Laurent Montel's avatar
Laurent Montel committed
176
177
178
    KAAlarm            alarm(KAAlarm::Type) const;
    KAAlarm            firstAlarm() const;
    KAAlarm            nextAlarm(KAAlarm::Type) const;
179
    bool               updateKCalEvent(const KCalendarCore::Event::Ptr &, KAEvent::UidAction, bool setCustomProperties = true) const;
Laurent Montel's avatar
Laurent Montel committed
180
181
182
    DateTime           mainDateTime(bool withRepeats = false) const
    {
        return (withRepeats && mNextRepeat && mRepetition)
David Jarvie's avatar
David Jarvie committed
183
               ? DateTime(mRepetition.duration(mNextRepeat).end(mNextMainDateTime.qDateTime())) : mNextMainDateTime;
Laurent Montel's avatar
Laurent Montel committed
184
185
186
    }
    DateTime           mainEndRepeatTime() const
    {
David Jarvie's avatar
David Jarvie committed
187
        return mRepetition ? DateTime(mRepetition.duration().end(mNextMainDateTime.qDateTime())) : mNextMainDateTime;
Laurent Montel's avatar
Laurent Montel committed
188
    }
Laurent Montel's avatar
Laurent Montel committed
189
    DateTime           deferralLimit(KAEvent::DeferLimitType * = nullptr) const;
Laurent Montel's avatar
Laurent Montel committed
190
    KAEvent::Flags     flags() const;
191
    bool               excludedByWorkTimeOrHoliday(const KADateTime &dt) const;
Laurent Montel's avatar
Laurent Montel committed
192
    bool               setRepetition(const Repetition &);
David Jarvie's avatar
David Jarvie committed
193
194
195
    bool               occursAfter(const KADateTime &preDateTime, bool includeRepetitions) const;
    KAEvent::OccurType nextOccurrence(const KADateTime &preDateTime, DateTime &result, KAEvent::OccurOption = KAEvent::IGNORE_REPETITION) const;
    KAEvent::OccurType previousOccurrence(const KADateTime &afterDateTime, DateTime &result, bool includeRepetitions = false) const;
Laurent Montel's avatar
Laurent Montel committed
196
    void               setRecurrence(const KARecurrence &);
Laurent Montel's avatar
Laurent Montel committed
197
    bool               setRecur(KCalendarCore::RecurrenceRule::PeriodType, int freq, int count, QDate end, KARecurrence::Feb29Type = KARecurrence::Feb29_None);
198
    bool               setRecur(KCalendarCore::RecurrenceRule::PeriodType, int freq, int count, const KADateTime &end, KARecurrence::Feb29Type = KARecurrence::Feb29_None);
Laurent Montel's avatar
Laurent Montel committed
199
200
201
    KARecurrence::Type checkRecur() const;
    void               clearRecur();
    void               calcTriggerTimes() const;
202
#ifdef KDE_NO_DEBUG_OUTPUT
Laurent Montel's avatar
Laurent Montel committed
203
    void               dumpDebug() const  { }
204
#else
Laurent Montel's avatar
Laurent Montel committed
205
    void               dumpDebug() const;
206
#endif
207
208
209
210
211
    static bool        convertRepetition(const KCalendarCore::Event::Ptr &);
    static bool        convertStartOfDay(const KCalendarCore::Event::Ptr &);
    static DateTime    readDateTime(const KCalendarCore::Event::Ptr &, bool localZone, bool dateOnly, DateTime &start);
    static void        readAlarms(const KCalendarCore::Event::Ptr &, AlarmMap *, bool cmdDisplay = false);
    static void        readAlarm(const KCalendarCore::Alarm::Ptr &, AlarmData &, bool audioMain, bool cmdDisplay = false);
David Jarvie's avatar
David Jarvie committed
212
    static QSharedPointer<const HolidayRegion> holidays();
Laurent Montel's avatar
Laurent Montel committed
213
214
private:
    void               copy(const KAEventPrivate &);
David Jarvie's avatar
David Jarvie committed
215
216
    bool               mayOccurDailyDuringWork(const KADateTime &) const;
    int                nextWorkRepetition(const KADateTime &pre) const;
Laurent Montel's avatar
Laurent Montel committed
217
218
    void               calcNextWorkingTime(const DateTime &nextTrigger) const;
    DateTime           nextWorkingTime() const;
David Jarvie's avatar
David Jarvie committed
219
    KAEvent::OccurType nextRecurrence(const KADateTime &preDateTime, DateTime &result) const;
220
221
222
    void               setAudioAlarm(const KCalendarCore::Alarm::Ptr &) const;
    KCalendarCore::Alarm::Ptr initKCalAlarm(const KCalendarCore::Event::Ptr &, const DateTime &, const QStringList &types, AlarmType = INVALID_ALARM) const;
    KCalendarCore::Alarm::Ptr initKCalAlarm(const KCalendarCore::Event::Ptr &, int startOffsetSecs, const QStringList &types, AlarmType = INVALID_ALARM) const;
Laurent Montel's avatar
Laurent Montel committed
223
224
    inline void        set_deferral(DeferType);
    inline void        activate_reminder(bool activate);
David Jarvie's avatar
David Jarvie committed
225
    static int         transitionIndex(const QDateTime &utc, const QTimeZone::OffsetDataList& transitions);
Laurent Montel's avatar
Laurent Montel committed
226
227
228

public:
    static QFont       mDefaultFont;       // default alarm message font
David Jarvie's avatar
David Jarvie committed
229
    static QSharedPointer<const HolidayRegion> mHolidays;  // holiday region to use
Laurent Montel's avatar
Laurent Montel committed
230
231
232
233
234
235
236
237
    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
    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
238
    mutable KAEvent::CmdErrType mCommandError{KAEvent::CMD_NO_ERROR}; // command execution error last time the alarm triggered
Laurent Montel's avatar
Laurent Montel committed
239

David Jarvie's avatar
David Jarvie committed
240
    QString            mEventID;           // UID: KCalendarCore::Event unique ID
241
242
    QMap<QByteArray, QString> mCustomProperties; // KCalendarCore::Event's non-KAlarm custom properties
    Akonadi::Item::Id  mItemId{-1};        // Akonadi::Item ID for this event
David Jarvie's avatar
David Jarvie committed
243
244
    mutable ResourceId mResourceId{-1};    // ID of resource containing the event, or for a displaying event,
                                           // saved resource ID (not the resource the event is in)
David Jarvie's avatar
David Jarvie committed
245
    QString            mName;              // name of the alarm
Laurent Montel's avatar
Laurent Montel committed
246
247
248
249
250
    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
David Jarvie's avatar
David Jarvie committed
251
    KADateTime         mCreatedDateTime;   // CREATED: date event was created, or saved in archive calendar
Laurent Montel's avatar
Laurent Montel committed
252
    DateTime           mNextMainDateTime;  // next time to display the alarm, excluding repetitions
David Jarvie's avatar
David Jarvie committed
253
    KADateTime         mAtLoginDateTime;   // repeat-at-login end time
Laurent Montel's avatar
Laurent Montel committed
254
255
    DateTime           mDeferralTime;      // extra time to trigger alarm (if alarm or reminder deferred)
    DateTime           mDisplayingTime;    // date/time shown in the alarm currently being displayed
256
    int                mDisplayingFlags;   // type of alarm which is currently being displayed (for display alarm)
257
    int                mReminderMinutes{0};// how long in advance reminder is to be, or 0 if none (<0 for reminder AFTER the alarm)
Laurent Montel's avatar
Laurent Montel committed
258
    DateTime           mReminderAfterTime; // if mReminderActive true, time to trigger reminder AFTER the main alarm, or invalid if not pending
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
    ReminderType       mReminderActive{NO_REMINDER}; // whether a reminder is due (before next, or after last, main alarm/recurrence)
    int                mDeferDefaultMinutes{0}; // default number of minutes for deferral dialog, or 0 to select time control
    bool               mDeferDefaultDateOnly{false}; // select date-only by default in deferral dialog
    int                mRevision{0};           // SEQUENCE: revision number of the original alarm, or 0
    KARecurrence      *mRecurrence{nullptr};   // RECUR: recurrence specification, or 0 if none
    Repetition         mRepetition;            // sub-repetition count and interval
    int                mNextRepeat{0};         // repetition count of next due sub-repetition
    int                mAlarmCount{0};         // number of alarms: count of !mMainExpired, mRepeatAtLogin, mDeferral, mReminderActive, mDisplaying
    DeferType          mDeferral{NO_DEFERRAL}; // whether the alarm is an extra deferred/deferred-reminder alarm
    Akonadi::Item::Id  mAkonadiItemId{-1};     // if email text, message's Akonadi item ID
    int                mTemplateAfterTime{-1}; // 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{0};  // 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};        // >0 = inhibit calling calcTriggerTimes()
    mutable bool       mTriggerChanged{false}; // true if need to recalculate trigger times
    QString            mLogFile;               // alarm output is to be logged to this URL
    float              mSoundVolume{-1.0f};    // volume for sound file (range 0 - 1), or < 0 for unspecified
    float              mFadeVolume{-1.0f};     // initial volume for sound file (range 0 - 1), or < 0 for no fade
    int                mFadeSeconds{0};        // fade time (seconds) for sound file, or 0 if none
    int                mRepeatSoundPause{-1};  // seconds to pause between sound file repetitions, or -1 if no repetition
    int                mLateCancel{0};         // how many minutes late will cancel the alarm, or 0 for no cancellation
    bool               mExcludeHolidays{false}; // don't trigger alarms on holidays
David Jarvie's avatar
David Jarvie committed
286
    mutable QSharedPointer<const HolidayRegion> mExcludeHolidayRegion; // holiday region used to exclude alarms on holidays (= mHolidays when trigger calculated)
287
288
289
290
291
292
293
294
295
296
297
    mutable int        mWorkTimeOnly{0};         // 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{CalEvent::EMPTY};   // event category (active, archived, template, ...)
    KAEvent::ExtraActionOptions mExtraActionOptions; // options for pre- or post-alarm actions
    KACalendar::Compat mCompatibility{KACalendar::Current}; // event's storage format compatibility
    bool               mReadOnly{false};         // event is read-only in its original calendar file
    bool               mConfirmAck{false};       // alarm acknowledgement requires confirmation by user
    bool               mUseDefaultFont;          // use default message font, not mFont
    bool               mCommandScript{false};    // the command text is a script, not a shell command line
    bool               mCommandXterm{false};     // command alarm is to be executed in a terminal window
    bool               mCommandDisplay{false};   // command output is to be displayed in an alarm window
298
    bool               mCommandHideError{false}; // don't show command execution errors to user
299
300
301
302
303
304
    bool               mEmailBcc{false};         // blind copy the email to the user
    bool               mBeep{false};             // whether to beep when the alarm is displayed
    bool               mSpeak{false};            // whether to speak the message when the alarm is displayed
    bool               mCopyToKOrganizer{false}; // KOrganizer should hold a copy of the event
    bool               mReminderOnceOnly{false}; // the reminder is output only for the first recurrence
    bool               mAutoClose{false};        // whether to close the alarm window after the late-cancel period
305
    bool               mNotify{false};           // alarm should be shown by the notification system, not in a window
306
307
308
309
310
311
312
313
    bool               mMainExpired;             // main alarm has expired (in which case a deferral alarm will exist)
    bool               mRepeatAtLogin{false};    // whether to repeat the alarm at every login
    bool               mArchiveRepeatAtLogin{false}; // if now archived, original event was repeat-at-login
    bool               mArchive{false};          // event has triggered in the past, so archive it when closed
    bool               mDisplaying{false};       // whether the alarm is currently being displayed (i.e. in displaying calendar)
    bool               mDisplayingDefer{false};  // show Defer button (applies to displaying calendar only)
    bool               mDisplayingEdit{false};   // show Edit button (applies to displaying calendar only)
    bool               mEnabled;                 // false if event is disabled
Laurent Montel's avatar
Laurent Montel committed
314
315
316
317

public:
    static const QByteArray FLAGS_PROPERTY;
    static const QString DATE_ONLY_FLAG;
318
    static const QString LOCAL_ZONE_FLAG;
Laurent Montel's avatar
Laurent Montel committed
319
320
321
322
323
324
325
326
327
    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;
328
    static const QString NOTIFY_FLAG;
Laurent Montel's avatar
Laurent Montel committed
329
    static const QString TEMPL_AFTER_TIME_FLAG;
330
    static const QString KMAIL_ITEM_FLAG;
Laurent Montel's avatar
Laurent Montel committed
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
    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;
364
365
};

David Jarvie's avatar
David Jarvie committed
366
//=============================================================================
367
368
369
370

// 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.
Laurent Montel's avatar
Laurent Montel committed
371

Laurent Montel's avatar
Laurent Montel committed
372
373
QByteArray KAEvent::currentCalendarVersionString()
{
David Jarvie's avatar
David Jarvie committed
374
    return QByteArray("2.7.0");   // This is NOT the KAlarmCal library .so version!
Laurent Montel's avatar
Laurent Montel committed
375
376
377
}
int        KAEvent::currentCalendarVersion()
{
David Jarvie's avatar
David Jarvie committed
378
    return Version(2, 7, 0);   // This is NOT the KAlarmCal library .so version!
Laurent Montel's avatar
Laurent Montel committed
379
}
380
381
382
383
384

// Custom calendar properties.
// Note that all custom property names are prefixed with X-KDE-KALARM- in the calendar file.

// Event properties
385
const QByteArray KAEventPrivate::FLAGS_PROPERTY("FLAGS");              // X-KDE-KALARM-FLAGS property
386
const QString    KAEventPrivate::DATE_ONLY_FLAG        = QStringLiteral("DATE");
387
const QString    KAEventPrivate::LOCAL_ZONE_FLAG       = QStringLiteral("LOCAL");
388
389
390
391
392
393
394
395
396
const QString    KAEventPrivate::EMAIL_BCC_FLAG        = QStringLiteral("BCC");
const QString    KAEventPrivate::CONFIRM_ACK_FLAG      = QStringLiteral("ACKCONF");
const QString    KAEventPrivate::KORGANIZER_FLAG       = QStringLiteral("KORG");
const QString    KAEventPrivate::EXCLUDE_HOLIDAYS_FLAG = QStringLiteral("EXHOLIDAYS");
const QString    KAEventPrivate::WORK_TIME_ONLY_FLAG   = QStringLiteral("WORKTIME");
const QString    KAEventPrivate::REMINDER_ONCE_FLAG    = QStringLiteral("ONCE");
const QString    KAEventPrivate::DEFER_FLAG            = QStringLiteral("DEFER");   // default defer interval for this alarm
const QString    KAEventPrivate::LATE_CANCEL_FLAG      = QStringLiteral("LATECANCEL");
const QString    KAEventPrivate::AUTO_CLOSE_FLAG       = QStringLiteral("LATECLOSE");
397
const QString    KAEventPrivate::NOTIFY_FLAG           = QStringLiteral("NOTIFY");
398
const QString    KAEventPrivate::TEMPL_AFTER_TIME_FLAG = QStringLiteral("TMPLAFTTIME");
399
const QString    KAEventPrivate::KMAIL_ITEM_FLAG       = QStringLiteral("KMAIL");
400
const QString    KAEventPrivate::ARCHIVE_FLAG          = QStringLiteral("ARCHIVE");
401
402
403
404

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
405
406
const QString    KAEventPrivate::xtermURL = QStringLiteral("xterm:");
const QString    KAEventPrivate::displayURL = QStringLiteral("display:");
407
408

// - General alarm properties
409
const QByteArray KAEventPrivate::TYPE_PROPERTY("TYPE");                // X-KDE-KALARM-TYPE property
David Jarvie's avatar
David Jarvie committed
410
411
412
413
414
415
416
417
418
const QString    KAEventPrivate::FILE_TYPE             = QStringLiteral("FILE");
const QString    KAEventPrivate::AT_LOGIN_TYPE         = QStringLiteral("LOGIN");
const QString    KAEventPrivate::REMINDER_TYPE         = QStringLiteral("REMINDER");
const QString    KAEventPrivate::TIME_DEFERRAL_TYPE    = QStringLiteral("DEFERRAL");
const QString    KAEventPrivate::DATE_DEFERRAL_TYPE    = QStringLiteral("DATE_DEFERRAL");
const QString    KAEventPrivate::DISPLAYING_TYPE       = QStringLiteral("DISPLAYING");   // used only in displaying calendar
const QString    KAEventPrivate::PRE_ACTION_TYPE       = QStringLiteral("PRE");
const QString    KAEventPrivate::POST_ACTION_TYPE      = QStringLiteral("POST");
const QString    KAEventPrivate::SOUND_REPEAT_TYPE     = QStringLiteral("SOUNDREPEAT");
419
const QByteArray KAEventPrivate::NEXT_REPEAT_PROPERTY("NEXTREPEAT");   // X-KDE-KALARM-NEXTREPEAT property
David Jarvie's avatar
David Jarvie committed
420
const QString    KAEventPrivate::HIDDEN_REMINDER_FLAG  = QStringLiteral("HIDE");
421
// - Display alarm properties
422
const QByteArray KAEventPrivate::FONT_COLOUR_PROPERTY("FONTCOLOR");    // X-KDE-KALARM-FONTCOLOR property
423
// - Email alarm properties
David Jarvie's avatar
David Jarvie committed
424
const QString    KAEventPrivate::EMAIL_ID_FLAG         = QStringLiteral("EMAILID");
425
// - Audio alarm properties
426
const QByteArray KAEventPrivate::VOLUME_PROPERTY("VOLUME");            // X-KDE-KALARM-VOLUME property
David Jarvie's avatar
David Jarvie committed
427
const QString    KAEventPrivate::SPEAK_FLAG            = QStringLiteral("SPEAK");
428
// - Command alarm properties
429
430
431
const QString    KAEventPrivate::EXEC_ON_DEFERRAL_FLAG = QStringLiteral("EXECDEFER");
const QString    KAEventPrivate::CANCEL_ON_ERROR_FLAG  = QStringLiteral("ERRCANCEL");
const QString    KAEventPrivate::DONT_SHOW_ERROR_FLAG  = QStringLiteral("ERRNOSHOW");
432
433

// Event status strings
David Jarvie's avatar
David Jarvie committed
434
const QString    KAEventPrivate::DISABLED_STATUS       = QStringLiteral("DISABLED");
435
436

// Displaying event ID identifier
437
438
const QString    KAEventPrivate::DISP_DEFER = QStringLiteral("DEFER");
const QString    KAEventPrivate::DISP_EDIT  = QStringLiteral("EDIT");
439
440

// Command error strings
441
442
443
const QString    KAEventPrivate::CMD_ERROR_VALUE      = QStringLiteral("MAIN");
const QString    KAEventPrivate::CMD_ERROR_PRE_VALUE  = QStringLiteral("PRE");
const QString    KAEventPrivate::CMD_ERROR_POST_VALUE = QStringLiteral("POST");
444

445
const QString    KAEventPrivate::SC = QStringLiteral(";");
446

David Jarvie's avatar
David Jarvie committed
447
448
449
450
451
452
QFont                               KAEventPrivate::mDefaultFont;
QSharedPointer<const HolidayRegion> KAEventPrivate::mHolidays;
QBitArray                           KAEventPrivate::mWorkDays(7);
QTime                               KAEventPrivate::mWorkDayStart(9, 0, 0);
QTime                               KAEventPrivate::mWorkDayEnd(17, 0, 0);
int                                 KAEventPrivate::mWorkTimeIndex = 1;
453

Laurent Montel's avatar
Laurent Montel committed
454
static void setProcedureAlarm(const Alarm::Ptr &, const QString &commandLine);
455
456
457
458
static QString reminderToString(int minutes);

/*=============================================================================
= Class KAEvent
David Jarvie's avatar
David Jarvie committed
459
= Corresponds to a KCalendarCore::Event instance.
460
461
=============================================================================*/

462
inline void KAEventPrivate::activate_reminder(bool activate)
463
{
Laurent Montel's avatar
Laurent Montel committed
464
465
    if (activate  &&  mReminderActive != ACTIVE_REMINDER  &&  mReminderMinutes) {
        if (mReminderActive == NO_REMINDER) {
466
            ++mAlarmCount;
Laurent Montel's avatar
Laurent Montel committed
467
        }
468
        mReminderActive = ACTIVE_REMINDER;
Laurent Montel's avatar
Laurent Montel committed
469
    } else if (!activate  &&  mReminderActive != NO_REMINDER) {
470
471
472
473
474
475
        mReminderActive = NO_REMINDER;
        mReminderAfterTime = DateTime();
        --mAlarmCount;
    }
}

David Jarvie's avatar
David Jarvie committed
476
Q_GLOBAL_STATIC_WITH_ARGS(QSharedDataPointer<KAEventPrivate>,
477
478
                          emptyKAEventPrivate, (new KAEventPrivate))

479
KAEvent::KAEvent()
480
    : d(*emptyKAEventPrivate)
481
482
{ }

483
KAEventPrivate::KAEventPrivate()
484
485
{ }

486
487
488
/******************************************************************************
* Initialise the instance with the specified parameters.
*/
David Jarvie's avatar
David Jarvie committed
489
490
KAEvent::KAEvent(const KADateTime &dt, const QString &name, const QString &message,
                 const QColor &bg, const QColor &fg, const QFont &f,
491
                 SubAction action, int lateCancel, Flags flags, bool changesPending)
David Jarvie's avatar
David Jarvie committed
492
    : d(new KAEventPrivate(dt, name, message, bg, fg, f, action, lateCancel, flags, changesPending))
493
494
495
{
}

David Jarvie's avatar
David Jarvie committed
496
497
498
499
500
501
502
503
504
505
KAEvent::KAEvent(const KADateTime &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, QString(), message, bg, fg, f, action, lateCancel, flags, changesPending))
{
}

KAEventPrivate::KAEventPrivate(const KADateTime &dateTime, const QString &name, const QString &text,
                               const QColor &bg, const QColor &fg, const QFont &font,
                               KAEvent::SubAction action, int lateCancel, KAEvent::Flags flags,
506
                               bool changesPending)
David Jarvie's avatar
David Jarvie committed
507
508
    : mName(name)
    , mAlarmCount(1)
509
510
511
512
513
    , mBgColour(bg)
    , mFgColour(fg)
    , mFont(font)
    , mLateCancel(lateCancel)     // do this before setting flags
    , mCategory(CalEvent::ACTIVE)
514
{
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
    mStartDateTime = dateTime;
    if (flags & KAEvent::ANY_TIME)
        mStartDateTime.setDateOnly(true);
    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;
Laurent Montel's avatar
Laurent Montel committed
530
    }
531
532
533
534
535
536
537
538
539
540
    mText                   = (mActionSubType == KAEvent::COMMAND) ? text.trimmed()
                            : (mActionSubType == KAEvent::AUDIO)   ? QString() : text;
    mAudioFile              = (mActionSubType == KAEvent::AUDIO) ? text : QString();
    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;
541
    mCommandHideError       = flags & KAEvent::DONT_SHOW_ERROR;
542
543
544
545
546
547
548
549
550
    mCopyToKOrganizer       = flags & KAEvent::COPY_KORGANIZER;
    mExcludeHolidays        = flags & KAEvent::EXCL_HOLIDAYS;
    mExcludeHolidayRegion   = holidays();
    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;
551
    mNotify                 = flags & KAEvent::NOTIFY;
552
553
554
555
556
557
558
559
560
561
562
    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);
    }

    mMainExpired            = false;
    mChangeCount            = changesPending ? 1 : 0;
    mTriggerChanged         = true;
563
564
565
}

/******************************************************************************
566
* Initialise the KAEvent from a KCalendarCore::Event.
567
*/
568
569
KAEvent::KAEvent(const KCalendarCore::Event::Ptr &event)
    : d(new KAEventPrivate(event))
570
571
572
{
}

573
KAEventPrivate::KAEventPrivate(const KCalendarCore::Event::Ptr &event)
Laurent Montel's avatar
Laurent Montel committed
574
{
575
576
    startChanges();
    // Extract status from the event
577
578
    mEventID        = event->uid();
    mRevision       = event->revision();
David Jarvie's avatar
David Jarvie committed
579
    mName           = event->summary();
580
581
582
583
584
    mBgColour       = QColor(255, 255, 255);    // missing/invalid colour - return white background
    mFgColour       = QColor(0, 0, 0);          // and black foreground
    mReadOnly       = event->isReadOnly();
    mUseDefaultFont = true;
    mEnabled        = true;
585
586
587
    QString param;
    bool ok;
    mCategory               = CalEvent::status(event, &param);
Laurent Montel's avatar
Laurent Montel committed
588
    if (mCategory == CalEvent::DISPLAYING) {
589
        // It's a displaying calendar event - set values specific to displaying alarms
590
        const QStringList params = param.split(SC, Qt::KeepEmptyParts);
591
        int n = params.count();
Laurent Montel's avatar
Laurent Montel committed
592
        if (n) {
593
            const qlonglong id = params[0].toLongLong(&ok);
Laurent Montel's avatar
Laurent Montel committed
594
            if (ok) {
David Jarvie's avatar
David Jarvie committed
595
                mResourceId = id;    // original resource ID which contained the event
Laurent Montel's avatar
Laurent Montel committed
596
597
598
            }
            for (int i = 1;  i < n;  ++i) {
                if (params[i] == DISP_DEFER) {
599
                    mDisplayingDefer = true;
Laurent Montel's avatar
Laurent Montel committed
600
601
                }
                if (params[i] == DISP_EDIT) {
602
                    mDisplayingEdit = true;
Laurent Montel's avatar
Laurent Montel committed
603
                }
604
605
606
607
608
609
            }
        }
    }
    // Store the non-KAlarm custom properties of the event
    const QByteArray kalarmKey = "X-KDE-" + KACalendar::APPNAME + '-';
    mCustomProperties = event->customProperties();
Laurent Montel's avatar
Laurent Montel committed
610
611
    for (QMap<QByteArray, QString>::Iterator it = mCustomProperties.begin();  it != mCustomProperties.end();) {
        if (it.key().startsWith(kalarmKey)) {
612
            it = mCustomProperties.erase(it);
Laurent Montel's avatar
Laurent Montel committed
613
        } else {
614
            ++it;
Laurent Montel's avatar
Laurent Montel committed
615
        }
616
617
618
    }

    bool dateOnly = false;
619
    bool localZone = false;
620
    QStringList flags = event->customProperty(KACalendar::APPNAME, FLAGS_PROPERTY).split(SC, Qt::SkipEmptyParts);
621
    flags << QString() << QString();    // to avoid having to check for end of list
Laurent Montel's avatar
Laurent Montel committed
622
    for (int i = 0, end = flags.count() - 1;  i < end;  ++i) {
623
        QString flag = flags.at(i);
Laurent Montel's avatar
Laurent Montel committed
624
        if (flag == DATE_ONLY_FLAG) {
625
            dateOnly = true;
626
627
        } else if (flag == LOCAL_ZONE_FLAG) {
            localZone = true;
Laurent Montel's avatar
Laurent Montel committed
628
        } else if (flag == CONFIRM_ACK_FLAG) {
629
            mConfirmAck = true;
Laurent Montel's avatar
Laurent Montel committed
630
        } else if (flag == EMAIL_BCC_FLAG) {
631
            mEmailBcc = true;
Laurent Montel's avatar
Laurent Montel committed
632
        } else if (flag == KORGANIZER_FLAG) {
633
            mCopyToKOrganizer = true;
Laurent Montel's avatar
Laurent Montel committed
634
        } else if (flag == EXCLUDE_HOLIDAYS_FLAG) {
David Jarvie's avatar
David Jarvie committed
635
636
            mExcludeHolidays      = true;
            mExcludeHolidayRegion = holidays();
Laurent Montel's avatar
Laurent Montel committed
637
        } else if (flag == WORK_TIME_ONLY_FLAG) {
638
            mWorkTimeOnly = 1;
639
640
        } else if (flag == NOTIFY_FLAG) {
            mNotify = true;
641
642
        } else if (flag == KMAIL_ITEM_FLAG) {
            const Akonadi::Item::Id id = flags.at(i + 1).toLongLong(&ok);
Laurent Montel's avatar
Laurent Montel committed
643
            if (!ok) {
644
                continue;
Laurent Montel's avatar
Laurent Montel committed
645
            }
646
            mAkonadiItemId = id;
647
            ++i;
Laurent Montel's avatar
Laurent Montel committed
648
        } else if (flag == KAEventPrivate::ARCHIVE_FLAG) {
649
            mArchive = true;
Laurent Montel's avatar
Laurent Montel committed
650
        } else if (flag == KAEventPrivate::AT_LOGIN_TYPE) {
651
            mArchiveRepeatAtLogin = true;
Laurent Montel's avatar
Laurent Montel committed
652
        } else if (flag == KAEventPrivate::REMINDER_TYPE) {
653
            flag = flags.at(++i);
Laurent Montel's avatar
Laurent Montel committed
654
            if (flag == KAEventPrivate::REMINDER_ONCE_FLAG) {
655
                mReminderOnceOnly = true;
David Jarvie's avatar
David Jarvie committed
656
                flag = flags.at(++i);
657
            }
658
            const int len = flag.length() - 1;
659
            mReminderMinutes = -flag.leftRef(len).toInt();    // -> 0 if conversion fails
Laurent Montel's avatar
Laurent Montel committed
660
            switch (flag.at(len).toLatin1()) {
Laurent Montel's avatar
Laurent Montel committed
661
662
663
664
            case 'M':  break;
            case 'H':  mReminderMinutes *= 60;  break;
            case 'D':  mReminderMinutes *= 1440;  break;
            default:   mReminderMinutes = 0;  break;
665
            }
Laurent Montel's avatar
Laurent Montel committed
666
        } else if (flag == DEFER_FLAG) {
667
            QString mins = flags.at(i + 1);
Laurent Montel's avatar
Laurent Montel committed
668
            if (mins.endsWith(QLatin1Char('D'))) {
669
                mDeferDefaultDateOnly = true;
Laurent Montel's avatar
Laurent Montel committed
670
                mins.chop(1);
671
672
            }
            const int n = static_cast<int>(mins.toUInt(&ok));
Laurent Montel's avatar
Laurent Montel committed
673
            if (!ok) {
674
                continue;
Laurent Montel's avatar
Laurent Montel committed
675
            }
676
677
            mDeferDefaultMinutes = n;
            ++i;
Laurent Montel's avatar
Laurent Montel committed
678
        } else if (flag == TEMPL_AFTER_TIME_FLAG) {
679
            const int n = static_cast<int>(flags.at(i + 1).toUInt(&ok));
Laurent Montel's avatar
Laurent Montel committed
680
            if (!ok) {
681
                continue;
Laurent Montel's avatar
Laurent Montel committed
682
            }
683
684
            mTemplateAfterTime = n;
            ++i;
Laurent Montel's avatar
Laurent Montel committed
685
        } else if (flag == LATE_CANCEL_FLAG) {
686
            mLateCancel = static_cast<int>(flags.at(i + 1).toUInt(&ok));
Laurent Montel's avatar
Laurent Montel committed
687
            if (ok) {
688
                ++i;
Laurent Montel's avatar
Laurent Montel committed
689
690
            }
            if (!ok  ||  !mLateCancel) {
691
                mLateCancel = 1;    // invalid parameter defaults to 1 minute
Laurent Montel's avatar
Laurent Montel committed
692
693
            }
        } else if (flag == AUTO_CLOSE_FLAG) {
694
            mLateCancel = static_cast<int>(flags.at(i + 1).toUInt(&ok));
Laurent Montel's avatar
Laurent Montel committed
695
            if (ok) {
696
                ++i;
Laurent Montel's avatar
Laurent Montel committed
697
698
            }
            if (!ok  ||  !mLateCancel) {
699
                mLateCancel = 1;    // invalid parameter defaults to 1 minute
Laurent Montel's avatar
Laurent Montel committed
700
            }
701
702
703
704
705
            mAutoClose = true;
        }
    }

    QString prop = event->customProperty(KACalendar::APPNAME, LOG_PROPERTY);
Laurent Montel's avatar
Laurent Montel committed
706
707
    if (!prop.isEmpty()) {
        if (prop == xtermURL) {
708
            mCommandXterm = true;
Laurent Montel's avatar
Laurent Montel committed
709
        } else if (prop == displayURL) {
710
            mCommandDisplay = true;
Laurent Montel's avatar
Laurent Montel committed
711
        } else {
712
            mLogFile = prop;
Laurent Montel's avatar
Laurent Montel committed
713
        }
714
715
    }
    prop = event->customProperty(KACalendar::APPNAME, REPEAT_PROPERTY);
Laurent Montel's avatar
Laurent Montel committed
716
    if (!prop.isEmpty()) {
717
718
        // This property is used only when the main alarm has expired.
        // If a main alarm is found, this property is ignored (see below).
719
        const QStringList list = prop.split(QLatin1Char(':'));
Laurent Montel's avatar
Laurent Montel committed
720
        if (list.count() >= 2) {
721
722
            const int interval = static_cast<int>(list[0].toUInt());
            const int count = static_cast<int>(list[1].toUInt());
Laurent Montel's avatar
Laurent Montel committed
723
724
            if (interval && count) {
                if (interval % (24 * 60)) {
725
                    mRepetition.set(Duration(interval * 60, Duration::Seconds), count);
Laurent Montel's avatar
Laurent Montel committed
726
727
728
                } else {
                    mRepetition.set(Duration(interval / (24 * 60), Duration::Days), count);
                }
729
730
731
            }
        }
    }
732
    mNextMainDateTime = readDateTime(event, localZone, dateOnly, mStartDateTime);
David Jarvie's avatar
David Jarvie committed
733
    mCreatedDateTime = KADateTime(event->created());
Laurent Montel's avatar
Laurent Montel committed
734
    if (dateOnly  &&  !mRepetition.isDaily()) {
735
        mRepetition.set(Duration(mRepetition.intervalDays(), Duration::Days));
Laurent Montel's avatar
Laurent Montel committed
736
737
    }
    if (event->customStatus() == DISABLED_STATUS) {
738
        mEnabled = false;
Laurent Montel's avatar
Laurent Montel committed
739
    }
740
741
742

    // Extract status from the event's alarms.
    // First set up defaults.
743
744
    mActionSubType = KAEvent::MESSAGE;
    mMainExpired   = true;
745
746
747
748
749
750
751
752
753
754
755
756

    // 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;
Laurent Montel's avatar
Laurent Montel committed
757
758
    for (AlarmMap::ConstIterator it = alarmMap.constBegin();  it != alarmMap.constEnd();  ++it) {
        const AlarmData &data = it.value();
David Jarvie's avatar
David Jarvie committed
759
        const DateTime dateTime(data.alarm->hasStartOffset() ? data.alarm->startOffset().end(mNextMainDateTime.effectiveDateTime()) : data.alarm->time());
Laurent Montel's avatar
Laurent Montel committed
760
        switch (data.type) {
Laurent Montel's avatar
Laurent Montel committed
761
762
763
764
        case MAIN_ALARM:
            mMainExpired = false;
            alTime = dateTime;
            alTime.setDateOnly(mStartDateTime.isDateOnly());
765
            mRepetition.set(0, 0);   // ignore X-KDE-KALARM-REPEAT if main alarm exists
766
            if (data.alarm->repeatCount()  &&  !data.alarm->snoozeTime().isNull()) {
Laurent Montel's avatar
Laurent Montel committed
767
768
769
770
                mRepetition.set(data.alarm->snoozeTime(), data.alarm->repeatCount());   // values may be adjusted in setRecurrence()
                mNextRepeat = data.nextRepeat;
            }
            if (data.action != KAAlarm::AUDIO) {
771
                break;
Laurent Montel's avatar
Laurent Montel committed
772
            }
Laurent Montel's avatar
Laurent Montel committed
773
774
            // Fall through to AUDIO_ALARM
            Q_FALLTHROUGH();
Laurent Montel's avatar
Laurent Montel committed
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
        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;
798
799
800
                    }
                }
            }
Laurent Montel's avatar
Laurent Montel committed
801
802
803
804
            break;
        case DEFERRED_REMINDER_ALARM:
        case DEFERRED_ALARM:
            mDeferral = (data.type == DEFERRED_REMINDER_ALARM) ? REMINDER_DEFERRAL : NORMAL_DEFERRAL;
805
806
            if (data.timedDeferral) {
                // Don't use start-of-day time for applying timed deferral alarm offset
David Jarvie's avatar
David Jarvie committed
807
808
                mDeferralTime = DateTime(data.alarm->hasStartOffset() ? data.alarm->startOffset().end(mNextMainDateTime.calendarDateTime()) : data.alarm->time());
            } else {
809
                mDeferralTime = dateTime;
Laurent Montel's avatar
Laurent Montel committed
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
                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;
836
837
838
        }

        bool noSetNextTime = false;
Laurent Montel's avatar
Laurent Montel committed
839
        switch (data.type) {
Laurent Montel's avatar
Laurent Montel committed
840
841
842
843
844
845
846
847
        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;
            }
Laurent Montel's avatar
Laurent Montel committed
848
849
            // fall through to REMINDER_ALARM
            Q_FALLTHROUGH();
Laurent Montel's avatar
Laurent Montel committed
850
851
852
853
854
855
        case REMINDER_ALARM:
        case AT_LOGIN_ALARM:
        case DISPLAYING_ALARM:
            if (!set  &&  !noSetNextTime) {
                mNextMainDateTime = alTime;
            }
Laurent Montel's avatar
Laurent Montel committed
856
857
            // fall through to MAIN_ALARM
            Q_FALLTHROUGH();
Laurent Montel's avatar
Laurent Montel committed
858
859
860
861
862
863
864
865
866
        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;
867
868
869
                    if (data.extraActionOptions & KAEvent::DontShowPreActError) {
                        mCommandHideError = true;
                    }
Laurent Montel's avatar
Laurent Montel committed
870
871
                    if (!mCommandDisplay) {
                        break;
Laurent Montel's avatar
Laurent Montel committed
872
                    }
Laurent Montel's avatar
Laurent Montel committed
873
874
                    // fall through to MESSAGE
                    Q_FALLTHROUGH();
Laurent Montel's avatar
Laurent Montel committed
875
876
877
878
879
880
                case KAAlarm::MESSAGE:
                    mFont           = data.font;
                    mUseDefaultFont = data.defaultFont;
                    if (data.isEmailText) {
                        isEmailText = true;
                    }
Laurent Montel's avatar
Laurent Montel committed
881
882
                    // fall through to FILE
                    Q_FALLTHROUGH();
Laurent Montel's avatar
Laurent Montel committed
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
                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;
899
                }
Laurent Montel's avatar
Laurent Montel committed
900
901
902
903
904
905
906
907
908
909
910
911
912
                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;
913
914
        }
    }
Laurent Montel's avatar
Laurent Montel committed
915
    if (!isEmailText) {
916
        mAkonadiItemId = -1;
Laurent Montel's avatar
Laurent Montel committed
917
    }
918

Laurent Montel's avatar
Laurent Montel committed
919
920
    Recurrence *recur = event->recurrence();
    if (recur  &&  recur->recurs()) {
921
922
        const int nextRepeat = mNextRepeat;    // setRecurrence() clears mNextRepeat
        setRecurrence(*recur);
Laurent Montel's avatar
Laurent Montel committed
923
        if (nextRepeat <= mRepetition.count()) {
924
            mNextRepeat = nextRepeat;
Laurent Montel's avatar
Laurent Montel committed
925
926
        }
    } else if (mRepetition) {
927
        // Convert a repetition with no recurrence into a recurrence
Laurent Montel's avatar
Laurent Montel committed
928
        if (mRepetition.isDaily()) {
929
            setRecur(RecurrenceRule::rDaily, mRepetition.intervalDays(), mRepetition.count() + 1, QDate());
Laurent Montel's avatar
Laurent Montel committed
930
        } else {
David Jarvie's avatar
David Jarvie committed
931
            setRecur(RecurrenceRule::rMinutely, mRepetition.intervalMinutes(), mRepetition.count() + 1, KADateTime());
Laurent Montel's avatar
Laurent Montel committed
932
        }
933
        mRepetition.set(0, 0);
934
        mTriggerChanged = true;
935
936
    }

Laurent Montel's avatar
Laurent Montel committed
937
    if (mRepeatAtLogin) {
938
        mArchiveRepeatAtLogin = false;
Laurent Montel's avatar
Laurent Montel committed
939
        if (mReminderMinutes > 0) {
940
941
942
943
944
945
            mReminderMinutes = 0;      // pre-alarm reminder not allowed for at-login alarm
            mReminderActive  = NO_REMINDER;
        }
        setRepeatAtLoginTrue(false);   // clear other incompatible statuses
    }

946
    if (mMainExpired  &&  !deferralOffset.isNull()  &&  checkRecur() != KARecurrence::NO_RECUR) {
947
948
949
950
        // 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());
Laurent Montel's avatar
Laurent Montel committed
951
        if (mDeferralTime.isDateOnly()) {
David Jarvie's avatar
David Jarvie committed
952
            mDeferralTime = DateTime(deferralOffset.end(dt.qDateTime()));
953
            mDeferralTime.setDateOnly(true);
Laurent Montel's avatar
Laurent Montel committed
954
        } else {
David Jarvie's avatar
David Jarvie committed
955
            mDeferralTime = DateTime(deferralOffset.end(dt.effectiveDateTime()));
Laurent Montel's avatar
Laurent Montel committed
956
        }
957
    }
Laurent Montel's avatar
Laurent Montel committed
958
959
    if (mDeferral != NO_DEFERRAL) {
        if (setDeferralTime) {
960
            mNextMainDateTime = mDeferralTime;
Laurent Montel's avatar
Laurent Montel committed
961
        }
962
963
964
965
966
    }
    mTriggerChanged = true;
    endChanges();
}

967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
KAEventPrivate::KAEventPrivate(const KAEventPrivate &other)
    : QSharedData(other)
    , mRecurrence(nullptr)
{
    copy(other);
}

KAEvent::KAEvent(const KAEvent &other)
    : d(other.d)
{ }

KAEvent::~KAEvent()
{ }

KAEvent &KAEvent::operator=(const KAEvent &other)
982
{
983
984
985
986
    if (&other != this) {
        d = other.d;
    }
    return *this;
987
988
989
}

/******************************************************************************
990
* Copies the data from another instance.
991
*/
992
void KAEventPrivate::copy(const KAEventPrivate &event)
993
{
994
995
996
997
998
999
1000
1001
1002
    mAllTrigger              = event.mAllTrigger;
    mMainTrigger             = event.mMainTrigger;
    mAllWorkTrigger          = event.mAllWorkTrigger;
    mMainWorkTrigger         = event.mMainWorkTrigger;
    mCommandError            = event.mCommandError;
    mEventID                 = event.mEventID;
    mCustomProperties        = event.mCustomProperties;
    mItemId                  = event.mItemId;
    mResourceId              = event.mResourceId;
David Jarvie's avatar
David Jarvie committed
1003
    mName                    = event.mName;
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
    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;
    mAkonadiItemId           = event.mAkonadiItemId;
    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;
    mExcludeHolidayRegion    = event.mExcludeHolidayRegion;
    mWorkTimeOnly            = event.mWorkTimeOnly;
    mActionSubType           = event.mActionSubType;
    mCategory                = event.mCategory;
    mExtraActionOptions      = event.mExtraActionOptions;
    mCompatibility           = event.mCompatibility;
    mReadOnly                = event.mReadOnly;
    mConfirmAck              = event.mConfirmAck;
    mUseDefaultFont          = event.mUseDefaultFont;
    mCommandScript           = event.mCommandScript;
    mCommandXterm            = event.mCommandXterm;
    mCommandDisplay          = event.mCommandDisplay;
1053
    mCommandHideError        = event.mCommandHideError;