KAlarmCal

kaevent.cpp
1 /*
2  * kaevent.cpp - represents KAlarm calendar events
3  * This file is part of kalarmcal library, which provides access to KAlarm
4  * calendar data.
5  * SPDX-FileCopyrightText: 2001-2021 David Jarvie <[email protected]>
6  *
7  * SPDX-License-Identifier: LGPL-2.0-or-later
8  */
9 
10 #include "kaevent.h"
11 
12 #include "akonadi.h" // for deprecated setItemPayload() only
13 #include "alarmtext.h"
14 #include "identities.h"
15 #include "version.h"
16 
17 #include <KCalendarCore/MemoryCalendar>
18 #include <KHolidays/Holiday>
19 #include <KHolidays/HolidayRegion>
20 
21 #include <klocalizedstring.h>
22 
23 #include "kalarmcal_debug.h"
24 
25 using namespace KCalendarCore;
26 using namespace KHolidays;
27 
28 namespace KAlarmCal
29 {
30 
31 //=============================================================================
32 
33 using EmailAddress = KCalendarCore::Person;
34 class EmailAddressList : public KCalendarCore::Person::List
35 {
36 public:
37  EmailAddressList() : KCalendarCore::Person::List() { }
38  EmailAddressList(const KCalendarCore::Person::List &list)
39  {
40  operator=(list);
41  }
42  EmailAddressList &operator=(const KCalendarCore::Person::List &);
43  operator QStringList() const;
44  QString join(const QString &separator) const;
45  QStringList pureAddresses() const;
46  QString pureAddresses(const QString &separator) const;
47 private:
48  QString address(int index) const;
49 };
50 
51 //=============================================================================
52 
53 class Q_DECL_HIDDEN KAAlarm::Private
54 {
55 public:
56  Private();
57 
58  Action mActionType; // alarm action type
59  Type mType{INVALID_ALARM}; // alarm type
60  DateTime mNextMainDateTime; // next time to display the alarm, excluding repetitions
61  Repetition mRepetition; // sub-repetition count and interval
62  int mNextRepeat{0}; // repetition count of next due sub-repetition
63  bool mRepeatAtLogin{false}; // whether to repeat the alarm at every login
64  bool mRecurs; // there is a recurrence rule for the alarm
65  bool mDeferred{false}; // whether the alarm is an extra deferred/deferred-reminder alarm
66  bool mTimedDeferral; // if mDeferred = true: true if the deferral is timed, false if date-only
67 };
68 
69 //=============================================================================
70 
71 class Q_DECL_HIDDEN KAEventPrivate : public QSharedData
72 {
73 public:
74  // Read-only internal flags additional to KAEvent::Flags enum values.
75  // NOTE: If any values are added to those in KAEvent::Flags, ensure
76  // that these values don't overlap them.
77  enum {
78  REMINDER = 0x100000,
79  DEFERRAL = 0x200000,
80  TIMED_FLAG = 0x400000,
81  DATE_DEFERRAL = DEFERRAL,
82  TIME_DEFERRAL = DEFERRAL | TIMED_FLAG,
83  DISPLAYING_ = 0x800000,
84  READ_ONLY_FLAGS = 0xF00000 //!< mask for all read-only internal values
85  };
86  enum ReminderType { // current active state of reminder
87  NO_REMINDER, // reminder is not due
88  ACTIVE_REMINDER, // reminder is due
89  HIDDEN_REMINDER // reminder-after is disabled due to main alarm being deferred past it
90  };
91  enum DeferType {
92  NO_DEFERRAL = 0, // there is no deferred alarm
93  NORMAL_DEFERRAL, // the main alarm, a recurrence or a repeat is deferred
94  REMINDER_DEFERRAL // a reminder alarm is deferred
95  };
96  // Alarm types.
97  // This uses the same scheme as KAAlarm::Type, with some extra values.
98  // Note that the actual enum values need not be the same as in KAAlarm::Type.
99  enum AlarmType {
100  INVALID_ALARM = 0, // Not an alarm
101  MAIN_ALARM = 1, // THE real alarm. Must be the first in the enumeration.
102  REMINDER_ALARM = 0x02, // Reminder in advance of/after the main alarm
103  DEFERRED_ALARM = 0x04, // Deferred alarm
104  DEFERRED_REMINDER_ALARM = REMINDER_ALARM | DEFERRED_ALARM, // Deferred reminder alarm
105  // The following values must be greater than the preceding ones, to
106  // ensure that in ordered processing they are processed afterwards.
107  AT_LOGIN_ALARM = 0x10, // Additional repeat-at-login trigger
108  DISPLAYING_ALARM = 0x20, // Copy of the alarm currently being displayed
109  // The following are extra internal KAEvent values
110  AUDIO_ALARM = 0x30, // sound to play when displaying the alarm
111  PRE_ACTION_ALARM = 0x40, // command to execute before displaying the alarm
112  POST_ACTION_ALARM = 0x50 // command to execute after the alarm window is closed
113  };
114 
115  struct AlarmData {
116  Alarm::Ptr alarm;
117  QString cleanText; // text or audio file name
118  QFont font;
119  QColor bgColour, fgColour;
120  float soundVolume;
121  float fadeVolume;
122  int fadeSeconds;
123  int repeatSoundPause;
124  int nextRepeat;
125  uint emailFromId;
126  KAEventPrivate::AlarmType type;
127  KAAlarm::Action action;
128  int displayingFlags;
129  KAEvent::ExtraActionOptions extraActionOptions;
130  bool speak;
131  bool defaultFont;
132  bool isEmailText;
133  bool commandScript;
134  bool timedDeferral;
135  bool hiddenReminder;
136  };
137  using AlarmMap = QMap<AlarmType, AlarmData>;
138 
139  KAEventPrivate();
140  KAEventPrivate(const KADateTime &, const QString &name, const QString &message,
141  const QColor &bg, const QColor &fg, const QFont &f,
142  KAEvent::SubAction, int lateCancel, KAEvent::Flags flags,
143  bool changesPending = false);
144  explicit KAEventPrivate(const KCalendarCore::Event::Ptr &);
145  KAEventPrivate(const KAEventPrivate &);
146  ~KAEventPrivate()
147  {
148  delete mRecurrence;
149  }
150  KAEventPrivate &operator=(const KAEventPrivate &e)
151  {
152  if (&e != this) {
153  copy(e);
154  }
155  return *this;
156  }
157  void setAudioFile(const QString &filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile);
158  KAEvent::OccurType setNextOccurrence(const KADateTime &preDateTime);
159  void setFirstRecurrence();
160  void setCategory(CalEvent::Type);
161  void setRepeatAtLogin(bool);
162  void setRepeatAtLoginTrue(bool clearReminder);
163  void setReminder(int minutes, bool onceOnly);
164  void activateReminderAfter(const DateTime &mainAlarmTime);
165  void defer(const DateTime &, bool reminder, bool adjustRecurrence = false);
166  void cancelDefer();
167  bool setDisplaying(const KAEventPrivate &, KAAlarm::Type, ResourceId, const KADateTime &dt, bool showEdit, bool showDefer);
168  void reinstateFromDisplaying(const KCalendarCore::Event::Ptr &, ResourceId &, bool &showEdit, bool &showDefer);
169  void startChanges()
170  {
171  ++mChangeCount;
172  }
173  void endChanges();
174  void removeExpiredAlarm(KAAlarm::Type);
175  bool compare(const KAEventPrivate&, KAEvent::Comparison) const;
176  KAAlarm alarm(KAAlarm::Type) const;
177  KAAlarm firstAlarm() const;
178  KAAlarm nextAlarm(KAAlarm::Type) const;
179  bool updateKCalEvent(const KCalendarCore::Event::Ptr &, KAEvent::UidAction, bool setCustomProperties = true) const;
180  DateTime mainDateTime(bool withRepeats = false) const
181  {
182  return (withRepeats && mNextRepeat && mRepetition)
183  ? DateTime(mRepetition.duration(mNextRepeat).end(mNextMainDateTime.qDateTime())) : mNextMainDateTime;
184  }
185  DateTime mainEndRepeatTime() const
186  {
187  return mRepetition ? DateTime(mRepetition.duration().end(mNextMainDateTime.qDateTime())) : mNextMainDateTime;
188  }
189  DateTime deferralLimit(KAEvent::DeferLimitType * = nullptr) const;
190  KAEvent::Flags flags() const;
191  bool excludedByWorkTimeOrHoliday(const KADateTime &dt) const;
192  bool setRepetition(const Repetition &);
193  bool occursAfter(const KADateTime &preDateTime, bool includeRepetitions) const;
194  KAEvent::OccurType nextOccurrence(const KADateTime &preDateTime, DateTime &result, KAEvent::OccurOption = KAEvent::IGNORE_REPETITION) const;
195  KAEvent::OccurType previousOccurrence(const KADateTime &afterDateTime, DateTime &result, bool includeRepetitions = false) const;
196  void setRecurrence(const KARecurrence &);
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);
199  KARecurrence::Type checkRecur() const;
200  void clearRecur();
201  void calcTriggerTimes() const;
202 #ifdef KDE_NO_DEBUG_OUTPUT
203  void dumpDebug() const { }
204 #else
205  void dumpDebug() const;
206 #endif
207  static bool convertRepetition(const KCalendarCore::Event::Ptr &);
208  static bool convertStartOfDay(const KCalendarCore::Event::Ptr &);
209  static DateTime readDateTime(const KCalendarCore::Event::Ptr &, bool localZone, bool dateOnly, DateTime &start);
210  static void readAlarms(const KCalendarCore::Event::Ptr &, AlarmMap *, bool cmdDisplay = false);
211  static void readAlarm(const KCalendarCore::Alarm::Ptr &, AlarmData &, bool audioMain, bool cmdDisplay = false);
212  static QSharedPointer<const HolidayRegion> holidays();
213 private:
214  void copy(const KAEventPrivate &);
215  bool mayOccurDailyDuringWork(const KADateTime &) const;
216  int nextWorkRepetition(const KADateTime &pre) const;
217  void calcNextWorkingTime(const DateTime &nextTrigger) const;
218  DateTime nextWorkingTime() const;
219  KAEvent::OccurType nextRecurrence(const KADateTime &preDateTime, DateTime &result) const;
220  void setAudioAlarm(const KCalendarCore::Alarm::Ptr &) const;
221  KCalendarCore::Alarm::Ptr initKCalAlarm(const KCalendarCore::Event::Ptr &, const DateTime &, const QStringList &types, AlarmType = INVALID_ALARM) const;
222  KCalendarCore::Alarm::Ptr initKCalAlarm(const KCalendarCore::Event::Ptr &, int startOffsetSecs, const QStringList &types, AlarmType = INVALID_ALARM) const;
223  inline void set_deferral(DeferType);
224  inline void activate_reminder(bool activate);
225  static int transitionIndex(const QDateTime &utc, const QTimeZone::OffsetDataList& transitions);
226 
227 public:
228  static QFont mDefaultFont; // default alarm message font
229  static QSharedPointer<const HolidayRegion> mHolidays; // holiday region to use
230  static QBitArray mWorkDays; // working days of the week
231  static QTime mWorkDayStart; // start time of the working day
232  static QTime mWorkDayEnd; // end time of the working day
233  static int mWorkTimeIndex; // incremented every time working days/times are changed
234  mutable DateTime mAllTrigger; // next trigger time, including reminders, ignoring working hours
235  mutable DateTime mMainTrigger; // next trigger time, ignoring reminders and working hours
236  mutable DateTime mAllWorkTrigger; // next trigger time, taking account of reminders and working hours
237  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
239 
240  QString mEventID; // UID: KCalendarCore::Event unique ID
241  QMap<QByteArray, QString> mCustomProperties; // KCalendarCore::Event's non-KAlarm custom properties
242  Akonadi::Item::Id mItemId{-1}; // Akonadi::Item ID for this event
243  mutable ResourceId mResourceId{-1}; // ID of resource containing the event, or for a displaying event,
244  // saved resource ID (not the resource the event is in)
245  QString mName; // name of the alarm
246  QString mText; // message text, file URL, command, email body [or audio file for KAAlarm]
247  QString mAudioFile; // ATTACH: audio file to play
248  QString mPreAction; // command to execute before alarm is displayed
249  QString mPostAction; // command to execute after alarm window is closed
250  DateTime mStartDateTime; // DTSTART and DTEND: start and end time for event
251  KADateTime mCreatedDateTime; // CREATED: date event was created, or saved in archive calendar
252  DateTime mNextMainDateTime; // next time to display the alarm, excluding repetitions
253  KADateTime mAtLoginDateTime; // repeat-at-login end time
254  DateTime mDeferralTime; // extra time to trigger alarm (if alarm or reminder deferred)
255  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)
258  DateTime mReminderAfterTime; // if mReminderActive true, time to trigger reminder AFTER the main alarm, or invalid if not pending
259  ReminderType mReminderActive{NO_REMINDER}; // whether a reminder is due (before next, or after last, main alarm/recurrence)
260  int mDeferDefaultMinutes{0}; // default number of minutes for deferral dialog, or 0 to select time control
261  bool mDeferDefaultDateOnly{false}; // select date-only by default in deferral dialog
262  int mRevision{0}; // SEQUENCE: revision number of the original alarm, or 0
263  KARecurrence *mRecurrence{nullptr}; // RECUR: recurrence specification, or 0 if none
264  Repetition mRepetition; // sub-repetition count and interval
265  int mNextRepeat{0}; // repetition count of next due sub-repetition
266  int mAlarmCount{0}; // number of alarms: count of !mMainExpired, mRepeatAtLogin, mDeferral, mReminderActive, mDisplaying
267  DeferType mDeferral{NO_DEFERRAL}; // whether the alarm is an extra deferred/deferred-reminder alarm
268  Akonadi::Item::Id mAkonadiItemId{-1}; // if email text, message's Akonadi item ID
269  int mTemplateAfterTime{-1}; // time not specified: use n minutes after default time, or -1 (applies to templates only)
270  QColor mBgColour; // background colour of alarm message
271  QColor mFgColour; // foreground colour of alarm message, or invalid for default
272  QFont mFont; // font of alarm message (ignored if mUseDefaultFont true)
273  uint mEmailFromIdentity{0}; // standard email identity uoid for 'From' field, or empty
274  EmailAddressList mEmailAddresses; // ATTENDEE: addresses to send email to
275  QString mEmailSubject; // SUMMARY: subject line of email
276  QStringList mEmailAttachments; // ATTACH: email attachment file names
277  mutable int mChangeCount{0}; // >0 = inhibit calling calcTriggerTimes()
278  mutable bool mTriggerChanged{false}; // true if need to recalculate trigger times
279  QString mLogFile; // alarm output is to be logged to this URL
280  float mSoundVolume{-1.0f}; // volume for sound file (range 0 - 1), or < 0 for unspecified
281  float mFadeVolume{-1.0f}; // initial volume for sound file (range 0 - 1), or < 0 for no fade
282  int mFadeSeconds{0}; // fade time (seconds) for sound file, or 0 if none
283  int mRepeatSoundPause{-1}; // seconds to pause between sound file repetitions, or -1 if no repetition
284  int mLateCancel{0}; // how many minutes late will cancel the alarm, or 0 for no cancellation
285  bool mExcludeHolidays{false}; // don't trigger alarms on holidays
286  mutable QSharedPointer<const HolidayRegion> mExcludeHolidayRegion; // holiday region used to exclude alarms on holidays (= mHolidays when trigger calculated)
287  mutable int mWorkTimeOnly{0}; // non-zero to trigger alarm only during working hours (= mWorkTimeIndex when trigger calculated)
288  KAEvent::SubAction mActionSubType; // sub-action type for the event's main alarm
289  CalEvent::Type mCategory{CalEvent::EMPTY}; // event category (active, archived, template, ...)
290  KAEvent::ExtraActionOptions mExtraActionOptions; // options for pre- or post-alarm actions
291  KACalendar::Compat mCompatibility{KACalendar::Current}; // event's storage format compatibility
292  bool mReadOnly{false}; // event is read-only in its original calendar file
293  bool mConfirmAck{false}; // alarm acknowledgement requires confirmation by user
294  bool mUseDefaultFont; // use default message font, not mFont
295  bool mCommandScript{false}; // the command text is a script, not a shell command line
296  bool mCommandXterm{false}; // command alarm is to be executed in a terminal window
297  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  bool mEmailBcc{false}; // blind copy the email to the user
300  bool mBeep{false}; // whether to beep when the alarm is displayed
301  bool mSpeak{false}; // whether to speak the message when the alarm is displayed
302  bool mCopyToKOrganizer{false}; // KOrganizer should hold a copy of the event
303  bool mReminderOnceOnly{false}; // the reminder is output only for the first recurrence
304  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  bool mMainExpired; // main alarm has expired (in which case a deferral alarm will exist)
307  bool mRepeatAtLogin{false}; // whether to repeat the alarm at every login
308  bool mArchiveRepeatAtLogin{false}; // if now archived, original event was repeat-at-login
309  bool mArchive{false}; // event has triggered in the past, so archive it when closed
310  bool mDisplaying{false}; // whether the alarm is currently being displayed (i.e. in displaying calendar)
311  bool mDisplayingDefer{false}; // show Defer button (applies to displaying calendar only)
312  bool mDisplayingEdit{false}; // show Edit button (applies to displaying calendar only)
313  bool mEnabled; // false if event is disabled
314 
315 public:
316  static const QByteArray FLAGS_PROPERTY;
317  static const QString DATE_ONLY_FLAG;
318  static const QString LOCAL_ZONE_FLAG;
319  static const QString EMAIL_BCC_FLAG;
320  static const QString CONFIRM_ACK_FLAG;
321  static const QString KORGANIZER_FLAG;
322  static const QString EXCLUDE_HOLIDAYS_FLAG;
323  static const QString WORK_TIME_ONLY_FLAG;
324  static const QString REMINDER_ONCE_FLAG;
325  static const QString DEFER_FLAG;
326  static const QString LATE_CANCEL_FLAG;
327  static const QString AUTO_CLOSE_FLAG;
328  static const QString NOTIFY_FLAG;
329  static const QString TEMPL_AFTER_TIME_FLAG;
330  static const QString KMAIL_ITEM_FLAG;
331  static const QString ARCHIVE_FLAG;
332  static const QByteArray NEXT_RECUR_PROPERTY;
333  static const QByteArray REPEAT_PROPERTY;
334  static const QByteArray LOG_PROPERTY;
335  static const QString xtermURL;
336  static const QString displayURL;
337  static const QByteArray TYPE_PROPERTY;
338  static const QString FILE_TYPE;
339  static const QString AT_LOGIN_TYPE;
340  static const QString REMINDER_TYPE;
341  static const QString REMINDER_ONCE_TYPE;
342  static const QString TIME_DEFERRAL_TYPE;
343  static const QString DATE_DEFERRAL_TYPE;
344  static const QString DISPLAYING_TYPE;
345  static const QString PRE_ACTION_TYPE;
346  static const QString POST_ACTION_TYPE;
347  static const QString SOUND_REPEAT_TYPE;
348  static const QByteArray NEXT_REPEAT_PROPERTY;
349  static const QString HIDDEN_REMINDER_FLAG;
350  static const QByteArray FONT_COLOUR_PROPERTY;
351  static const QByteArray VOLUME_PROPERTY;
352  static const QString EMAIL_ID_FLAG;
353  static const QString SPEAK_FLAG;
354  static const QString EXEC_ON_DEFERRAL_FLAG;
355  static const QString CANCEL_ON_ERROR_FLAG;
356  static const QString DONT_SHOW_ERROR_FLAG;
357  static const QString DISABLED_STATUS;
358  static const QString DISP_DEFER;
359  static const QString DISP_EDIT;
360  static const QString CMD_ERROR_VALUE;
361  static const QString CMD_ERROR_PRE_VALUE;
362  static const QString CMD_ERROR_POST_VALUE;
363  static const QString SC;
364 };
365 
366 //=============================================================================
367 
368 // KAlarm version which first used the current calendar/event format.
369 // If this changes, KAEvent::convertKCalEvents() must be changed correspondingly.
370 // The string version is the KAlarm version string used in the calendar file.
371 
372 QByteArray KAEvent::currentCalendarVersionString()
373 {
374  return {"2.7.0"}; // This is NOT the KAlarmCal library .so version!
375 }
376 int KAEvent::currentCalendarVersion()
377 {
378  return Version(2, 7, 0); // This is NOT the KAlarmCal library .so version!
379 }
380 
381 // Custom calendar properties.
382 // Note that all custom property names are prefixed with X-KDE-KALARM- in the calendar file.
383 
384 // 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 const QString KAEventPrivate::EMAIL_BCC_FLAG = QStringLiteral("BCC");
389 const QString KAEventPrivate::CONFIRM_ACK_FLAG = QStringLiteral("ACKCONF");
390 const QString KAEventPrivate::KORGANIZER_FLAG = QStringLiteral("KORG");
391 const QString KAEventPrivate::EXCLUDE_HOLIDAYS_FLAG = QStringLiteral("EXHOLIDAYS");
392 const QString KAEventPrivate::WORK_TIME_ONLY_FLAG = QStringLiteral("WORKTIME");
393 const QString KAEventPrivate::REMINDER_ONCE_FLAG = QStringLiteral("ONCE");
394 const QString KAEventPrivate::DEFER_FLAG = QStringLiteral("DEFER"); // default defer interval for this alarm
395 const QString KAEventPrivate::LATE_CANCEL_FLAG = QStringLiteral("LATECANCEL");
396 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 const QByteArray KAEventPrivate::NEXT_RECUR_PROPERTY("NEXTRECUR"); // X-KDE-KALARM-NEXTRECUR property
403 const QByteArray KAEventPrivate::REPEAT_PROPERTY("REPEAT"); // X-KDE-KALARM-REPEAT property
404 const QByteArray KAEventPrivate::LOG_PROPERTY("LOG"); // X-KDE-KALARM-LOG property
405 const QString KAEventPrivate::xtermURL = QStringLiteral("xterm:");
406 const QString KAEventPrivate::displayURL = QStringLiteral("display:");
407 
408 // - General alarm properties
409 const QByteArray KAEventPrivate::TYPE_PROPERTY("TYPE"); // X-KDE-KALARM-TYPE property
410 const QString KAEventPrivate::FILE_TYPE = QStringLiteral("FILE");
411 const QString KAEventPrivate::AT_LOGIN_TYPE = QStringLiteral("LOGIN");
412 const QString KAEventPrivate::REMINDER_TYPE = QStringLiteral("REMINDER");
413 const QString KAEventPrivate::TIME_DEFERRAL_TYPE = QStringLiteral("DEFERRAL");
414 const QString KAEventPrivate::DATE_DEFERRAL_TYPE = QStringLiteral("DATE_DEFERRAL");
415 const QString KAEventPrivate::DISPLAYING_TYPE = QStringLiteral("DISPLAYING"); // used only in displaying calendar
416 const QString KAEventPrivate::PRE_ACTION_TYPE = QStringLiteral("PRE");
417 const QString KAEventPrivate::POST_ACTION_TYPE = QStringLiteral("POST");
418 const QString KAEventPrivate::SOUND_REPEAT_TYPE = QStringLiteral("SOUNDREPEAT");
419 const QByteArray KAEventPrivate::NEXT_REPEAT_PROPERTY("NEXTREPEAT"); // X-KDE-KALARM-NEXTREPEAT property
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
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
427 const QString KAEventPrivate::SPEAK_FLAG = QStringLiteral("SPEAK");
428 // - Command alarm properties
429 const QString KAEventPrivate::EXEC_ON_DEFERRAL_FLAG = QStringLiteral("EXECDEFER");
430 const QString KAEventPrivate::CANCEL_ON_ERROR_FLAG = QStringLiteral("ERRCANCEL");
431 const QString KAEventPrivate::DONT_SHOW_ERROR_FLAG = QStringLiteral("ERRNOSHOW");
432 
433 // Event status strings
434 const QString KAEventPrivate::DISABLED_STATUS = QStringLiteral("DISABLED");
435 
436 // Displaying event ID identifier
437 const QString KAEventPrivate::DISP_DEFER = QStringLiteral("DEFER");
438 const QString KAEventPrivate::DISP_EDIT = QStringLiteral("EDIT");
439 
440 // Command error strings
441 const QString KAEventPrivate::CMD_ERROR_VALUE = QStringLiteral("MAIN");
442 const QString KAEventPrivate::CMD_ERROR_PRE_VALUE = QStringLiteral("PRE");
443 const QString KAEventPrivate::CMD_ERROR_POST_VALUE = QStringLiteral("POST");
444 
445 const QString KAEventPrivate::SC = QStringLiteral(";");
446 
447 QFont KAEventPrivate::mDefaultFont;
448 QSharedPointer<const HolidayRegion> KAEventPrivate::mHolidays;
449 QBitArray KAEventPrivate::mWorkDays(7);
450 QTime KAEventPrivate::mWorkDayStart(9, 0, 0);
451 QTime KAEventPrivate::mWorkDayEnd(17, 0, 0);
452 int KAEventPrivate::mWorkTimeIndex = 1;
453 
454 static void setProcedureAlarm(const Alarm::Ptr &, const QString &commandLine);
455 static QString reminderToString(int minutes);
456 
457 /*=============================================================================
458 = Class KAEvent
459 = Corresponds to a KCalendarCore::Event instance.
460 =============================================================================*/
461 
462 inline void KAEventPrivate::activate_reminder(bool activate)
463 {
464  if (activate && mReminderActive != ACTIVE_REMINDER && mReminderMinutes) {
465  if (mReminderActive == NO_REMINDER) {
466  ++mAlarmCount;
467  }
468  mReminderActive = ACTIVE_REMINDER;
469  } else if (!activate && mReminderActive != NO_REMINDER) {
470  mReminderActive = NO_REMINDER;
471  mReminderAfterTime = DateTime();
472  --mAlarmCount;
473  }
474 }
475 
476 Q_GLOBAL_STATIC_WITH_ARGS(QSharedDataPointer<KAEventPrivate>,
477  emptyKAEventPrivate, (new KAEventPrivate))
478 
479 KAEvent::KAEvent()
480  : d(*emptyKAEventPrivate)
481 { }
482 
483 KAEventPrivate::KAEventPrivate() = default;
484 
485 /******************************************************************************
486 * Initialise the instance with the specified parameters.
487 */
488 KAEvent::KAEvent(const KADateTime &dt, const QString &name, const QString &message,
489  const QColor &bg, const QColor &fg, const QFont &f,
490  SubAction action, int lateCancel, Flags flags, bool changesPending)
491  : d(new KAEventPrivate(dt, name, message, bg, fg, f, action, lateCancel, flags, changesPending))
492 {
493 }
494 
496  const QColor &bg, const QColor &fg, const QFont &f,
497  SubAction action, int lateCancel, Flags flags, bool changesPending)
498  : d(new KAEventPrivate(dt, QString(), message, bg, fg, f, action, lateCancel, flags, changesPending))
499 {
500 }
501 
502 KAEventPrivate::KAEventPrivate(const KADateTime &dateTime, const QString &name, const QString &text,
503  const QColor &bg, const QColor &fg, const QFont &font,
505  bool changesPending)
506  : mName(name)
507  , mAlarmCount(1)
508  , mBgColour(bg)
509  , mFgColour(fg)
510  , mFont(font)
511  , mLateCancel(lateCancel) // do this before setting flags
512  , mCategory(CalEvent::ACTIVE)
513 {
514  mStartDateTime = dateTime;
515  if (flags & KAEvent::ANY_TIME) {
516  mStartDateTime.setDateOnly(true);
517  }
518  mNextMainDateTime = mStartDateTime;
519  switch (action) {
520  case KAEvent::MESSAGE:
521  case KAEvent::FILE:
522  case KAEvent::COMMAND:
523  case KAEvent::EMAIL:
524  case KAEvent::AUDIO:
525  mActionSubType = static_cast<KAEvent::SubAction>(action);
526  break;
527  default:
528  mActionSubType = KAEvent::MESSAGE;
529  break;
530  }
531  mText = (mActionSubType == KAEvent::COMMAND) ? text.trimmed()
532  : (mActionSubType == KAEvent::AUDIO) ? QString() : text;
533  mAudioFile = (mActionSubType == KAEvent::AUDIO) ? text : QString();
534  set_deferral((flags & DEFERRAL) ? NORMAL_DEFERRAL : NO_DEFERRAL);
535  mRepeatAtLogin = flags & KAEvent::REPEAT_AT_LOGIN;
536  mConfirmAck = flags & KAEvent::CONFIRM_ACK;
537  mUseDefaultFont = flags & KAEvent::DEFAULT_FONT;
538  mCommandScript = flags & KAEvent::SCRIPT;
539  mCommandXterm = flags & KAEvent::EXEC_IN_XTERM;
540  mCommandDisplay = flags & KAEvent::DISPLAY_COMMAND;
541  mCommandHideError = flags & KAEvent::DONT_SHOW_ERROR;
542  mCopyToKOrganizer = flags & KAEvent::COPY_KORGANIZER;
543  mExcludeHolidays = flags & KAEvent::EXCL_HOLIDAYS;
544  mExcludeHolidayRegion = holidays();
545  mWorkTimeOnly = flags & KAEvent::WORK_TIME_ONLY;
546  mEmailBcc = flags & KAEvent::EMAIL_BCC;
547  mEnabled = !(flags & KAEvent::DISABLED);
548  mDisplaying = flags & DISPLAYING_;
549  mReminderOnceOnly = flags & KAEvent::REMINDER_ONCE;
550  mAutoClose = (flags & KAEvent::AUTO_CLOSE) && mLateCancel;
551  mNotify = flags & KAEvent::NOTIFY;
552  mRepeatSoundPause = (flags & KAEvent::REPEAT_SOUND) ? 0 : -1;
553  mSpeak = (flags & KAEvent::SPEAK) && action != KAEvent::AUDIO;
554  mBeep = (flags & KAEvent::BEEP) && action != KAEvent::AUDIO && !mSpeak;
555  if (mRepeatAtLogin) { // do this after setting other flags
556  ++mAlarmCount;
557  setRepeatAtLoginTrue(false);
558  }
559 
560  mMainExpired = false;
561  mChangeCount = changesPending ? 1 : 0;
562  mTriggerChanged = true;
563 }
564 
565 /******************************************************************************
566 * Initialise the KAEvent from a KCalendarCore::Event.
567 */
569  : d(new KAEventPrivate(event))
570 {
571 }
572 
573 KAEventPrivate::KAEventPrivate(const KCalendarCore::Event::Ptr &event)
574 {
575  startChanges();
576  // Extract status from the event
577  mEventID = event->uid();
578  mRevision = event->revision();
579  mName = event->summary();
580  mBgColour = QColor(255, 255, 255); // missing/invalid colour - return white background
581  mFgColour = QColor(0, 0, 0); // and black foreground
582  mReadOnly = event->isReadOnly();
583  mUseDefaultFont = true;
584  mEnabled = true;
585  QString param;
586  bool ok;
587  mCategory = CalEvent::status(event, &param);
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();
592  if (n) {
593  const qlonglong id = params[0].toLongLong(&ok);
594  if (ok) {
595  mResourceId = id; // original resource ID which contained the event
596  }
597  for (int i = 1; i < n; ++i) {
598  if (params[i] == DISP_DEFER) {
599  mDisplayingDefer = true;
600  }
601  if (params[i] == DISP_EDIT) {
602  mDisplayingEdit = true;
603  }
604  }
605  }
606  }
607  // Store the non-KAlarm custom properties of the event
608  const QByteArray kalarmKey = "X-KDE-" + KACalendar::APPNAME + '-';
609  mCustomProperties = event->customProperties();
610  for (QMap<QByteArray, QString>::Iterator it = mCustomProperties.begin(); it != mCustomProperties.end();) {
611  if (it.key().startsWith(kalarmKey)) {
612  it = mCustomProperties.erase(it);
613  } else {
614  ++it;
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
622  for (int i = 0, end = flags.count() - 1; i < end; ++i) {
623  QString flag = flags.at(i);
624  if (flag == DATE_ONLY_FLAG) {
625  dateOnly = true;
626  } else if (flag == LOCAL_ZONE_FLAG) {
627  localZone = true;
628  } else if (flag == CONFIRM_ACK_FLAG) {
629  mConfirmAck = true;
630  } else if (flag == EMAIL_BCC_FLAG) {
631  mEmailBcc = true;
632  } else if (flag == KORGANIZER_FLAG) {
633  mCopyToKOrganizer = true;
634  } else if (flag == EXCLUDE_HOLIDAYS_FLAG) {
635  mExcludeHolidays = true;
636  mExcludeHolidayRegion = holidays();
637  } else if (flag == WORK_TIME_ONLY_FLAG) {
638  mWorkTimeOnly = 1;
639  } else if (flag == NOTIFY_FLAG) {
640  mNotify = true;
641  } else if (flag == KMAIL_ITEM_FLAG) {
642  const Akonadi::Item::Id id = flags.at(i + 1).toLongLong(&ok);
643  if (!ok) {
644  continue;
645  }
646  mAkonadiItemId = id;
647  ++i;
648  } else if (flag == KAEventPrivate::ARCHIVE_FLAG) {
649  mArchive = true;
650  } else if (flag == KAEventPrivate::AT_LOGIN_TYPE) {
651  mArchiveRepeatAtLogin = true;
652  } else if (flag == KAEventPrivate::REMINDER_TYPE) {
653  flag = flags.at(++i);
654  if (flag == KAEventPrivate::REMINDER_ONCE_FLAG) {
655  mReminderOnceOnly = true;
656  flag = flags.at(++i);
657  }
658  const int len = flag.length() - 1;
659  mReminderMinutes = -flag.leftRef(len).toInt(); // -> 0 if conversion fails
660  switch (flag.at(len).toLatin1()) {
661  case 'M': break;
662  case 'H': mReminderMinutes *= 60; break;
663  case 'D': mReminderMinutes *= 1440; break;
664  default: mReminderMinutes = 0; break;
665  }
666  } else if (flag == DEFER_FLAG) {
667  QString mins = flags.at(i + 1);
668  if (mins.endsWith(QLatin1Char('D'))) {
669  mDeferDefaultDateOnly = true;
670  mins.chop(1);
671  }
672  const int n = static_cast<int>(mins.toUInt(&ok));
673  if (!ok) {
674  continue;
675  }
676  mDeferDefaultMinutes = n;
677  ++i;
678  } else if (flag == TEMPL_AFTER_TIME_FLAG) {
679  const int n = static_cast<int>(flags.at(i + 1).toUInt(&ok));
680  if (!ok) {
681  continue;
682  }
683  mTemplateAfterTime = n;
684  ++i;
685  } else if (flag == LATE_CANCEL_FLAG) {
686  mLateCancel = static_cast<int>(flags.at(i + 1).toUInt(&ok));
687  if (ok) {
688  ++i;
689  }
690  if (!ok || !mLateCancel) {
691  mLateCancel = 1; // invalid parameter defaults to 1 minute
692  }
693  } else if (flag == AUTO_CLOSE_FLAG) {
694  mLateCancel = static_cast<int>(flags.at(i + 1).toUInt(&ok));
695  if (ok) {
696  ++i;
697  }
698  if (!ok || !mLateCancel) {
699  mLateCancel = 1; // invalid parameter defaults to 1 minute
700  }
701  mAutoClose = true;
702  }
703  }
704 
705  QString prop = event->customProperty(KACalendar::APPNAME, LOG_PROPERTY);
706  if (!prop.isEmpty()) {
707  if (prop == xtermURL) {
708  mCommandXterm = true;
709  } else if (prop == displayURL) {
710  mCommandDisplay = true;
711  } else {
712  mLogFile = prop;
713  }
714  }
715  prop = event->customProperty(KACalendar::APPNAME, REPEAT_PROPERTY);
716  if (!prop.isEmpty()) {
717  // This property is used only when the main alarm has expired.
718  // If a main alarm is found, this property is ignored (see below).
719  const QStringList list = prop.split(QLatin1Char(':'));
720  if (list.count() >= 2) {
721  const int interval = static_cast<int>(list[0].toUInt());
722  const int count = static_cast<int>(list[1].toUInt());
723  if (interval && count) {
724  if (interval % (24 * 60)) {
725  mRepetition.set(Duration(interval * 60, Duration::Seconds), count);
726  } else {
727  mRepetition.set(Duration(interval / (24 * 60), Duration::Days), count);
728  }
729  }
730  }
731  }
732  mNextMainDateTime = readDateTime(event, localZone, dateOnly, mStartDateTime);
733  mCreatedDateTime = KADateTime(event->created());
734  if (dateOnly && !mRepetition.isDaily()) {
735  mRepetition.set(Duration(mRepetition.intervalDays(), Duration::Days));
736  }
737  if (event->customStatus() == DISABLED_STATUS) {
738  mEnabled = false;
739  }
740 
741  // Extract status from the event's alarms.
742  // First set up defaults.
743  mActionSubType = KAEvent::MESSAGE;
744  mMainExpired = true;
745 
746  // Extract data from all the event's alarms and index the alarms by sequence number
747  AlarmMap alarmMap;
748  readAlarms(event, &alarmMap, mCommandDisplay);
749 
750  // Incorporate the alarms' details into the overall event
751  mAlarmCount = 0; // initialise as invalid
752  DateTime alTime;
753  bool set = false;
754  bool isEmailText = false;
755  bool setDeferralTime = false;
756  Duration deferralOffset;
757  for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it) {
758  const AlarmData &data = it.value();
759  const DateTime dateTime(data.alarm->hasStartOffset() ? data.alarm->startOffset().end(mNextMainDateTime.effectiveDateTime()) : data.alarm->time());
760  switch (data.type) {
761  case MAIN_ALARM:
762  mMainExpired = false;
763  alTime = dateTime;
764  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()) {
767  mRepetition.set(data.alarm->snoozeTime(), data.alarm->repeatCount()); // values may be adjusted in setRecurrence()
768  mNextRepeat = data.nextRepeat;
769  }
770  if (data.action != KAAlarm::AUDIO) {
771  break;
772  }
773  // Fall through to AUDIO_ALARM
774  Q_FALLTHROUGH();
775  case AUDIO_ALARM:
776  mAudioFile = data.cleanText;
777  mSpeak = data.speak && mAudioFile.isEmpty();
778  mBeep = !mSpeak && mAudioFile.isEmpty();
779  mSoundVolume = (!mBeep && !mSpeak) ? data.soundVolume : -1;
780  mFadeVolume = (mSoundVolume >= 0 && data.fadeSeconds > 0) ? data.fadeVolume : -1;
781  mFadeSeconds = (mFadeVolume >= 0) ? data.fadeSeconds : 0;
782  mRepeatSoundPause = (!mBeep && !mSpeak) ? data.repeatSoundPause : -1;
783  break;
784  case AT_LOGIN_ALARM:
785  mRepeatAtLogin = true;
786  mAtLoginDateTime = dateTime.kDateTime();
787  alTime = mAtLoginDateTime;
788  break;
789  case REMINDER_ALARM:
790  --mAlarmCount; // reminder alarms only contribute to the alarm count if active
791  // N.B. there can be a start offset but no valid date/time (e.g. in template)
792  if (data.alarm->startOffset().asSeconds() / 60) {
793  mReminderActive = ACTIVE_REMINDER;
794  if (mReminderMinutes < 0) {
795  mReminderAfterTime = dateTime; // the reminder is AFTER the main alarm
796  mReminderAfterTime.setDateOnly(dateOnly);
797  if (data.hiddenReminder) {
798  mReminderActive = HIDDEN_REMINDER;
799  }
800  }
801  }
802  break;
803  case DEFERRED_REMINDER_ALARM:
804  case DEFERRED_ALARM:
805  mDeferral = (data.type == DEFERRED_REMINDER_ALARM) ? REMINDER_DEFERRAL : NORMAL_DEFERRAL;
806  if (data.timedDeferral) {
807  // Don't use start-of-day time for applying timed deferral alarm offset
808  mDeferralTime = DateTime(data.alarm->hasStartOffset() ? data.alarm->startOffset().end(mNextMainDateTime.calendarDateTime()) : data.alarm->time());
809  } else {
810  mDeferralTime = dateTime;
811  mDeferralTime.setDateOnly(true);
812  }
813  if (data.alarm->hasStartOffset()) {
814  deferralOffset = data.alarm->startOffset();
815  }
816  break;
817  case DISPLAYING_ALARM: {
818  mDisplaying = true;
819  mDisplayingFlags = data.displayingFlags;
820  const bool dateOnly = (mDisplayingFlags & DEFERRAL) ? !(mDisplayingFlags & TIMED_FLAG)
821  : mStartDateTime.isDateOnly();
822  mDisplayingTime = dateTime;
823  mDisplayingTime.setDateOnly(dateOnly);
824  alTime = mDisplayingTime;
825  break;
826  }
827  case PRE_ACTION_ALARM:
828  mPreAction = data.cleanText;
829  mExtraActionOptions = data.extraActionOptions;
830  break;
831  case POST_ACTION_ALARM:
832  mPostAction = data.cleanText;
833  break;
834  case INVALID_ALARM:
835  default:
836  break;
837  }
838 
839  bool noSetNextTime = false;
840  switch (data.type) {
841  case DEFERRED_REMINDER_ALARM:
842  case DEFERRED_ALARM:
843  if (!set) {
844  // The recurrence has to be evaluated before we can
845  // calculate the time of a deferral alarm.
846  setDeferralTime = true;
847  noSetNextTime = true;
848  }
849  // fall through to REMINDER_ALARM
850  Q_FALLTHROUGH();
851  case REMINDER_ALARM:
852  case AT_LOGIN_ALARM:
853  case DISPLAYING_ALARM:
854  if (!set && !noSetNextTime) {
855  mNextMainDateTime = alTime;
856  }
857  // fall through to MAIN_ALARM
858  Q_FALLTHROUGH();
859  case MAIN_ALARM:
860  // Ensure that the basic fields are set up even if there is no main
861  // alarm in the event (if it has expired and then been deferred)
862  if (!set) {
863  mActionSubType = static_cast<KAEvent::SubAction>(data.action);
864  mText = (mActionSubType == KAEvent::COMMAND) ? data.cleanText.trimmed() : data.cleanText;
865  switch (data.action) {
866  case KAAlarm::COMMAND:
867  mCommandScript = data.commandScript;
868  if (data.extraActionOptions & KAEvent::DontShowPreActError) {
869  mCommandHideError = true;
870  }
871  if (!mCommandDisplay) {
872  break;
873  }
874  // fall through to MESSAGE
875  Q_FALLTHROUGH();
876  case KAAlarm::MESSAGE:
877  mFont = data.font;
878  mUseDefaultFont = data.defaultFont;
879  if (data.isEmailText) {
880  isEmailText = true;
881  }
882  // fall through to FILE
883  Q_FALLTHROUGH();
884  case KAAlarm::FILE:
885  mBgColour = data.bgColour;
886  mFgColour = data.fgColour;
887  break;
888  case KAAlarm::EMAIL:
889  mEmailFromIdentity = data.emailFromId;
890  mEmailAddresses = data.alarm->mailAddresses();
891  mEmailSubject = data.alarm->mailSubject();
892  mEmailAttachments = data.alarm->mailAttachments();
893  break;
894  case KAAlarm::AUDIO:
895  // Already mostly handled above
896  mRepeatSoundPause = data.repeatSoundPause;
897  break;
898  default:
899  break;
900  }
901  set = true;
902  }
903  if (data.action == KAAlarm::FILE && mActionSubType == KAEvent::MESSAGE) {
904  mActionSubType = KAEvent::FILE;
905  }
906  ++mAlarmCount;
907  break;
908  case AUDIO_ALARM:
909  case PRE_ACTION_ALARM:
910  case POST_ACTION_ALARM:
911  case INVALID_ALARM:
912  default:
913  break;
914  }
915  }
916  if (!isEmailText) {
917  mAkonadiItemId = -1;
918  }
919 
920  Recurrence *recur = event->recurrence();
921  if (recur && recur->recurs()) {
922  const int nextRepeat = mNextRepeat; // setRecurrence() clears mNextRepeat
923  setRecurrence(*recur);
924  if (nextRepeat <= mRepetition.count()) {
925  mNextRepeat = nextRepeat;
926  }
927  } else if (mRepetition) {
928  // Convert a repetition with no recurrence into a recurrence
929  if (mRepetition.isDaily()) {
930  setRecur(RecurrenceRule::rDaily, mRepetition.intervalDays(), mRepetition.count() + 1, QDate());
931  } else {
932  setRecur(RecurrenceRule::rMinutely, mRepetition.intervalMinutes(), mRepetition.count() + 1, KADateTime());
933  }
934  mRepetition.set(0, 0);
935  mTriggerChanged = true;
936  }
937 
938  if (mRepeatAtLogin) {
939  mArchiveRepeatAtLogin = false;
940  if (mReminderMinutes > 0) {
941  mReminderMinutes = 0; // pre-alarm reminder not allowed for at-login alarm
942  mReminderActive = NO_REMINDER;
943  }
944  setRepeatAtLoginTrue(false); // clear other incompatible statuses
945  }
946 
947  // Adjust the alarm count if there is an active reminder alarm.
948  if (mReminderActive != NO_REMINDER) {
949  ++mAlarmCount;
950  }
951 
952  if (mMainExpired && !deferralOffset.isNull() && checkRecur() != KARecurrence::NO_RECUR) {
953  // Adjust the deferral time for an expired recurrence, since the
954  // offset is relative to the first actual occurrence.
955  DateTime dt = mRecurrence->getNextDateTime(mStartDateTime.addDays(-1).kDateTime());
956  dt.setDateOnly(mStartDateTime.isDateOnly());
957  if (mDeferralTime.isDateOnly()) {
958  mDeferralTime = DateTime(deferralOffset.end(dt.qDateTime()));
959  mDeferralTime.setDateOnly(true);
960  } else {
961  mDeferralTime = DateTime(deferralOffset.end(dt.effectiveDateTime()));
962  }
963  }
964  if (mDeferral != NO_DEFERRAL) {
965  if (setDeferralTime) {
966  mNextMainDateTime = mDeferralTime;
967  }
968  }
969  mTriggerChanged = true;
970  endChanges();
971 }
972 
973 KAEventPrivate::KAEventPrivate(const KAEventPrivate &other)
974  : QSharedData(other)
975  , mRecurrence(nullptr)
976 {
977  copy(other);
978 }
979 
980 KAEvent::KAEvent(const KAEvent &other) = default;
981 
982 KAEvent::~KAEvent() = default;
983 
984 KAEvent &KAEvent::operator=(const KAEvent &other)
985 {
986  if (&other != this) {
987  d = other.d;
988  }
989  return *this;
990 }
991 
992 /******************************************************************************
993 * Copies the data from another instance.
994 */
995 void KAEventPrivate::copy(const KAEventPrivate &event)
996 {
997  mAllTrigger = event.mAllTrigger;
998  mMainTrigger = event.mMainTrigger;
999  mAllWorkTrigger = event.mAllWorkTrigger;
1000  mMainWorkTrigger = event.mMainWorkTrigger;
1001  mCommandError = event.mCommandError;
1002  mEventID = event.mEventID;
1003  mCustomProperties = event.mCustomProperties;
1004  mItemId = event.mItemId;
1005  mResourceId = event.mResourceId;
1006  mName = event.mName;
1007  mText = event.mText;
1008  mAudioFile = event.mAudioFile;
1009  mPreAction = event.mPreAction;
1010  mPostAction = event.mPostAction;
1011  mStartDateTime = event.mStartDateTime;
1012  mCreatedDateTime = event.mCreatedDateTime;
1013  mNextMainDateTime = event.mNextMainDateTime;
1014  mAtLoginDateTime = event.mAtLoginDateTime;
1015  mDeferralTime = event.mDeferralTime;
1016  mDisplayingTime = event.mDisplayingTime;
1017  mDisplayingFlags = event.mDisplayingFlags;
1018  mReminderMinutes = event.mReminderMinutes;
1019  mReminderAfterTime = event.mReminderAfterTime;
1020  mReminderActive = event.mReminderActive;
1021  mDeferDefaultMinutes = event.mDeferDefaultMinutes;
1022  mDeferDefaultDateOnly = event.mDeferDefaultDateOnly;
1023  mRevision = event.mRevision;
1024  mRepetition = event.mRepetition;
1025  mNextRepeat = event.mNextRepeat;
1026  mAlarmCount = event.mAlarmCount;
1027  mDeferral = event.mDeferral;
1028  mAkonadiItemId = event.mAkonadiItemId;
1029  mTemplateAfterTime = event.mTemplateAfterTime;
1030  mBgColour = event.mBgColour;
1031  mFgColour = event.mFgColour;
1032  mFont = event.mFont;
1033  mEmailFromIdentity = event.mEmailFromIdentity;
1034  mEmailAddresses = event.mEmailAddresses;
1035  mEmailSubject = event.mEmailSubject;
1036  mEmailAttachments = event.mEmailAttachments;
1037  mLogFile = event.mLogFile;
1038  mSoundVolume = event.mSoundVolume;
1039  mFadeVolume = event.mFadeVolume;
1040  mFadeSeconds = event.mFadeSeconds;
1041  mRepeatSoundPause = event.mRepeatSoundPause;
1042  mLateCancel = event.mLateCancel;
1043  mExcludeHolidays = event.mExcludeHolidays;
1044  mExcludeHolidayRegion = event.mExcludeHolidayRegion;
1045  mWorkTimeOnly = event.mWorkTimeOnly;
1046  mActionSubType = event.mActionSubType;
1047  mCategory = event.mCategory;
1048  mExtraActionOptions = event.mExtraActionOptions;
1049  mCompatibility = event.mCompatibility;
1050  mReadOnly = event.mReadOnly;
1051  mConfirmAck = event.mConfirmAck;
1052  mUseDefaultFont = event.mUseDefaultFont;
1053  mCommandScript = event.mCommandScript;
1054  mCommandXterm = event.mCommandXterm;
1055  mCommandDisplay = event.mCommandDisplay;
1056  mCommandHideError = event.mCommandHideError;
1057  mEmailBcc = event.mEmailBcc;
1058  mBeep = event.mBeep;
1059  mSpeak = event.mSpeak;
1060  mCopyToKOrganizer = event.mCopyToKOrganizer;
1061  mReminderOnceOnly = event.mReminderOnceOnly;
1062  mAutoClose = event.mAutoClose;
1063  mNotify = event.mNotify;
1064  mMainExpired = event.mMainExpired;
1065  mRepeatAtLogin = event.mRepeatAtLogin;
1066  mArchiveRepeatAtLogin = event.mArchiveRepeatAtLogin;
1067  mArchive = event.mArchive;
1068  mDisplaying = event.mDisplaying;
1069  mDisplayingDefer = event.mDisplayingDefer;
1070  mDisplayingEdit = event.mDisplayingEdit;
1071  mEnabled = event.mEnabled;
1072  mChangeCount = 0;
1073  mTriggerChanged = event.mTriggerChanged;
1074  delete mRecurrence;
1075  if (event.mRecurrence) {
1076  mRecurrence = new KARecurrence(*event.mRecurrence);
1077  } else {
1078  mRecurrence = nullptr;
1079  }
1080 }
1081 
1082 // Deprecated
1084 {
1085  *this = KAEvent(e);
1086 }
1087 
1088 // Deprecated
1089 void KAEvent::set(const KADateTime &dt, const QString &message, const QColor &bg, const QColor &fg,
1090  const QFont &f, SubAction act, int lateCancel, Flags flags, bool changesPending)
1091 {
1092  *this = KAEvent(dt, QString(), message, bg, fg, f, act, lateCancel, flags, changesPending);
1093 }
1094 
1095 /******************************************************************************
1096 * Update an existing KCalendarCore::Event with the KAEventPrivate data.
1097 * If 'setCustomProperties' is true, all the KCalendarCore::Event's existing
1098 * custom properties are cleared and replaced with the KAEvent's custom
1099 * properties. If false, the KCalendarCore::Event's non-KAlarm custom properties
1100 * are left untouched.
1101 */
1102 bool KAEvent::updateKCalEvent(const KCalendarCore::Event::Ptr &e, UidAction u, bool setCustomProperties) const
1103 {
1104  return d->updateKCalEvent(e, u, setCustomProperties);
1105 }
1106 
1107 bool KAEventPrivate::updateKCalEvent(const Event::Ptr &ev, KAEvent::UidAction uidact, bool setCustomProperties) const
1108 {
1109  // If it's an archived event, the event start date/time will be adjusted to its original
1110  // value instead of its next occurrence, and the expired main alarm will be reinstated.
1111  const bool archived = (mCategory == CalEvent::ARCHIVED);
1112 
1113  if (!ev
1114  || (uidact == KAEvent::UID_CHECK && !mEventID.isEmpty() && mEventID != ev->uid())
1115  || (!mAlarmCount && (!archived || !mMainExpired))) {
1116  return false;
1117  }
1118 
1119  ev->startUpdates(); // prevent multiple update notifications
1120  checkRecur(); // ensure recurrence/repetition data is consistent
1121  const bool readOnly = ev->isReadOnly();
1122  if (uidact == KAEvent::UID_SET) {
1123  ev->setUid(mEventID);
1124  }
1125  ev->setReadOnly(mReadOnly);
1126  ev->setTransparency(Event::Transparent);
1127 
1128  // Set up event-specific data
1129  ev->setSummary(mName);
1130 
1131  // Set up custom properties.
1132  if (setCustomProperties) {
1133  ev->setCustomProperties(mCustomProperties);
1134  }
1135  ev->removeCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY);
1136  ev->removeCustomProperty(KACalendar::APPNAME, NEXT_RECUR_PROPERTY);
1137  ev->removeCustomProperty(KACalendar::APPNAME, REPEAT_PROPERTY);
1138  ev->removeCustomProperty(KACalendar::APPNAME, LOG_PROPERTY);
1139 
1140  QString param;
1141  if (mCategory == CalEvent::DISPLAYING) {
1142  param = QString::number(mResourceId); // original resource ID which contained the event
1143  if (mDisplayingDefer) {
1144  param += SC + DISP_DEFER;
1145  }
1146  if (mDisplayingEdit) {
1147  param += SC + DISP_EDIT;
1148  }
1149  }
1150  CalEvent::setStatus(ev, mCategory, param);
1152  if (mStartDateTime.isDateOnly()) {
1153  flags += DATE_ONLY_FLAG;
1154  }
1155  if (mStartDateTime.timeType() == KADateTime::LocalZone) {
1156  flags += LOCAL_ZONE_FLAG;
1157  }
1158  if (mConfirmAck) {
1159  flags += CONFIRM_ACK_FLAG;
1160  }
1161  if (mEmailBcc) {
1162  flags += EMAIL_BCC_FLAG;
1163  }
1164  if (mCopyToKOrganizer) {
1165  flags += KORGANIZER_FLAG;
1166  }
1167  if (mExcludeHolidays) {
1168  flags += EXCLUDE_HOLIDAYS_FLAG;
1169  }
1170  if (mWorkTimeOnly) {
1171  flags += WORK_TIME_ONLY_FLAG;
1172  }
1173  if (mNotify) {
1174  flags += NOTIFY_FLAG;
1175  }
1176  if (mLateCancel) {
1177  (flags += (mAutoClose ? AUTO_CLOSE_FLAG : LATE_CANCEL_FLAG)) += QString::number(mLateCancel);
1178  }
1179  if (mReminderMinutes) {
1180  flags += REMINDER_TYPE;
1181  if (mReminderOnceOnly) {
1182  flags += REMINDER_ONCE_FLAG;
1183  }
1184  flags += reminderToString(-mReminderMinutes);
1185  }
1186  if (mDeferDefaultMinutes) {
1187  QString param = QString::number(mDeferDefaultMinutes);
1188  if (mDeferDefaultDateOnly) {
1189  param += QLatin1Char('D');
1190  }
1191  (flags += DEFER_FLAG) += param;
1192  }
1193  if (mCategory == CalEvent::TEMPLATE && mTemplateAfterTime >= 0) {
1194  (flags += TEMPL_AFTER_TIME_FLAG) += QString::number(mTemplateAfterTime);
1195  }
1196  if (mAkonadiItemId >= 0) {
1197  (flags += KMAIL_ITEM_FLAG) += QString::number(mAkonadiItemId);
1198  }
1199  if (mArchive && !archived) {
1200  flags += ARCHIVE_FLAG;
1201  if (mArchiveRepeatAtLogin) {
1202  flags += AT_LOGIN_TYPE;
1203  }
1204  }
1205  if (!flags.isEmpty()) {
1206  ev->setCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY, flags.join(SC));
1207  }
1208 
1209  if (mCommandXterm) {
1210  ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, xtermURL);
1211  } else if (mCommandDisplay) {
1212  ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, displayURL);
1213  } else if (!mLogFile.isEmpty()) {
1214  ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, mLogFile);
1215  }
1216 
1217  ev->setCustomStatus(mEnabled ? QString() : DISABLED_STATUS);
1218  ev->setRevision(mRevision);
1219  ev->clearAlarms();
1220 
1221  /* Always set DTSTART as date/time, and use the category "DATE" to indicate
1222  * a date-only event, instead of calling setAllDay(). This is necessary to
1223  * allow a time zone to be specified for a date-only event. Also, KAlarm
1224  * allows the alarm to float within the 24-hour period defined by the
1225  * start-of-day time (which is user-dependent and therefore can't be
1226  * written into the calendar) rather than midnight to midnight, and there
1227  * is no RFC2445 conformant way to specify this.
1228  * RFC2445 states that alarm trigger times specified in absolute terms
1229  * (rather than relative to DTSTART or DTEND) can only be specified as a
1230  * UTC DATE-TIME value. So always use a time relative to DTSTART instead of
1231  * an absolute time.
1232  */
1233  ev->setDtStart(mStartDateTime.calendarDateTime());
1234  ev->setAllDay(false);
1235  ev->setDtEnd(QDateTime());
1236 
1237  const DateTime dtMain = archived ? mStartDateTime : mNextMainDateTime;
1238  int ancillaryType = 0; // 0 = invalid, 1 = time, 2 = offset
1239  DateTime ancillaryTime; // time for ancillary alarms (pre-action, extra audio, etc)
1240  int ancillaryOffset = 0; // start offset for ancillary alarms
1241  if (!mMainExpired || archived) {
1242  /* The alarm offset must always be zero for the main alarm. To determine
1243  * which recurrence is due, the property X-KDE-KALARM_NEXTRECUR is used.
1244  * If the alarm offset was non-zero, exception dates and rules would not
1245  * work since they apply to the event time, not the alarm time.
1246  */
1247  if (!archived && checkRecur() != KARecurrence::NO_RECUR) {
1248  QDateTime dt = mNextMainDateTime.kDateTime().toTimeSpec(mStartDateTime.timeSpec()).qDateTime();
1249  ev->setCustomProperty(KACalendar::APPNAME, NEXT_RECUR_PROPERTY,
1250  QLocale::c().toString(dt, mNextMainDateTime.isDateOnly() ? QStringLiteral("yyyyMMdd") : QStringLiteral("yyyyMMddThhmmss")));
1251  }
1252  // Add the main alarm
1253  initKCalAlarm(ev, 0, QStringList(), MAIN_ALARM);
1254  ancillaryOffset = 0;
1255  ancillaryType = dtMain.isValid() ? 2 : 0;
1256  } else if (mRepetition) {
1257  // Alarm repetition is normally held in the main alarm, but since
1258  // the main alarm has expired, store in a custom property.
1259  const QString param = QStringLiteral("%1:%2").arg(mRepetition.intervalMinutes()).arg(mRepetition.count());
1260  ev->setCustomProperty(KACalendar::APPNAME, REPEAT_PROPERTY, param);
1261  }
1262 
1263  // Add subsidiary alarms
1264  if (mRepeatAtLogin || (mArchiveRepeatAtLogin && archived)) {
1265  DateTime dtl;
1266  if (mArchiveRepeatAtLogin) {
1267  dtl = mStartDateTime.calendarKDateTime().addDays(-1);
1268  } else if (mAtLoginDateTime.isValid()) {
1269  dtl = mAtLoginDateTime;
1270  } else if (mStartDateTime.isDateOnly()) {
1271  dtl = DateTime(KADateTime::currentLocalDate().addDays(-1), mStartDateTime.timeSpec());
1272  } else {
1274  }
1275  initKCalAlarm(ev, dtl, QStringList(AT_LOGIN_TYPE));
1276  if (!ancillaryType && dtl.isValid()) {
1277  ancillaryTime = dtl;
1278  ancillaryType = 1;
1279  }
1280  }
1281 
1282  // Find the base date/time for calculating alarm offsets
1283  DateTime nextDateTime = mNextMainDateTime;
1284  if (mMainExpired) {
1285  if (checkRecur() == KARecurrence::NO_RECUR) {
1286  nextDateTime = mStartDateTime;
1287  } else if (!archived) {
1288  // It's a deferral of an expired recurrence.
1289  // Need to ensure that the alarm offset is to an occurrence
1290  // which isn't excluded by an exception - otherwise, it will
1291  // never be triggered. So choose the first recurrence which
1292  // isn't an exception.
1293  KADateTime dt = mRecurrence->getNextDateTime(mStartDateTime.addDays(-1).kDateTime());
1294  dt.setDateOnly(mStartDateTime.isDateOnly());
1295  nextDateTime = dt;
1296  }
1297  }
1298 
1299  if (mReminderMinutes && (mReminderActive != NO_REMINDER || archived)) {
1300  int startOffset;
1301  if (mReminderMinutes < 0 && mReminderActive != NO_REMINDER) {
1302  // A reminder AFTER the main alarm is active or disabled
1303  startOffset = nextDateTime.calendarKDateTime().secsTo(mReminderAfterTime.calendarKDateTime());
1304  } else {
1305  // A reminder BEFORE the main alarm is active
1306  startOffset = -mReminderMinutes * 60;
1307  }
1308  initKCalAlarm(ev, startOffset, QStringList(REMINDER_TYPE));
1309  // Don't set ancillary time if the reminder AFTER is hidden by a deferral
1310  if (!ancillaryType && (mReminderActive == ACTIVE_REMINDER || archived)) {
1311  ancillaryOffset = startOffset;
1312  ancillaryType = 2;
1313  }
1314  }
1315  if (mDeferral != NO_DEFERRAL) {
1316  int startOffset;
1317  QStringList list;
1318  if (mDeferralTime.isDateOnly()) {
1319  startOffset = nextDateTime.secsTo(mDeferralTime.calendarKDateTime());
1320  list += DATE_DEFERRAL_TYPE;
1321  } else {
1322  startOffset = nextDateTime.calendarKDateTime().secsTo(mDeferralTime.calendarKDateTime());
1323  list += TIME_DEFERRAL_TYPE;
1324  }
1325  if (mDeferral == REMINDER_DEFERRAL) {
1326  list += REMINDER_TYPE;
1327  }
1328  initKCalAlarm(ev, startOffset, list);
1329  if (!ancillaryType && mDeferralTime.isValid()) {
1330  ancillaryOffset = startOffset;
1331  ancillaryType = 2;
1332  }
1333  }
1334  if (mDisplaying && mCategory != CalEvent::TEMPLATE) {
1335  QStringList list(DISPLAYING_TYPE);
1336  if (mDisplayingFlags & KAEvent::REPEAT_AT_LOGIN) {
1337  list += AT_LOGIN_TYPE;
1338  } else if (mDisplayingFlags & DEFERRAL) {
1339  if (mDisplayingFlags & TIMED_FLAG) {
1340  list += TIME_DEFERRAL_TYPE;
1341  } else {
1342  list += DATE_DEFERRAL_TYPE;
1343  }
1344  }
1345  if (mDisplayingFlags & REMINDER) {
1346  list += REMINDER_TYPE;
1347  }
1348  initKCalAlarm(ev, mDisplayingTime, list);
1349  if (!ancillaryType && mDisplayingTime.isValid()) {
1350  ancillaryTime = mDisplayingTime;
1351  ancillaryType = 1;
1352  }
1353  }
1354  if ((mBeep || mSpeak || !mAudioFile.isEmpty()) && mActionSubType != KAEvent::AUDIO) {
1355  // A sound is specified
1356  if (ancillaryType == 2) {
1357  initKCalAlarm(ev, ancillaryOffset, QStringList(), AUDIO_ALARM);
1358  } else {
1359  initKCalAlarm(ev, ancillaryTime, QStringList(), AUDIO_ALARM);
1360  }
1361  }
1362  if (!mPreAction.isEmpty()) {
1363  // A pre-display action is specified
1364  if (ancillaryType == 2) {
1365  initKCalAlarm(ev, ancillaryOffset, QStringList(PRE_ACTION_TYPE), PRE_ACTION_ALARM);
1366  } else {
1367  initKCalAlarm(ev, ancillaryTime, QStringList(PRE_ACTION_TYPE), PRE_ACTION_ALARM);
1368  }
1369  }
1370  if (!mPostAction.isEmpty()) {
1371  // A post-display action is specified
1372  if (ancillaryType == 2) {
1373  initKCalAlarm(ev, ancillaryOffset, QStringList(POST_ACTION_TYPE), POST_ACTION_ALARM);
1374  } else {
1375  initKCalAlarm(ev, ancillaryTime, QStringList(POST_ACTION_TYPE), POST_ACTION_ALARM);
1376  }
1377  }
1378 
1379  if (mRecurrence) {
1380  mRecurrence->writeRecurrence(*ev->recurrence());
1381  } else {
1382  ev->clearRecurrence();
1383  }
1384  if (mCreatedDateTime.isValid()) {
1385  ev->setCreated(mCreatedDateTime.qDateTime());
1386  }
1387  ev->setReadOnly(readOnly);
1388  ev->endUpdates(); // finally issue an update notification
1389  return true;
1390 }
1391 
1392 /******************************************************************************
1393 * Create a new alarm for a libkcal event, and initialise it according to the
1394 * alarm action. If 'types' is non-null, it is appended to the X-KDE-KALARM-TYPE
1395 * property value list.
1396 * NOTE: The variant taking a DateTime calculates the offset from mStartDateTime,
1397 * which is not suitable for an alarm in a recurring event.
1398 */
1399 Alarm::Ptr KAEventPrivate::initKCalAlarm(const KCalendarCore::Event::Ptr &event, const DateTime &dt, const QStringList &types, AlarmType type) const
1400 {
1401  const int startOffset = dt.isDateOnly() ? mStartDateTime.secsTo(dt)
1402  : mStartDateTime.calendarKDateTime().secsTo(dt.calendarKDateTime());
1403  return initKCalAlarm(event, startOffset, types, type);
1404 }
1405 
1406 Alarm::Ptr KAEventPrivate::initKCalAlarm(const KCalendarCore::Event::Ptr &event, int startOffsetSecs, const QStringList &types, AlarmType type) const
1407 {
1408  QStringList alltypes;
1410  Alarm::Ptr alarm = event->newAlarm();
1411  alarm->setEnabled(true);
1412  if (type != MAIN_ALARM) {
1413  // RFC2445 specifies that absolute alarm times must be stored as a UTC DATE-TIME value.
1414  // Set the alarm time as an offset to DTSTART for the reasons described in updateKCalEvent().
1415  alarm->setStartOffset(startOffsetSecs);
1416  }
1417 
1418  switch (type) {
1419  case AUDIO_ALARM:
1420  setAudioAlarm(alarm);
1421  if (mSpeak) {
1422  flags << KAEventPrivate::SPEAK_FLAG;
1423  }
1424  if (mRepeatSoundPause >= 0) {
1425  // Alarm::setSnoozeTime() sets 5 seconds if duration parameter is zero,
1426  // so repeat count = -1 represents 0 pause, -2 represents non-zero pause.
1427  alarm->setRepeatCount(mRepeatSoundPause ? -2 : -1);
1428  alarm->setSnoozeTime(Duration(mRepeatSoundPause, Duration::Seconds));
1429  }
1430  break;
1431  case PRE_ACTION_ALARM:
1432  setProcedureAlarm(alarm, mPreAction);
1433  if (mExtraActionOptions & KAEvent::ExecPreActOnDeferral) {
1434  flags << KAEventPrivate::EXEC_ON_DEFERRAL_FLAG;
1435  }
1436  if (mExtraActionOptions & KAEvent::CancelOnPreActError) {
1437  flags << KAEventPrivate::CANCEL_ON_ERROR_FLAG;
1438  }
1439  if (mExtraActionOptions & KAEvent::DontShowPreActError) {
1440  flags << KAEventPrivate::DONT_SHOW_ERROR_FLAG;
1441  }
1442  break;
1443  case POST_ACTION_ALARM:
1444  setProcedureAlarm(alarm, mPostAction);
1445  break;
1446  case MAIN_ALARM:
1447  alarm->setSnoozeTime(mRepetition.interval());
1448  alarm->setRepeatCount(mRepetition.count());
1449  if (mRepetition) {
1450  alarm->setCustomProperty(KACalendar::APPNAME, NEXT_REPEAT_PROPERTY,
1451  QString::number(mNextRepeat));
1452  }
1453  // fall through to INVALID_ALARM
1454  Q_FALLTHROUGH();
1455  case REMINDER_ALARM:
1456  case INVALID_ALARM: {
1457  if (types == QStringList(REMINDER_TYPE)
1458  && mReminderMinutes < 0 && mReminderActive == HIDDEN_REMINDER) {
1459  // It's a reminder AFTER the alarm which is currently disabled
1460  // due to the main alarm being deferred past it.
1461  flags << HIDDEN_REMINDER_FLAG;
1462  }
1463  bool display = false;
1464  switch (mActionSubType) {
1465  case KAEvent::FILE:
1466  alltypes += FILE_TYPE;
1467  // fall through to MESSAGE
1468  Q_FALLTHROUGH();
1469  case KAEvent::MESSAGE:
1470  alarm->setDisplayAlarm(AlarmText::toCalendarText(mText));
1471  display = true;
1472  break;
1473  case KAEvent::COMMAND:
1474  if (mCommandScript) {
1475  alarm->setProcedureAlarm(QString(), mText);
1476  } else {
1477  setProcedureAlarm(alarm, mText);
1478  }
1479  display = mCommandDisplay;
1480  if (mCommandHideError) {
1481  flags += DONT_SHOW_ERROR_FLAG;
1482  }
1483  break;
1484  case KAEvent::EMAIL:
1485  alarm->setEmailAlarm(mEmailSubject, mText, mEmailAddresses, mEmailAttachments);
1486  if (mEmailFromIdentity) {
1487  flags << KAEventPrivate::EMAIL_ID_FLAG << QString::number(mEmailFromIdentity);
1488  }
1489  break;
1490  case KAEvent::AUDIO:
1491  setAudioAlarm(alarm);
1492  if (mRepeatSoundPause >= 0 && type == MAIN_ALARM) {
1493  // Indicate repeating sound in the main alarm by a non-standard
1494  // method, since it might have a sub-repetition too.
1495  alltypes << SOUND_REPEAT_TYPE << QString::number(mRepeatSoundPause);
1496  }
1497  break;
1498  }
1499  if (display && !mNotify) {
1500  alarm->setCustomProperty(KACalendar::APPNAME, FONT_COLOUR_PROPERTY,
1501  QStringLiteral("%1;%2;%3").arg(mBgColour.name(), mFgColour.name(), mUseDefaultFont ? QString() : mFont.toString()));
1502  }
1503  break;
1504  }
1505  case DEFERRED_ALARM:
1506  case DEFERRED_REMINDER_ALARM:
1507  case AT_LOGIN_ALARM:
1508  case DISPLAYING_ALARM:
1509  break;
1510  }
1511  alltypes += types;
1512  if (!alltypes.isEmpty()) {
1513  alarm->setCustomProperty(KACalendar::APPNAME, TYPE_PROPERTY, alltypes.join(QLatin1Char(',')));
1514  }
1515  if (!flags.isEmpty()) {
1516  alarm->setCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY, flags.join(SC));
1517  }
1518  return alarm;
1519 }
1520 
1521 /******************************************************************************
1522 * Find the index to the last daylight savings time transition at or before a
1523 * given UTC time.
1524 * Returns index, or -1 if before the first transition.
1525 */
1526 int KAEventPrivate::transitionIndex(const QDateTime &utc, const QTimeZone::OffsetDataList& transitions)
1527 {
1528  if (utc.timeSpec() != Qt::UTC || transitions.isEmpty()) {
1529  return -1;
1530  }
1531  int start = 0;
1532  int end = transitions.size() - 1;
1533  while (start != end) {
1534  int i = (start + end + 1) / 2;
1535  if (transitions[i].atUtc == utc) {
1536  return i;
1537  }
1538  if (transitions[i].atUtc > utc) {
1539  end = i - 1;
1540  if (end < 0) {
1541  return -1;
1542  }
1543  } else {
1544  start = i;
1545  }
1546  }
1547  return start;
1548 }
1549 
1550 bool KAEvent::isValid() const
1551 {
1552  return d->mAlarmCount && (d->mAlarmCount != 1 || !d->mRepeatAtLogin);
1553 }
1554 
1555 void KAEvent::setEnabled(bool enable)
1556 {
1557  d->mEnabled = enable;
1558 }
1559 
1560 bool KAEvent::enabled() const
1561 {
1562  return d->mEnabled;
1563 }
1564 
1566 {
1567  d->mReadOnly = ro;
1568 }
1569 
1571 {
1572  return d->mReadOnly;
1573 }
1574 
1576 {
1577  d->mArchive = true;
1578 }
1579 
1581 {
1582  return d->mArchive;
1583 }
1584 
1586 {
1587  return d->mMainExpired;
1588 }
1589 
1590 bool KAEvent::expired() const
1591 {
1592  return (d->mDisplaying && d->mMainExpired) || d->mCategory == CalEvent::ARCHIVED;
1593 }
1594 
1596 {
1597  return d->flags();
1598 }
1599 
1600 KAEvent::Flags KAEventPrivate::flags() const
1601 {
1602  KAEvent::Flags result{};
1603  if (mBeep) {
1604  result |= KAEvent::BEEP;
1605  }
1606  if (mRepeatSoundPause >= 0) {
1607  result |= KAEvent::REPEAT_SOUND;
1608  }
1609  if (mEmailBcc) {
1610  result |= KAEvent::EMAIL_BCC;
1611  }
1612  if (mStartDateTime.isDateOnly()) {
1613  result |= KAEvent::ANY_TIME;
1614  }
1615  if (mSpeak) {
1616  result |= KAEvent::SPEAK;
1617  }
1618  if (mRepeatAtLogin) {
1619  result |= KAEvent::REPEAT_AT_LOGIN;
1620  }
1621  if (mConfirmAck) {
1622  result |= KAEvent::CONFIRM_ACK;
1623  }
1624  if (mUseDefaultFont) {
1625  result |= KAEvent::DEFAULT_FONT;
1626  }
1627  if (mCommandScript) {
1628  result |= KAEvent::SCRIPT;
1629  }
1630  if (mCommandXterm) {
1631  result |= KAEvent::EXEC_IN_XTERM;
1632  }
1633  if (mCommandDisplay) {
1634  result |= KAEvent::DISPLAY_COMMAND;
1635  }
1636  if (mCommandHideError) {
1637  result |= KAEvent::DONT_SHOW_ERROR;
1638  }
1639  if (mCopyToKOrganizer) {
1640  result |= KAEvent::COPY_KORGANIZER;
1641  }
1642  if (mExcludeHolidays) {
1643  result |= KAEvent::EXCL_HOLIDAYS;
1644  }
1645  if (mWorkTimeOnly) {
1646  result |= KAEvent::WORK_TIME_ONLY;
1647  }
1648  if (mReminderOnceOnly) {
1649  result |= KAEvent::REMINDER_ONCE;
1650  }
1651  if (mAutoClose) {
1652  result |= KAEvent::AUTO_CLOSE;
1653  }
1654  if (mNotify) {
1655  result |= KAEvent::NOTIFY;
1656  }
1657  if (!mEnabled) {
1658  result |= KAEvent::DISABLED;
1659  }
1660  return result;
1661 }
1662 
1663 /******************************************************************************
1664 * Change the type of an event.
1665 * If it is being set to archived, set the archived indication in the event ID;
1666 * otherwise, remove the archived indication from the event ID.
1667 */
1669 {
1670  d->setCategory(s);
1671 }
1672 
1673 void KAEventPrivate::setCategory(CalEvent::Type s)
1674 {
1675  if (s == mCategory) {
1676  return;
1677  }
1678  mEventID = CalEvent::uid(mEventID, s);
1679  mCategory = s;
1680  mTriggerChanged = true; // templates and archived don't have trigger times
1681 }
1682 
1684 {
1685  return d->mCategory;
1686 }
1687 
1689 {
1690  d->mEventID = id;
1691 }
1692 
1694 {
1695  return d->mEventID;
1696 }
1697 
1699 {
1700  ++d->mRevision;
1701 }
1702 
1704 {
1705  return d->mRevision;
1706 }
1707 
1708 void KAEvent::setResourceId(ResourceId id)
1709 {
1710  d->mResourceId = id;
1711 }
1712 
1713 void KAEvent::setResourceId_const(ResourceId id) const
1714 {
1715  d->mResourceId = id;
1716 }
1717 
1718 ResourceId KAEvent::resourceId() const
1719 {
1720  // A displaying alarm contains the event's original resource ID
1721  return d->mDisplaying ? -1 : d->mResourceId;
1722 }
1723 
1725 {
1726  setResourceId(id);
1727 }
1728 
1730 {
1731  setResourceId_const(id);
1732 }
1733 
1735 {
1736  // A displaying alarm contains the event's original collection ID
1737  return d->mDisplaying ? -1 : d->mResourceId;
1738 }
1739 
1741 {
1742  d->mItemId = id;
1743 }
1744 
1746 {
1747  return d->mItemId;
1748 }
1749 
1750 /******************************************************************************
1751 * Initialise an Item with the event.
1752 * Note that the event is not updated with the Item ID.
1753 * Reply = true if successful,
1754 * false if event's category does not match collection's mime types.
1755 */
1756 bool KAEvent::setItemPayload(Akonadi::Item &item, const QStringList &collectionMimeTypes) const
1757 {
1758  return KAlarmCal::setItemPayload(item, *this, collectionMimeTypes);
1759 }
1760 
1762 {
1763  d->mCompatibility = c;
1764 }
1765 
1767 {
1768  return d->mCompatibility;
1769 }
1770 
1772 {
1773  return d->mCustomProperties;
1774 }
1775 
1777 {
1778  return d->mActionSubType;
1779 }
1780 
1782 {
1783  switch (d->mActionSubType) {
1784  case MESSAGE:
1785  case FILE: return ACT_DISPLAY;
1786  case COMMAND: return d->mCommandDisplay ? ACT_DISPLAY_COMMAND : ACT_COMMAND;
1787  case EMAIL: return ACT_EMAIL;
1788  case AUDIO: return ACT_AUDIO;
1789  default: return ACT_NONE;
1790  }
1791 }
1792 
1793 void KAEvent::setLateCancel(int minutes)
1794 {
1795  if (d->mRepeatAtLogin) {
1796  minutes = 0;
1797  }
1798  d->mLateCancel = minutes;
1799  if (!minutes) {
1800  d->mAutoClose = false;
1801  }
1802 }
1803 
1805 {
1806  return d->mLateCancel;
1807 }
1808 
1810 {
1811  d->mAutoClose = ac;
1812 }
1813 
1815 {
1816  return d->mAutoClose;
1817 }
1818 
1819 void KAEvent::setNotify(bool useNotify)
1820 {
1821  d->mNotify = useNotify;
1822 }
1823 
1824 bool KAEvent::notify() const
1825 {
1826  return d->mNotify;
1827 }
1828 
1830 {
1831  d->mAkonadiItemId = id;
1832 }
1833 
1835 {
1836  return d->mAkonadiItemId;
1837 }
1838 
1839 void KAEvent::setName(const QString& newName)
1840 {
1841  d->mName = newName;
1842 }
1843 
1845 {
1846  return d->mName;
1847 }
1848 
1850 {
1851  return d->mText;
1852 }
1853 
1855 {
1856  return (d->mActionSubType == MESSAGE
1857  || d->mActionSubType == EMAIL) ? d->mText : QString();
1858 }
1859 
1861 {
1862  return (d->mActionSubType == MESSAGE) ? d->mText : QString();
1863 }
1864 
1866 {
1867  return (d->mActionSubType == FILE) ? d->mText : QString();
1868 }
1869 
1871 {
1872  return d->mBgColour;
1873 }
1874 
1876 {
1877  return d->mFgColour;
1878 }
1879 
1881 {
1882  KAEventPrivate::mDefaultFont = f;
1883 }
1884 
1886 {
1887  return d->mUseDefaultFont;
1888 }
1889 
1891 {
1892  return d->mUseDefaultFont ? KAEventPrivate::mDefaultFont : d->mFont;
1893 }
1894 
1896 {
1897  return (d->mActionSubType == COMMAND) ? d->mText : QString();
1898 }
1899 
1901 {
1902  return d->mCommandScript;
1903 }
1904 
1906 {
1907  return d->mCommandXterm;
1908 }
1909 
1911 {
1912  return d->mCommandDisplay;
1913 }
1914 
1916 {
1917  d->mCommandError = t;
1918 }
1919 
1921 {
1922  return d->mCommandError;
1923 }
1924 
1926 {
1927  return d->mCommandHideError;
1928 }
1929 
1930 void KAEvent::setLogFile(const QString &logfile)
1931 {
1932  d->mLogFile = logfile;
1933  if (!logfile.isEmpty()) {
1934  d->mCommandDisplay = d->mCommandXterm = false;
1935  }
1936 }
1937 
1939 {
1940  return d->mLogFile;
1941 }
1942 
1944 {
1945  return d->mConfirmAck;
1946 }
1947 
1949 {
1950  return d->mCopyToKOrganizer;
1951 }
1952 
1953 void KAEvent::setEmail(uint from, const KCalendarCore::Person::List &addresses, const QString &subject,
1954  const QStringList &attachments)
1955 {
1956  d->mEmailFromIdentity = from;
1957  d->mEmailAddresses = addresses;
1958  d->mEmailSubject = subject;
1959  d->mEmailAttachments = attachments;
1960 }
1961 
1963 {
1964  return (d->mActionSubType == EMAIL) ? d->mText : QString();
1965 }
1966 
1968 {
1969  return d->mEmailFromIdentity;
1970 }
1971 
1973 {
1974  return d->mEmailAddresses;
1975 }
1976 
1978 {
1979  return static_cast<QStringList>(d->mEmailAddresses);
1980 }
1981 
1983 {
1984  return d->mEmailAddresses.join(sep);
1985 }
1986 
1988 {
1989  return EmailAddressList(addresses).join(separator);
1990 }
1991 
1993 {
1994  return d->mEmailAddresses.pureAddresses();
1995 }
1996 
1998 {
1999  return d->mEmailAddresses.pureAddresses(sep);
2000 }
2001 
2003 {
2004  return d->mEmailSubject;
2005 }
2006 
2008 {
2009  return d->mEmailAttachments;
2010 }
2011 
2013 {
2014  return d->mEmailAttachments.join(sep);
2015 }
2016 
2017 bool KAEvent::emailBcc() const
2018 {
2019  return d->mEmailBcc;
2020 }
2021 
2022 void KAEvent::setAudioFile(const QString &filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile)
2023 {
2024  d->setAudioFile(filename, volume, fadeVolume, fadeSeconds, repeatPause, allowEmptyFile);
2025 }
2026 
2027 void KAEventPrivate::setAudioFile(const QString &filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile)
2028 {
2029  mAudioFile = filename;
2030  mSoundVolume = (!allowEmptyFile && filename.isEmpty()) ? -1 : volume;
2031  if (mSoundVolume >= 0) {
2032  mFadeVolume = (fadeSeconds > 0) ? fadeVolume : -1;
2033  mFadeSeconds = (mFadeVolume >= 0) ? fadeSeconds : 0;
2034  } else {
2035  mFadeVolume = -1;
2036  mFadeSeconds = 0;
2037  }
2038  mRepeatSoundPause = repeatPause;
2039 }
2040 
2042 {
2043  return d->mAudioFile;
2044 }
2045 
2047 {
2048  return d->mSoundVolume;
2049 }
2050 
2051 float KAEvent::fadeVolume() const
2052 {
2053  return d->mSoundVolume >= 0 && d->mFadeSeconds ? d->mFadeVolume : -1;
2054 }
2055 
2057 {
2058  return d->mSoundVolume >= 0 && d->mFadeVolume >= 0 ? d->mFadeSeconds : 0;
2059 }
2060 
2062 {
2063  return d->mRepeatSoundPause >= 0;
2064 }
2065 
2067 {
2068  return d->mRepeatSoundPause;
2069 }
2070 
2071 bool KAEvent::beep() const
2072 {
2073  return d->mBeep;
2074 }
2075 
2076 bool KAEvent::speak() const
2077 {
2078  return (d->mActionSubType == MESSAGE
2079  || (d->mActionSubType == COMMAND && d->mCommandDisplay))
2080  && d->mSpeak;
2081 }
2082 
2083 /******************************************************************************
2084 * Set the event to be an alarm template.
2085 */
2086 void KAEvent::setTemplate(const QString &name, int afterTime)
2087 {
2088  d->setCategory(CalEvent::TEMPLATE);
2089  d->mName = name;
2090  d->mTemplateAfterTime = afterTime;
2091  d->mTriggerChanged = true; // templates and archived don't have trigger times
2092 }
2093 
2095 {
2096  return d->mCategory == CalEvent::TEMPLATE;
2097 }
2098 
2100 {
2101  return d->mName;
2102 }
2103 
2105 {
2106  return d->mTemplateAfterTime == 0;
2107 }
2108 
2110 {
2111  return d->mTemplateAfterTime;
2112 }
2113 
2114 void KAEvent::setActions(const QString &pre, const QString &post, ExtraActionOptions options)
2115 {
2116  d->mPreAction = pre;
2117  d->mPostAction = post;
2118  d->mExtraActionOptions = options;
2119 }
2120 
2122 {
2123  return d->mPreAction;
2124 }
2125 
2127 {
2128  return d->mPostAction;
2129 }
2130 
2132 {
2133  return d->mExtraActionOptions;
2134 }
2135 
2136 /******************************************************************************
2137 * Set a reminder.
2138 * 'minutes' = number of minutes BEFORE the main alarm.
2139 */
2140 void KAEvent::setReminder(int minutes, bool onceOnly)
2141 {
2142  d->setReminder(minutes, onceOnly);
2143 }
2144 
2145 void KAEventPrivate::setReminder(int minutes, bool onceOnly)
2146 {
2147  if (minutes > 0 && mRepeatAtLogin) {
2148  minutes = 0;
2149  }
2150  if (minutes != mReminderMinutes || (minutes && mReminderActive != ACTIVE_REMINDER)) {
2151  const ReminderType oldReminderActive = mReminderActive;
2152  mReminderMinutes = minutes;
2153  mReminderActive = minutes > 0 ? ACTIVE_REMINDER : NO_REMINDER;
2154  mReminderOnceOnly = onceOnly;
2155  mReminderAfterTime = DateTime();
2156  if (mReminderActive != NO_REMINDER && oldReminderActive == NO_REMINDER) {
2157  ++mAlarmCount;
2158  } else if (mReminderActive == NO_REMINDER && oldReminderActive != NO_REMINDER) {
2159  --mAlarmCount;
2160  }
2161  mTriggerChanged = true;
2162  }
2163 }
2164 
2165 /******************************************************************************
2166 * Activate the event's reminder which occurs AFTER the given main alarm time.
2167 * Reply = true if successful (i.e. reminder falls before the next main alarm).
2168 */
2169 void KAEvent::activateReminderAfter(const DateTime &mainAlarmTime)
2170 {
2171  d->activateReminderAfter(mainAlarmTime);
2172 }
2173 
2174 void KAEventPrivate::activateReminderAfter(const DateTime &mainAlarmTime)
2175 {
2176  if (mReminderMinutes >= 0 || mReminderActive == ACTIVE_REMINDER || !mainAlarmTime.isValid()) {
2177  return;
2178  }
2179  // There is a reminder AFTER the main alarm.
2180  if (checkRecur() != KARecurrence::NO_RECUR) {
2181  // For a recurring alarm, the given alarm time must be a recurrence, not a sub-repetition.
2182  DateTime next;
2183  //???? For some unknown reason, addSecs(-1) returns the recurrence after the next,
2184  //???? so addSecs(-60) is used instead.
2185  if (nextRecurrence(mainAlarmTime.addSecs(-60).effectiveKDateTime(), next) == KAEvent::NO_OCCURRENCE
2186  || mainAlarmTime != next) {
2187  return;
2188  }
2189  } else if (!mRepeatAtLogin) {
2190  // For a non-recurring alarm, the given alarm time must be the main alarm time.
2191  if (mainAlarmTime != mStartDateTime) {
2192  return;
2193  }
2194  }
2195 
2196  const DateTime reminderTime = mainAlarmTime.addMins(-mReminderMinutes);
2197  DateTime next;
2199  && reminderTime >= next) {
2200  return; // the reminder time is after the next occurrence of the main alarm
2201  }
2202 
2203  qCDebug(KALARMCAL_LOG) << "Setting reminder at" << reminderTime.effectiveKDateTime().toString(QStringLiteral("%Y-%m-%d %H:%M"));
2204  activate_reminder(true);
2205  mReminderAfterTime = reminderTime;
2206 }
2207 
2209 {
2210  return d->mReminderMinutes;
2211 }
2212 
2214 {
2215  return d->mReminderActive == KAEventPrivate::ACTIVE_REMINDER;
2216 }
2217 
2219 {
2220  return d->mReminderOnceOnly;
2221 }
2222 
2224 {
2225  return d->mDeferral == KAEventPrivate::REMINDER_DEFERRAL;
2226 }
2227 
2228 /******************************************************************************
2229 * Defer the event to the specified time.
2230 * If the main alarm time has passed, the main alarm is marked as expired.
2231 * If 'adjustRecurrence' is true, ensure that the next scheduled recurrence is
2232 * after the current time.
2233 */
2234 void KAEvent::defer(const DateTime &dt, bool reminder, bool adjustRecurrence)
2235 {
2236  d->defer(dt, reminder, adjustRecurrence);
2237 }
2238 
2239 void KAEventPrivate::defer(const DateTime &dateTime, bool reminder, bool adjustRecurrence)
2240 {
2241  startChanges(); // prevent multiple trigger time evaluation here
2242  bool setNextRepetition = false;
2243  bool checkRepetition = false;
2244  bool checkReminderAfter = false;
2245  if (checkRecur() == KARecurrence::NO_RECUR) {
2246  // Deferring a non-recurring alarm
2247  if (mReminderMinutes) {
2248  bool deferReminder = false;
2249  if (mReminderMinutes > 0) {
2250  // There's a reminder BEFORE the main alarm
2251  if (dateTime < mNextMainDateTime.effectiveKDateTime()) {
2252  deferReminder = true;
2253  } else if (mReminderActive == ACTIVE_REMINDER || mDeferral == REMINDER_DEFERRAL) {
2254  // Deferring past the main alarm time, so adjust any existing deferral
2255  set_deferral(NO_DEFERRAL);
2256  mTriggerChanged = true;
2257  }
2258  } else if (mReminderMinutes < 0 && reminder) {
2259  deferReminder = true; // deferring a reminder AFTER the main alarm
2260  }
2261  if (deferReminder) {
2262  set_deferral(REMINDER_DEFERRAL); // defer reminder alarm
2263  mDeferralTime = dateTime;
2264  mTriggerChanged = true;
2265  }
2266  if (mReminderActive == ACTIVE_REMINDER) {
2267  activate_reminder(false);
2268  mTriggerChanged = true;
2269  }
2270  }
2271  if (mDeferral != REMINDER_DEFERRAL) {
2272  // We're deferring the main alarm.
2273  // Main alarm has now expired.
2274  mNextMainDateTime = mDeferralTime = dateTime;
2275  set_deferral(NORMAL_DEFERRAL);
2276  mTriggerChanged = true;
2277  checkReminderAfter = true;
2278  if (!mMainExpired) {
2279  // Mark the alarm as expired now
2280  mMainExpired = true;
2281  --mAlarmCount;
2282  if (mRepeatAtLogin) {
2283  // Remove the repeat-at-login alarm, but keep a note of it for archiving purposes
2284  mArchiveRepeatAtLogin = true;
2285  mRepeatAtLogin = false;
2286  --mAlarmCount;
2287  }
2288  }
2289  }
2290  } else if (reminder) {
2291  // Deferring a reminder for a recurring alarm
2292  if (dateTime >= mNextMainDateTime.effectiveKDateTime()) {
2293  // Trying to defer it past the next main alarm (regardless of whether
2294  // the reminder triggered before or after the main alarm).
2295  set_deferral(NO_DEFERRAL); // (error)
2296  } else {
2297  set_deferral(REMINDER_DEFERRAL);
2298  mDeferralTime = dateTime;
2299  checkRepetition = true;
2300  }
2301  mTriggerChanged = true;
2302  } else {
2303  // Deferring a recurring alarm
2304  mDeferralTime = dateTime;
2305  if (mDeferral == NO_DEFERRAL) {
2306  set_deferral(NORMAL_DEFERRAL);
2307  }
2308  mTriggerChanged = true;
2309  checkReminderAfter = true;
2310  if (adjustRecurrence) {
2312  if (mainEndRepeatTime() < now) {
2313  // The last repetition (if any) of the current recurrence has already passed.
2314  // Adjust to the next scheduled recurrence after now.
2315  if (!mMainExpired && setNextOccurrence(now) == KAEvent::NO_OCCURRENCE) {
2316  mMainExpired = true;
2317  --mAlarmCount;
2318  }
2319  } else {
2320  setNextRepetition = mRepetition;
2321  }
2322  } else {
2323  checkRepetition = true;
2324  }
2325  }
2326  if (checkReminderAfter && mReminderMinutes < 0 && mReminderActive != NO_REMINDER) {
2327  // Enable/disable the active reminder AFTER the main alarm,
2328  // depending on whether the deferral is before or after the reminder.
2329  mReminderActive = (mDeferralTime < mReminderAfterTime) ? ACTIVE_REMINDER : HIDDEN_REMINDER;
2330  }
2331  if (checkRepetition) {
2332  setNextRepetition = (mRepetition && mDeferralTime < mainEndRepeatTime());
2333  }
2334  if (setNextRepetition) {
2335  // The alarm is repeated, and we're deferring to a time before the last repetition.
2336  // Set the next scheduled repetition to the one after the deferral.
2337  if (mNextMainDateTime >= mDeferralTime) {
2338  mNextRepeat = 0;
2339  } else {
2340  mNextRepeat = mRepetition.nextRepeatCount(mNextMainDateTime.kDateTime(), mDeferralTime.kDateTime());
2341  }
2342  mTriggerChanged = true;
2343  }
2344  endChanges();
2345 }
2346 
2347 /******************************************************************************
2348 * Cancel any deferral alarm.
2349 */
2351 {
2352  d->cancelDefer();
2353 }
2354 
2355 void KAEventPrivate::cancelDefer()
2356 {
2357  if (mDeferral != NO_DEFERRAL) {
2358  mDeferralTime = DateTime();
2359  set_deferral(NO_DEFERRAL);
2360  mTriggerChanged = true;
2361  }
2362 }
2363 
2364 void KAEvent::setDeferDefaultMinutes(int minutes, bool dateOnly)
2365 {
2366  d->mDeferDefaultMinutes = minutes;
2367  d->mDeferDefaultDateOnly = dateOnly;
2368 }
2369 
2370 bool KAEvent::deferred() const
2371 {
2372  return d->mDeferral != KAEventPrivate::NO_DEFERRAL;
2373 }
2374 
2376 {
2377  return d->mDeferralTime;
2378 }
2379 
2380 /******************************************************************************
2381 * Find the latest time which the alarm can currently be deferred to.
2382 */
2384 {
2385  return d->deferralLimit(limitType);
2386 }
2387 
2388 DateTime KAEventPrivate::deferralLimit(KAEvent::DeferLimitType *limitType) const
2389 {
2391  DateTime endTime;
2392  if (checkRecur() != KARecurrence::NO_RECUR) {
2393  // It's a recurring alarm. Find the latest time it can be deferred to:
2394  // it cannot be deferred past its next occurrence or sub-repetition,
2395  // or any advance reminder before that.
2396  DateTime reminderTime;
2398  const KAEvent::OccurType type = nextOccurrence(now, endTime, KAEvent::RETURN_REPETITION);
2399  if (type & KAEvent::OCCURRENCE_REPEAT) {
2400  ltype = KAEvent::LIMIT_REPETITION;
2401  } else if (type == KAEvent::NO_OCCURRENCE) {
2402  ltype = KAEvent::LIMIT_NONE;
2403  } else if (mReminderActive == ACTIVE_REMINDER && mReminderMinutes > 0
2404  && (now < (reminderTime = endTime.addMins(-mReminderMinutes)))) {
2405  endTime = reminderTime;
2406  ltype = KAEvent::LIMIT_REMINDER;
2407  } else {
2408  ltype = KAEvent::LIMIT_RECURRENCE;
2409  }
2410  } else if (mReminderMinutes < 0) {
2411  // There is a reminder alarm which occurs AFTER the main alarm.
2412  // Don't allow the reminder to be deferred past the next main alarm time.
2413  if (KADateTime::currentUtcDateTime() < mNextMainDateTime.effectiveKDateTime()) {
2414  endTime = mNextMainDateTime;
2415  ltype = KAEvent::LIMIT_MAIN;
2416  }
2417  } else if (mReminderMinutes > 0
2418  && KADateTime::currentUtcDateTime() < mNextMainDateTime.effectiveKDateTime()) {
2419  // It's a reminder BEFORE the main alarm.
2420  // Don't allow it to be deferred past its main alarm time.
2421  endTime = mNextMainDateTime;
2422  ltype = KAEvent::LIMIT_MAIN;
2423  }
2424  if (ltype != KAEvent::LIMIT_NONE) {
2425  endTime = endTime.addMins(-1);
2426  }
2427  if (limitType) {
2428  *limitType = ltype;
2429  }
2430  return endTime;
2431 }
2432 
2434 {
2435  return d->mDeferDefaultMinutes;
2436 }
2437 
2439 {
2440  return d->mDeferDefaultDateOnly;
2441 }
2442 
2444 {
2445  return d->mStartDateTime;
2446 }
2447 
2449 {
2450  d->mNextMainDateTime = dt;
2451  d->mTriggerChanged = true;
2452 }
2453 
2454 DateTime KAEvent::mainDateTime(bool withRepeats) const
2455 {
2456  return d->mainDateTime(withRepeats);
2457 }
2458 
2460 {
2461  return d->mNextMainDateTime.effectiveTime();
2462 }
2463 
2465 {
2466  return d->mainEndRepeatTime();
2467 }
2468 
2469 /******************************************************************************
2470 * Set the start-of-day time for date-only alarms.
2471 */
2472 void KAEvent::setStartOfDay(const QTime &startOfDay)
2473 {
2474  DateTime::setStartOfDay(startOfDay);
2475 #pragma message("Does this need all trigger times for date-only alarms to be recalculated?")
2476 }
2477 
2478 /******************************************************************************
2479 * Called when the user changes the start-of-day time.
2480 * Adjust the start time of the recurrence to match, for each date-only event in
2481 * a list.
2482 */
2484 {
2485  for (KAEvent *event : events) {
2486  KAEventPrivate *const p = event->d;
2487  if (p->mStartDateTime.isDateOnly() && p->checkRecur() != KARecurrence::NO_RECUR) {
2488  p->mRecurrence->setStartDateTime(p->mStartDateTime.effectiveKDateTime(), true);
2489  }
2490  }
2491 }
2492 
2494 {
2495  d->calcTriggerTimes();
2496  switch (type) {
2497  case ALL_TRIGGER: return d->mAllTrigger;
2498  case MAIN_TRIGGER: return d->mMainTrigger;
2499  case ALL_WORK_TRIGGER: return d->mAllWorkTrigger;
2500  case WORK_TRIGGER: return d->mMainWorkTrigger;
2501  case DISPLAY_TRIGGER: {
2502  const bool reminderAfter = d->mMainExpired && d->mReminderActive && d->mReminderMinutes < 0;
2503  return d->checkRecur() != KARecurrence::NO_RECUR && (d->mWorkTimeOnly || d->mExcludeHolidays)
2504  ? (reminderAfter ? d->mAllWorkTrigger : d->mMainWorkTrigger)
2505  : (reminderAfter ? d->mAllTrigger : d->mMainTrigger);
2506  }
2507  default: return {};
2508  }
2509 }
2510 
2512 {
2513  d->mCreatedDateTime = dt;
2514 }
2515 
2517 {
2518  return d->mCreatedDateTime;
2519 }
2520 
2521 /******************************************************************************
2522 * Set or clear repeat-at-login.
2523 */
2525 {
2526  d->setRepeatAtLogin(rl);
2527 }
2528 
2529 void KAEventPrivate::setRepeatAtLogin(bool rl)
2530 {
2531  if (rl && !mRepeatAtLogin) {
2532  setRepeatAtLoginTrue(true); // clear incompatible statuses
2533  ++mAlarmCount;
2534  } else if (!rl && mRepeatAtLogin) {
2535  --mAlarmCount;
2536  }
2537  mRepeatAtLogin = rl;
2538  mTriggerChanged = true;
2539 }
2540 
2541 /******************************************************************************
2542 * Clear incompatible statuses when repeat-at-login is set.
2543 */
2544 void KAEventPrivate::setRepeatAtLoginTrue(bool clearReminder)
2545 {
2546  clearRecur(); // clear recurrences
2547  if (mReminderMinutes >= 0 && clearReminder) {
2548  setReminder(0, false); // clear pre-alarm reminder
2549  }
2550  mLateCancel = 0;
2551  mAutoClose = false;
2552  mCopyToKOrganizer = false;
2553 }
2554 
2555 bool KAEvent::repeatAtLogin(bool includeArchived) const
2556 {
2557  return d->mRepeatAtLogin || (includeArchived && d->mArchiveRepeatAtLogin);
2558 }
2559 
2561 {
2562  d->mExcludeHolidays = ex;
2563  d->mExcludeHolidayRegion = KAEventPrivate::holidays();
2564  // Option only affects recurring alarms
2565  d->mTriggerChanged = (d->checkRecur() != KARecurrence::NO_RECUR);
2566 }
2567 
2569 {
2570  return d->mExcludeHolidays;
2571 }
2572 
2573 /******************************************************************************
2574 * Set a new holiday region.
2575 * Alarms which exclude holidays record the pointer to the holiday definition
2576 * at the time their next trigger times were last calculated. The change in
2577 * holiday definition pointer will cause their next trigger times to be
2578 * recalculated.
2579 */
2581 {
2582  KAEventPrivate::mHolidays.reset(new HolidayRegion(h.regionCode()));
2583 }
2584 
2586 {
2587  d->mWorkTimeOnly = wto;
2588  // Option only affects recurring alarms
2589  d->mTriggerChanged = (d->checkRecur() != KARecurrence::NO_RECUR);
2590 }
2591 
2593 {
2594  return d->mWorkTimeOnly;
2595 }
2596 
2597 /******************************************************************************
2598 * Check whether a date/time conflicts with working hours and/or holiday
2599 * restrictions for the alarm.
2600 */
2602 {
2603  return d->excludedByWorkTimeOrHoliday(dt);
2604 }
2605 
2606 bool KAEvent::isWorkingTime(const KADateTime &dt) const
2607 {
2608  return !excludedByWorkTimeOrHoliday(dt);
2609 }
2610 
2611 bool KAEventPrivate::excludedByWorkTimeOrHoliday(const KADateTime &dt) const
2612 {
2613  if ((mWorkTimeOnly && !mWorkDays.testBit(dt.date().dayOfWeek() - 1))
2614  || (mExcludeHolidays && holidays()->isHoliday(dt.date()))) {
2615  return true;
2616  }
2617  if (!mWorkTimeOnly) {
2618  return false;
2619  }
2620  return !dt.isDateOnly()
2621  && (dt.time() < mWorkDayStart || dt.time() >= mWorkDayEnd);
2622 }
2623 
2624 /******************************************************************************
2625 * Set new working days and times.
2626 * Increment a counter so that working-time-only alarms can detect that they
2627 * need to update their next trigger time.
2628 */
2629 void KAEvent::setWorkTime(const QBitArray &days, const QTime &start, const QTime &end)
2630 {
2631  if (days != KAEventPrivate::mWorkDays || start != KAEventPrivate::mWorkDayStart || end != KAEventPrivate::mWorkDayEnd) {
2632  KAEventPrivate::mWorkDays = days;
2633  KAEventPrivate::mWorkDayStart = start;
2634  KAEventPrivate::mWorkDayEnd = end;
2635  if (!++KAEventPrivate::mWorkTimeIndex) {
2636  ++KAEventPrivate::mWorkTimeIndex;
2637  }
2638  }
2639 }
2640 
2641 /******************************************************************************
2642 * Clear the event's recurrence and alarm repetition data.
2643 */
2645 {
2646  d->clearRecur();
2647 }
2648 
2649 void KAEventPrivate::clearRecur()
2650 {
2651  if (mRecurrence || mRepetition) {
2652  delete mRecurrence;
2653  mRecurrence = nullptr;
2654  mRepetition.set(0, 0);
2655  mTriggerChanged = true;
2656  }
2657  mNextRepeat = 0;
2658 }
2659 
2660 /******************************************************************************
2661 * Initialise the event's recurrence from a KCalendarCore::Recurrence.
2662 * The event's start date/time is not changed.
2663 */
2665 {
2666  d->setRecurrence(recurrence);
2667 }
2668 
2669 void KAEventPrivate::setRecurrence(const KARecurrence &recurrence)
2670 {
2671  startChanges(); // prevent multiple trigger time evaluation here
2672  if (recurrence.recurs()) {
2673  delete mRecurrence;
2674  mRecurrence = new KARecurrence(recurrence);
2675  mRecurrence->setStartDateTime(mStartDateTime.effectiveKDateTime(), mStartDateTime.isDateOnly());
2676  mTriggerChanged = true;
2677 
2678  // Adjust sub-repetition values to fit the recurrence.
2679  setRepetition(mRepetition);
2680  } else {
2681  clearRecur();
2682  }
2683 
2684  endChanges();
2685 }
2686 
2687 /******************************************************************************
2688 * Set the recurrence to recur at a minutes interval.
2689 * Parameters:
2690 * freq = how many minutes between recurrences.
2691 * count = number of occurrences, including first and last.
2692 * = -1 to recur indefinitely.
2693 * = 0 to use 'end' instead.
2694 * end = end date/time (invalid to use 'count' instead).
2695 * Reply = false if no recurrence was set up.
2696 */
2697 bool KAEvent::setRecurMinutely(int freq, int count, const KADateTime &end)
2698 {
2699  const bool success = d->setRecur(RecurrenceRule::rMinutely, freq, count, end);
2700  d->mTriggerChanged = true;
2701  return success;
2702 }
2703 
2704 /******************************************************************************
2705 * Set the recurrence to recur daily.
2706 * Parameters:
2707 * freq = how many days between recurrences.
2708 * days = which days of the week alarms are allowed to occur on.
2709 * count = number of occurrences, including first and last.
2710 * = -1 to recur indefinitely.
2711 * = 0 to use 'end' instead.
2712 * end = end date (invalid to use 'count' instead).
2713 * Reply = false if no recurrence was set up.
2714 */
2715 bool KAEvent::setRecurDaily(int freq, const QBitArray &days, int count, QDate end)
2716 {
2717  const bool success = d->setRecur(RecurrenceRule::rDaily, freq, count, end);
2718  if (success) {
2719  if (days.size() != 7) {
2720  qCWarning(KALARMCAL_LOG) << "KAEvent::setRecurDaily: Error! 'days' parameter must have 7 elements: actual size" << days.size();
2721  } else {
2722  int n = days.count(true); // number of days when alarms occur
2723  if (n < 7) {
2724  d->mRecurrence->addWeeklyDays(days);
2725  }
2726  }
2727  }
2728  d->mTriggerChanged = true;
2729  return success;
2730 }
2731 
2732 /******************************************************************************
2733 * Set the recurrence to recur weekly, on the specified weekdays.
2734 * Parameters:
2735 * freq = how many weeks between recurrences.
2736 * days = which days of the week alarms should occur on.
2737 * count = number of occurrences, including first and last.
2738 * = -1 to recur indefinitely.
2739 * = 0 to use 'end' instead.
2740 * end = end date (invalid to use 'count' instead).
2741 * Reply = false if no recurrence was set up.
2742 */
2743 bool KAEvent::setRecurWeekly(int freq, const QBitArray &days, int count, QDate end)
2744 {
2745  const bool success = d->setRecur(RecurrenceRule::rWeekly, freq, count, end);
2746  if (success) {
2747  d->mRecurrence->addWeeklyDays(days);
2748  }
2749  d->mTriggerChanged = true;
2750  return success;
2751 }
2752 
2753 /******************************************************************************
2754 * Set the recurrence to recur monthly, on the specified days within the month.
2755 * Parameters:
2756 * freq = how many months between recurrences.
2757 * days = which days of the month alarms should occur on.
2758 * count = number of occurrences, including first and last.
2759 * = -1 to recur indefinitely.
2760 * = 0 to use 'end' instead.
2761 * end = end date (invalid to use 'count' instead).
2762 * Reply = false if no recurrence was set up.
2763 */
2764 bool KAEvent::setRecurMonthlyByDate(int freq, const QVector<int> &days, int count, QDate end)
2765 {
2766  const bool success = d->setRecur(RecurrenceRule::rMonthly, freq, count, end);
2767  if (success) {
2768  for (int day : days) {
2769  d->mRecurrence->addMonthlyDate(day);
2770  }
2771  }
2772  d->mTriggerChanged = true;
2773  return success;
2774 }
2775 
2776 /******************************************************************************
2777 * Set the recurrence to recur monthly, on the specified weekdays in the
2778 * specified weeks of the month.
2779 * Parameters:
2780 * freq = how many months between recurrences.
2781 * posns = which days of the week/weeks of the month alarms should occur on.
2782 * count = number of occurrences, including first and last.
2783 * = -1 to recur indefinitely.
2784 * = 0 to use 'end' instead.
2785 * end = end date (invalid to use 'count' instead).
2786 * Reply = false if no recurrence was set up.
2787 */
2788 bool KAEvent::setRecurMonthlyByPos(int freq, const QVector<MonthPos> &posns, int count, QDate end)
2789 {
2790  const bool success = d->setRecur(RecurrenceRule::rMonthly, freq, count, end);
2791  if (success) {
2792  for (const MonthPos& posn : posns) {
2793  d->mRecurrence->addMonthlyPos(posn.weeknum, posn.days);
2794  }
2795  }
2796  d->mTriggerChanged = true;
2797  return success;
2798 }
2799 
2800 /******************************************************************************
2801 * Set the recurrence to recur annually, on the specified start date in each
2802 * of the specified months.
2803 * Parameters:
2804 * freq = how many years between recurrences.
2805 * months = which months of the year alarms should occur on.
2806 * day = day of month, or 0 to use start date
2807 * feb29 = when February 29th should recur in non-leap years.
2808 * count = number of occurrences, including first and last.
2809 * = -1 to recur indefinitely.
2810 * = 0 to use 'end' instead.
2811 * end = end date (invalid to use 'count' instead).
2812 * Reply = false if no recurrence was set up.
2813 */
2814 bool KAEvent::setRecurAnnualByDate(int freq, const QVector<int> &months, int day, KARecurrence::Feb29Type feb29, int count, QDate end)
2815 {
2816  const bool success = d->setRecur(RecurrenceRule::rYearly, freq, count, end, feb29);
2817  if (success) {
2818  for (int month : months) {
2819  d->mRecurrence->addYearlyMonth(month);
2820  }
2821  if (day) {
2822  d->mRecurrence->addMonthlyDate(day);
2823  }
2824  }
2825  d->mTriggerChanged = true;
2826  return success;
2827 }
2828 
2829 /******************************************************************************
2830 * Set the recurrence to recur annually, on the specified weekdays in the
2831 * specified weeks of the specified months.
2832 * Parameters:
2833 * freq = how many years between recurrences.
2834 * posns = which days of the week/weeks of the month alarms should occur on.
2835 * months = which months of the year alarms should occur on.
2836 * count = number of occurrences, including first and last.
2837 * = -1 to recur indefinitely.
2838 * = 0 to use 'end' instead.
2839 * end = end date (invalid to use 'count' instead).
2840 * Reply = false if no recurrence was set up.
2841 */
2842 bool KAEvent::setRecurAnnualByPos(int freq, const QVector<MonthPos> &posns, const QVector<int> &months, int count, QDate end)
2843 {
2844  const bool success = d->setRecur(RecurrenceRule::rYearly, freq, count, end);
2845  if (success) {
2846  for (int month : months) {
2847  d->mRecurrence->addYearlyMonth(month);
2848  }
2849  for (const MonthPos& posn : posns) {
2850  d->mRecurrence->addYearlyPos(posn.weeknum, posn.days);
2851  }
2852  }
2853  d->mTriggerChanged = true;
2854  return success;
2855 }
2856 
2857 /******************************************************************************
2858 * Initialise the event's recurrence data.
2859 * Parameters:
2860 * freq = how many intervals between recurrences.
2861 * count = number of occurrences, including first and last.
2862 * = -1 to recur indefinitely.
2863 * = 0 to use 'end' instead.
2864 * end = end date/time (invalid to use 'count' instead).
2865 * Reply = false if no recurrence was set up.
2866 */
2867 bool KAEventPrivate::setRecur(KCalendarCore::RecurrenceRule::PeriodType recurType, int freq, int count, QDate end, KARecurrence::Feb29Type feb29)
2868 {
2869  KADateTime edt = mNextMainDateTime.kDateTime();
2870  edt.setDate(end);
2871  return setRecur(recurType, freq, count, edt, feb29);
2872 }
2873 bool KAEventPrivate::setRecur(KCalendarCore::RecurrenceRule::PeriodType recurType, int freq, int count, const KADateTime &end, KARecurrence::Feb29Type feb29)
2874 {
2875  if (count >= -1 && (count || end.date().isValid())) {
2876  if (!mRecurrence) {
2877  mRecurrence = new KARecurrence;
2878  }
2879  if (mRecurrence->init(recurType, freq, count, mNextMainDateTime.kDateTime(), end, feb29)) {
2880  return true;
2881  }
2882  }
2883  clearRecur();
2884  return false;
2885 }
2886 
2887 bool KAEvent::recurs() const
2888 {
2889  return d->checkRecur() != KARecurrence::NO_RECUR;
2890 }
2891 
2893 {
2894  return d->checkRecur();
2895 }
2896 
2898 {
2899  return d->mRecurrence;
2900 }
2901 
2902 /******************************************************************************
2903 * Return the recurrence interval in units of the recurrence period type.
2904 */
2906 {
2907  if (d->mRecurrence) {
2908  switch (d->mRecurrence->type()) {
2910  case KARecurrence::DAILY:
2911  case KARecurrence::WEEKLY:
2916  return d->mRecurrence->frequency();
2917  default:
2918  break;
2919  }
2920  }
2921  return 0;
2922 }
2923 
2925 {
2926  return d->mRecurrence ? d->mRecurrence->longestInterval() : Duration(0);
2927 }
2928 
2929 /******************************************************************************
2930 * Adjust the event date/time to the first recurrence of the event, on or after
2931 * start date/time. The event start date may not be a recurrence date, in which
2932 * case a later date will be set.
2933 */
2935 {
2936  d->setFirstRecurrence();
2937 }
2938 
2939 void KAEventPrivate::setFirstRecurrence()
2940 {
2941  switch (checkRecur()) {
2944  return;
2947  if (mRecurrence->yearMonths().isEmpty()) {
2948  return; // (presumably it's a template)
2949  }
2950  break;
2951  case KARecurrence::DAILY:
2952  case KARecurrence::WEEKLY:
2955  break;
2956  }
2957  const KADateTime recurStart = mRecurrence->startDateTime();
2958  if (mRecurrence->recursOn(recurStart.date(), recurStart.timeSpec())) {
2959  return; // it already recurs on the start date
2960  }
2961 
2962  // Set the frequency to 1 to find the first possible occurrence
2963  const int frequency = mRecurrence->frequency();
2964  mRecurrence->setFrequency(1);
2965  DateTime next;
2966  nextRecurrence(mNextMainDateTime.effectiveKDateTime(), next);
2967  if (!next.isValid()) {
2968  mRecurrence->setStartDateTime(recurStart, mStartDateTime.isDateOnly()); // reinstate the old value
2969  } else {
2970  mRecurrence->setStartDateTime(next.effectiveKDateTime(), next.isDateOnly());
2971  mStartDateTime = mNextMainDateTime = next;
2972  mTriggerChanged = true;
2973  }
2974  mRecurrence->setFrequency(frequency); // restore the frequency
2975 }
2976 
2977 /******************************************************************************
2978 * Return the recurrence interval as text suitable for display.
2979 */
2981 {
2982  if (d->mRepeatAtLogin) {
2983  return brief ? i18nc("@info Brief form of 'At Login'", "Login") : i18nc("@info", "At login");
2984  }
2985  if (d->mRecurrence) {
2986  QLocale locale;
2987  const int frequency = d->mRecurrence->frequency();
2988  switch (d->mRecurrence->defaultRRuleConst()->recurrenceType()) {
2989  case RecurrenceRule::rMinutely:
2990  if (frequency < 60) {
2991  return i18ncp("@info", "1 Minute", "%1 Minutes", frequency);
2992  } else if (frequency % 60 == 0) {
2993  return i18ncp("@info", "1 Hour", "%1 Hours", frequency / 60);
2994  } else {
2995  return i18nc("@info Hours and minutes", "%1h %2m", locale.toString(frequency / 60), locale.toString(frequency % 60));
2996  }
2997  case RecurrenceRule::rDaily:
2998  return i18ncp("@info", "1 Day", "%1 Days", frequency);
2999  case RecurrenceRule::rWeekly:
3000  return i18ncp("@info", "1 Week", "%1 Weeks", frequency);
3001  case RecurrenceRule::rMonthly:
3002  return i18ncp("@info", "1 Month", "%1 Months", frequency);
3003  case RecurrenceRule::rYearly:
3004  return i18ncp("@info", "1 Year", "%1 Years", frequency);
3005  case RecurrenceRule::rNone:
3006  default:
3007  break;
3008  }
3009  }
3010  return brief ? QString() : i18nc("@info No recurrence", "None");
3011 }
3012 
3013 /******************************************************************************
3014 * Initialise the event's sub-repetition.
3015 * The repetition length is adjusted if necessary to fit the recurrence interval.
3016 * If the event doesn't recur, the sub-repetition is cleared.
3017 * Reply = false if a non-daily interval was specified for a date-only recurrence.
3018 */
3020 {
3021  return d->setRepetition(r);
3022 }
3023 
3024 bool KAEventPrivate::setRepetition(const Repetition &repetition)
3025 {
3026  // Don't set mRepetition to zero at the start of this function, in case the
3027  // 'repetition' parameter passed in is a reference to mRepetition.
3028  mNextRepeat = 0;
3029  if (repetition && !mRepeatAtLogin) {
3030  Q_ASSERT(checkRecur() != KARecurrence::NO_RECUR);
3031  if (!repetition.isDaily() && mStartDateTime.isDateOnly()) {
3032  mRepetition.set(0, 0);
3033  return false; // interval must be in units of days for date-only alarms
3034  }
3035  Duration longestInterval = mRecurrence->longestInterval();
3036  if (repetition.duration() >= longestInterval) {
3037  const int count = mStartDateTime.isDateOnly()
3038  ? (longestInterval.asDays() - 1) / repetition.intervalDays()
3039  : (longestInterval.asSeconds() - 1) / repetition.intervalSeconds();
3040  mRepetition.set(repetition.interval(), count);
3041  } else {
3042  mRepetition = repetition;
3043  }
3044  mTriggerChanged = true;
3045  } else if (mRepetition) {
3046  mRepetition.set(0, 0);
3047  mTriggerChanged = true;
3048  }
3049  return true;
3050 }
3051 
3053 {
3054  return d->mRepetition;
3055 }
3056 
3058 {
3059  return d->mNextRepeat;
3060 }
3061 
3062 /******************************************************************************
3063 * Return the repetition interval as text suitable for display.
3064 */
3066 {
3067  if (d->mRepetition) {
3068  if (!d->mRepetition.isDaily()) {
3069  const int minutes = d->mRepetition.intervalMinutes();
3070  if (minutes < 60) {
3071  return i18ncp("@info", "1 Minute", "%1 Minutes", minutes);
3072  }
3073  if (minutes % 60 == 0) {
3074  return i18ncp("@info", "1 Hour", "%1 Hours", minutes / 60);
3075  }
3076  return i18nc("@info Hours and minutes", "%1h %2m", minutes / 60, QString::asprintf("%02d", minutes % 60));
3077  }
3078  const int days = d->mRepetition.intervalDays();
3079  if (days % 7) {
3080  return i18ncp("@info", "1 Day", "%1 Days", days);
3081  }
3082  return i18ncp("@info", "1 Week", "%1 Weeks", days / 7);
3083  }
3084  return brief ? QString() : i18nc("@info No repetition", "None");
3085 }
3086 
3087 /******************************************************************************
3088 * Determine whether the event will occur after the specified date/time.
3089 * If 'includeRepetitions' is true and the alarm has a sub-repetition, it
3090 * returns true if any repetitions occur after the specified date/time.
3091 */
3092 bool KAEvent::occursAfter(const KADateTime &preDateTime, bool includeRepetitions) const
3093 {
3094  return d->occursAfter(preDateTime, includeRepetitions);
3095 }
3096 
3097 bool KAEventPrivate::occursAfter(const KADateTime &preDateTime, bool includeRepetitions) const
3098 {
3099  KADateTime dt;
3100  if (checkRecur() != KARecurrence::NO_RECUR) {
3101  if (mRecurrence->duration() < 0) {
3102  return true; // infinite recurrence
3103  }
3104  dt = mRecurrence->endDateTime();
3105  } else {
3106  dt = mNextMainDateTime.effectiveKDateTime();
3107  }
3108  if (mStartDateTime.isDateOnly()) {
3109  QDate pre = preDateTime.date();
3110  if (preDateTime.toTimeSpec(mStartDateTime.timeSpec()).time() < DateTime::startOfDay()) {
3111  pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come
3112  }
3113  if (pre < dt.date()) {
3114  return true;
3115  }
3116  } else if (preDateTime < dt) {
3117  return true;
3118  }
3119 
3120  if (includeRepetitions && mRepetition) {
3121  if (preDateTime < KADateTime(mRepetition.duration().end(dt.qDateTime()))) {
3122  return true;
3123  }
3124  }
3125  return false;
3126 }
3127 
3128 /******************************************************************************
3129 * Set the date/time of the event to the next scheduled occurrence after the
3130 * specified date/time, provided that this is later than its current date/time.
3131 * Any reminder alarm is adjusted accordingly.
3132 * If the alarm has a sub-repetition, and a repetition of a previous recurrence
3133 * occurs after the specified date/time, that repetition is set as the next
3134 * occurrence.
3135 */
3137 {
3138  return d->setNextOccurrence(preDateTime);
3139 }
3140 
3141 KAEvent::OccurType KAEventPrivate::setNextOccurrence(const KADateTime &preDateTime)
3142 {
3143  if (preDateTime < mNextMainDateTime.effectiveKDateTime()) {
3144  return KAEvent::FIRST_OR_ONLY_OCCURRENCE; // it might not be the first recurrence - tant pis
3145  }
3146  KADateTime pre = preDateTime;
3147  // If there are repetitions, adjust the comparison date/time so that
3148  // we find the earliest recurrence which has a repetition falling after
3149  // the specified preDateTime.
3150  if (mRepetition) {
3151  pre = KADateTime(mRepetition.duration(-mRepetition.count()).end(preDateTime.qDateTime()));
3152  }
3153 
3154  DateTime afterPre; // next recurrence after 'pre'
3155  KAEvent::OccurType type;
3156  if (pre < mNextMainDateTime.effectiveKDateTime()) {
3157  afterPre = mNextMainDateTime;
3158  type = KAEvent::FIRST_OR_ONLY_OCCURRENCE; // may not actually be the first occurrence
3159  } else if (checkRecur() != KARecurrence::NO_RECUR) {
3160  type = nextRecurrence(pre, afterPre);
3161  if (type == KAEvent::NO_OCCURRENCE) {
3162  return KAEvent::NO_OCCURRENCE;
3163  }
3164  if (type != KAEvent::FIRST_OR_ONLY_OCCURRENCE && afterPre != mNextMainDateTime) {
3165  // Need to reschedule the next trigger date/time
3166  mNextMainDateTime = afterPre;
3167  if (mReminderMinutes > 0 && (mDeferral == REMINDER_DEFERRAL || mReminderActive != ACTIVE_REMINDER)) {
3168  // Reinstate the advance reminder for the rescheduled recurrence.
3169  // Note that a reminder AFTER the main alarm will be left active.
3170  activate_reminder(!mReminderOnceOnly);
3171  }
3172  if (mDeferral == REMINDER_DEFERRAL) {
3173  set_deferral(NO_DEFERRAL);
3174  }
3175  mTriggerChanged = true;
3176  }
3177  } else {
3178  return KAEvent::NO_OCCURRENCE;
3179  }
3180 
3181  if (mRepetition) {
3182  if (afterPre <= preDateTime) {
3183  // The next occurrence is a sub-repetition.
3184  type = static_cast<KAEvent::OccurType>(type | KAEvent::OCCURRENCE_REPEAT);
3185  mNextRepeat = mRepetition.nextRepeatCount(afterPre.effectiveKDateTime(), preDateTime);
3186  // Repetitions can't have a reminder, so remove any.
3187  activate_reminder(false);
3188  if (mDeferral == REMINDER_DEFERRAL) {
3189  set_deferral(NO_DEFERRAL);
3190  }
3191  mTriggerChanged = true;
3192  } else if (mNextRepeat) {
3193  // The next occurrence is the main occurrence, not a repetition
3194  mNextRepeat = 0;
3195  mTriggerChanged = true;
3196  }
3197  }
3198  return type;
3199 }
3200 
3201 /******************************************************************************
3202 * Get the date/time of the next occurrence of the event, after the specified
3203 * date/time.
3204 * 'result' = date/time of next occurrence, or invalid date/time if none.
3205 */
3207 {
3208  return d->nextOccurrence(preDateTime, result, o);
3209 }
3210 
3211 KAEvent::OccurType KAEventPrivate::nextOccurrence(const KADateTime &preDateTime, DateTime &result,
3212  KAEvent::OccurOption includeRepetitions) const
3213 {
3214  KADateTime pre = preDateTime;
3215  if (includeRepetitions != KAEvent::IGNORE_REPETITION) {
3216  // RETURN_REPETITION or ALLOW_FOR_REPETITION
3217  if (!mRepetition) {
3218  includeRepetitions = KAEvent::IGNORE_REPETITION;
3219  } else {
3220  pre = KADateTime(mRepetition.duration(-mRepetition.count()).end(preDateTime.qDateTime()));
3221  }
3222  }
3223 
3224  KAEvent::OccurType type;
3225  const bool recurs = (checkRecur() != KARecurrence::NO_RECUR);
3226  if (recurs) {
3227  type = nextRecurrence(pre, result);
3228  } else if (pre < mNextMainDateTime.effectiveKDateTime()) {
3229  result = mNextMainDateTime;
3231  } else {
3232  result = DateTime();
3233  type = KAEvent::NO_OCCURRENCE;
3234  }
3235 
3236  if (type != KAEvent::NO_OCCURRENCE && result <= preDateTime && includeRepetitions != KAEvent::IGNORE_REPETITION) {
3237  // RETURN_REPETITION or ALLOW_FOR_REPETITION
3238  // The next occurrence is a sub-repetition
3239  int repetition = mRepetition.nextRepeatCount(result.kDateTime(), preDateTime);
3240  const DateTime repeatDT(mRepetition.duration(repetition).end(result.qDateTime()));
3241  if (recurs) {
3242  // We've found a recurrence before the specified date/time, which has
3243  // a sub-repetition after the date/time.
3244  // However, if the intervals between recurrences vary, we could possibly
3245  // have missed a later recurrence which fits the criterion, so check again.
3246  DateTime dt;
3247  const KAEvent::OccurType newType = previousOccurrence(repeatDT.effectiveKDateTime(), dt, false);
3248  if (dt > result) {
3249  type = newType;
3250  result = dt;
3251  if (includeRepetitions == KAEvent::RETURN_REPETITION && result <= preDateTime) {
3252  // The next occurrence is a sub-repetition
3253  repetition = mRepetition.nextRepeatCount(result.kDateTime(), preDateTime);
3254  result = DateTime(mRepetition.duration(repetition).end(result.qDateTime()));
3255  type = static_cast<KAEvent::OccurType>(type | KAEvent::OCCURRENCE_REPEAT);
3256  }
3257  return type;
3258  }
3259  }
3260  if (includeRepetitions == KAEvent::RETURN_REPETITION) {
3261  // The next occurrence is a sub-repetition
3262  result = repeatDT;
3263  type = static_cast<KAEvent::OccurType>(type | KAEvent::OCCURRENCE_REPEAT);
3264  }
3265  }
3266  return type;
3267 }
3268 
3269 /******************************************************************************
3270 * Get the date/time of the last previous occurrence of the event, before the
3271 * specified date/time.
3272 * If 'includeRepetitions' is true and the alarm has a sub-repetition, the
3273 * last previous repetition is returned if appropriate.
3274 * 'result' = date/time of previous occurrence, or invalid date/time if none.
3275 */
3276 KAEvent::OccurType KAEvent::previousOccurrence(const KADateTime &afterDateTime, DateTime &result, bool includeRepetitions) const
3277 {
3278  return d->previousOccurrence(afterDateTime, result, includeRepetitions);
3279 }
3280 
3281 KAEvent::OccurType KAEventPrivate::previousOccurrence(const KADateTime &afterDateTime, DateTime &result,
3282  bool includeRepetitions) const
3283 {
3284  Q_ASSERT(!afterDateTime.isDateOnly());
3285  if (mStartDateTime >= afterDateTime) {
3286  result = KADateTime();
3287  return KAEvent::NO_OCCURRENCE; // the event starts after the specified date/time
3288  }
3289 
3290  // Find the latest recurrence of the event
3291  KAEvent::OccurType type;
3292  if (checkRecur() == KARecurrence::NO_RECUR) {
3293  result = mStartDateTime;
3295  } else {
3296  const KADateTime recurStart = mRecurrence->startDateTime();
3297  KADateTime after = afterDateTime.toTimeSpec(mStartDateTime.timeSpec());
3298  if (mStartDateTime.isDateOnly() && afterDateTime.time() > DateTime::startOfDay()) {
3299  after = after.addDays(1); // today's recurrence (if today recurs) has passed
3300  }
3301  const KADateTime dt = mRecurrence->getPreviousDateTime(after);
3302  result = dt;
3303  result.setDateOnly(mStartDateTime.isDateOnly());
3304  if (!dt.isValid()) {
3305  return KAEvent::NO_OCCURRENCE;
3306  }
3307  if (dt == recurStart) {
3309  } else if (mRecurrence->getNextDateTime(dt).isValid()) {
3311  } else {
3312  type = KAEvent::LAST_RECURRENCE;
3313  }
3314  }
3315 
3316  if (includeRepetitions && mRepetition) {
3317  // Find the latest repetition which is before the specified time.
3318  const int repetition = mRepetition.previousRepeatCount(result.effectiveKDateTime(), afterDateTime);
3319  if (repetition > 0) {
3320  result = DateTime(mRepetition.duration(qMin(repetition, mRepetition.count())).end(result.qDateTime()));
3321  return static_cast<KAEvent::OccurType>(type | KAEvent::OCCURRENCE_REPEAT);
3322  }
3323  }
3324  return type;
3325 }
3326 
3327 /******************************************************************************
3328 * Set the event to be a copy of the specified event, making the specified
3329 * alarm the 'displaying' alarm.
3330 * The purpose of setting up a 'displaying' alarm is to be able to reinstate
3331 * the alarm message in case of a crash, or to reinstate it should the user
3332 * choose to defer the alarm. Note that even repeat-at-login alarms need to be
3333 * saved in case their end time expires before the next login.
3334 * Reply = true if successful, false if alarm was not copied.
3335 */
3336 bool KAEvent::setDisplaying(const KAEvent &e, KAAlarm::Type t, ResourceId id, const KADateTime &dt, bool showEdit, bool showDefer)
3337 {
3338  return d->setDisplaying(*e.d, t, id, dt, showEdit, showDefer);
3339 }
3340 
3341 bool KAEventPrivate::setDisplaying(const KAEventPrivate &event, KAAlarm::Type alarmType, ResourceId resourceId,
3342  const KADateTime &repeatAtLoginTime, bool showEdit, bool showDefer)
3343 {
3344  if (!mDisplaying
3345  && (alarmType == KAAlarm::MAIN_ALARM
3346  || alarmType == KAAlarm::REMINDER_ALARM
3347  || alarmType == KAAlarm::DEFERRED_REMINDER_ALARM
3348  || alarmType == KAAlarm::DEFERRED_ALARM
3349  || alarmType == KAAlarm::AT_LOGIN_ALARM)) {
3350 //qCDebug(KALARMCAL_LOG)<<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();
3351  KAAlarm al = event.alarm(alarmType);
3352  if (al.isValid()) {
3353  *this = event;
3354  // Change the event ID to avoid duplicating the same unique ID as the original event
3356  mItemId = -1; // the display event doesn't have an associated Item
3357  mResourceId = resourceId; // original resource ID which contained the event
3358  mDisplayingDefer = showDefer;
3359  mDisplayingEdit = showEdit;
3360  mDisplaying = true;
3361  mDisplayingTime = (alarmType == KAAlarm::AT_LOGIN_ALARM) ? repeatAtLoginTime : al.dateTime().kDateTime();
3362  switch (al.type()) {
3363  case KAAlarm::AT_LOGIN_ALARM: mDisplayingFlags = KAEvent::REPEAT_AT_LOGIN; break;
3364  case KAAlarm::REMINDER_ALARM: mDisplayingFlags = REMINDER; break;
3365  case KAAlarm::DEFERRED_REMINDER_ALARM: mDisplayingFlags = al.timedDeferral() ? (REMINDER | TIME_DEFERRAL) : (REMINDER | DATE_DEFERRAL); break;
3366  case KAAlarm::DEFERRED_ALARM: mDisplayingFlags = al.timedDeferral() ? TIME_DEFERRAL : DATE_DEFERRAL; break;
3367  default: mDisplayingFlags = 0; break;
3368  }
3369  ++mAlarmCount;
3370  return true;
3371  }
3372  }
3373  return false;
3374 }
3375 
3376 /******************************************************************************
3377 * Reinstate the original event from the 'displaying' event.
3378 */
3379 void KAEvent::reinstateFromDisplaying(const KCalendarCore::Event::Ptr &e, ResourceId &id, bool &showEdit, bool &showDefer)
3380 {
3381  d->reinstateFromDisplaying(e, id, showEdit, showDefer);
3382 }
3383 
3384 void KAEventPrivate::reinstateFromDisplaying(const Event::Ptr &kcalEvent, ResourceId &resourceId, bool &showEdit, bool &showDefer)
3385 {
3386  *this = KAEventPrivate(kcalEvent);
3387  if (mDisplaying) {
3388  // Retrieve the original event's unique ID
3390  resourceId = mResourceId;
3391  mResourceId = -1;
3392  showDefer = mDisplayingDefer;
3393  showEdit = mDisplayingEdit;
3394  mDisplaying = false;
3395  --mAlarmCount;
3396  }
3397 }
3398 
3399 /******************************************************************************
3400 * Return the original alarm which the displaying alarm refers to.
3401 * Note that the caller is responsible for ensuring that the event was a
3402 * displaying event, since this is normally called after
3403 * reinstateFromDisplaying(), which clears mDisplaying.
3404 */
3406 {
3408  KAAlarm::Private *const al_d = al.d;
3409  const int displayingFlags = d->mDisplayingFlags;
3410  if (displayingFlags & REPEAT_AT_LOGIN) {
3411  al_d->mRepeatAtLogin = true;
3412  al_d->mType = KAAlarm::AT_LOGIN_ALARM;
3413  } else if (displayingFlags & KAEventPrivate::DEFERRAL) {
3414  al_d->mDeferred = true;
3415  al_d->mTimedDeferral = (displayingFlags & KAEventPrivate::TIMED_FLAG);
3416  al_d->mType = (displayingFlags & KAEventPrivate::REMINDER) ? KAAlarm::DEFERRED_REMINDER_ALARM : KAAlarm::DEFERRED_ALARM;
3417  } else if (displayingFlags & KAEventPrivate::REMINDER) {
3418  al_d->mType = KAAlarm::REMINDER_ALARM;
3419  } else {
3420  al_d->mType = KAAlarm::MAIN_ALARM;
3421  }
3422  return al;
3423 }
3424 
3426 {
3427  return d->mDisplaying;
3428 }
3429 
3430 /******************************************************************************
3431 * Return the alarm of the specified type.
3432 */
3434 {
3435  return d->alarm(t);
3436 }
3437 
3438 KAAlarm KAEventPrivate::alarm(KAAlarm::Type type) const
3439 {
3440  checkRecur(); // ensure recurrence/repetition data is consistent
3441  KAAlarm al; // this sets type to INVALID_ALARM
3442  KAAlarm::Private *const al_d = al.d;
3443  if (mAlarmCount) {
3444  al_d->mActionType = static_cast<KAAlarm::Action>(mActionSubType);
3445  al_d->mRepeatAtLogin = false;
3446  al_d->mDeferred = false;
3447  switch (type) {
3448  case KAAlarm::MAIN_ALARM:
3449  if (!mMainExpired) {
3450  al_d->mType = KAAlarm::MAIN_ALARM;
3451  al_d->mNextMainDateTime = mNextMainDateTime;
3452  al_d->mRepetition = mRepetition;
3453  al_d->mNextRepeat = mNextRepeat;
3454  }
3455  break;
3457  if (mReminderActive == ACTIVE_REMINDER) {
3458  al_d->mType = KAAlarm::REMINDER_ALARM;
3459  if (mReminderMinutes < 0) {
3460  al_d->mNextMainDateTime = mReminderAfterTime;
3461  } else if (mReminderOnceOnly) {
3462  al_d->mNextMainDateTime = mStartDateTime.addMins(-mReminderMinutes);
3463  } else {
3464  al_d->mNextMainDateTime = mNextMainDateTime.addMins(-mReminderMinutes);
3465  }
3466  }
3467  break;
3469  if (mDeferral != REMINDER_DEFERRAL) {
3470  break;
3471  }
3472  // fall through to DEFERRED_ALARM
3473  Q_FALLTHROUGH();
3475  if (mDeferral != NO_DEFERRAL) {
3476  al_d->mType = (mDeferral == REMINDER_DEFERRAL) ? KAAlarm::DEFERRED_REMINDER_ALARM : KAAlarm::DEFERRED_ALARM;
3477  al_d->mNextMainDateTime = mDeferralTime;
3478  al_d->mDeferred = true;
3479  al_d->mTimedDeferral = !mDeferralTime.isDateOnly();
3480  }
3481  break;
3483  if (mRepeatAtLogin) {
3484  al_d->mType = KAAlarm::AT_LOGIN_ALARM;
3485  al_d->mNextMainDateTime = mAtLoginDateTime;
3486  al_d->mRepeatAtLogin = true;
3487  }
3488  break;
3490  if (mDisplaying) {
3491  al_d->mType = KAAlarm::DISPLAYING_ALARM;
3492  al_d->mNextMainDateTime = mDisplayingTime;
3493  }
3494  break;
3496  default:
3497  break;
3498  }
3499  }
3500  return al;
3501 }
3502 
3503 /******************************************************************************
3504 * Return the main alarm for the event.
3505 * If the main alarm does not exist, one of the subsidiary ones is returned if
3506 * possible.
3507 * N.B. a repeat-at-login alarm can only be returned if it has been read from/
3508 * written to the calendar file.
3509 */
3511 {
3512  return d->firstAlarm();
3513 }
3514 
3515 KAAlarm KAEventPrivate::firstAlarm() const
3516 {
3517  if (mAlarmCount) {
3518  if (!mMainExpired) {
3519  return alarm(KAAlarm::MAIN_ALARM);
3520  }
3522  }
3523  return {};
3524 }
3525 
3526 /******************************************************************************
3527 * Return the next alarm for the event, after the specified alarm.
3528 * N.B. a repeat-at-login alarm can only be returned if it has been read from/
3529 * written to the calendar file.
3530 */
3531 KAAlarm KAEvent::nextAlarm(const KAAlarm &previousAlarm) const
3532 {
3533  return d->nextAlarm(previousAlarm.type());
3534 }
3535 
3537 {
3538  return d->nextAlarm(previousType);
3539 }
3540 
3541 KAAlarm KAEventPrivate::nextAlarm(KAAlarm::Type previousType) const
3542 {
3543  switch (previousType) {
3544  case KAAlarm::MAIN_ALARM:
3545  if (mReminderActive == ACTIVE_REMINDER) {
3547  }
3548  // fall through to REMINDER_ALARM
3549  Q_FALLTHROUGH();
3551  // There can only be one deferral alarm
3552  if (mDeferral == REMINDER_DEFERRAL) {
3554  }
3555  if (mDeferral == NORMAL_DEFERRAL) {
3557  }
3558  // fall through to DEFERRED_ALARM
3559  Q_FALLTHROUGH();
3562  if (mRepeatAtLogin) {
3564  }
3565  // fall through to AT_LOGIN_ALARM
3566  Q_FALLTHROUGH();
3568  if (mDisplaying) {
3570  }
3571  // fall through to DISPLAYING_ALARM
3572  Q_FALLTHROUGH();
3574  // fall through to default
3576  default:
3577  break;
3578  }
3579  return {};
3580 }
3581 
3583 {
3584  return d->mAlarmCount;
3585 }
3586 
3587 /******************************************************************************
3588 * Remove the alarm of the specified type from the event.
3589 * This must only be called to remove an alarm which has expired, not to
3590 * reconfigure the event.
3591 */
3593 {
3594  d->removeExpiredAlarm(type);
3595 }
3596 
3597 void KAEventPrivate::removeExpiredAlarm(KAAlarm::Type type)
3598 {
3599  const int count = mAlarmCount;
3600  switch (type) {
3601  case KAAlarm::MAIN_ALARM:
3602  if (!mReminderActive || mReminderMinutes > 0) {
3603  mAlarmCount = 0; // removing main alarm - also remove subsidiary alarms
3604  break;
3605  }
3606  // There is a reminder after the main alarm - retain the
3607  // reminder and remove other subsidiary alarms.
3608  mMainExpired = true; // mark the alarm as expired now
3609  --mAlarmCount;
3610  set_deferral(NO_DEFERRAL);
3611  if (mDisplaying) {
3612  mDisplaying = false;
3613  --mAlarmCount;
3614  }
3615  // fall through to AT_LOGIN_ALARM
3616  Q_FALLTHROUGH();
3618  if (mRepeatAtLogin) {
3619  // Remove the at-login alarm, but keep a note of it for archiving purposes
3620  mArchiveRepeatAtLogin = true;
3621  mRepeatAtLogin = false;
3622  --mAlarmCount;
3623  }
3624  break;
3626  // Remove any reminder alarm, but keep a note of it for archiving purposes
3627  // and for restoration after the next recurrence.
3628  activate_reminder(false);
3629  break;
3632  set_deferral(NO_DEFERRAL);
3633  break;
3635  if (mDisplaying) {
3636  mDisplaying = false;
3637  --mAlarmCount;
3638  }
3639  break;
3641  default:
3642  break;
3643  }
3644  if (mAlarmCount != count) {
3645  mTriggerChanged = true;
3646  }
3647 }
3648 
3649 /******************************************************************************
3650 * Compare this instance with another.
3651 */
3652 bool KAEvent::compare(const KAEvent& other, Comparison comparison) const
3653 {
3654  return d->compare(*other.d, comparison);
3655 }
3656 bool KAEventPrivate::compare(const KAEventPrivate& other, KAEvent::Comparison comparison) const
3657 {
3658  if (comparison & KAEvent::Compare::Id) {
3659  if (mEventID != other.mEventID) {
3660  return false;
3661  }
3662  }
3663  if (mCategory != other.mCategory
3664  || mActionSubType != other.mActionSubType
3665  || mDisplaying != other.mDisplaying
3666  || mName != other.mName
3667  || mText != other.mText
3668  || mStartDateTime != other.mStartDateTime
3669  || mLateCancel != other.mLateCancel
3670  || mCopyToKOrganizer != other.mCopyToKOrganizer
3671  || mCompatibility != other.mCompatibility
3672  || mEnabled != other.mEnabled
3673  || mReadOnly != other.mReadOnly) {
3674  return false;
3675  }
3676  if (mRecurrence) {
3677  if (!other.mRecurrence
3678  || *mRecurrence != *other.mRecurrence
3679  || mExcludeHolidays != other.mExcludeHolidays
3680  || mWorkTimeOnly != other.mWorkTimeOnly
3681  || mRepetition != mRepetition) {
3682  return false;
3683  }
3684  } else {
3685  if (other.mRecurrence
3686  || mRepeatAtLogin != other.mRepeatAtLogin
3687  || mArchiveRepeatAtLogin != other.mArchiveRepeatAtLogin
3688  || (mRepeatAtLogin && mAtLoginDateTime != other.mAtLoginDateTime)) {
3689  return false;
3690  }
3691  }
3692  if (mDisplaying) {
3693  if (mDisplayingTime != other.mDisplayingTime
3694  || mDisplayingFlags != other.mDisplayingFlags
3695  || mDisplayingDefer != other.mDisplayingDefer
3696  || mDisplayingEdit != other.mDisplayingEdit) {
3697  return false;
3698  }
3699  }
3700  if (comparison & KAEvent::Compare::ICalendar) {
3701  if (mCreatedDateTime != other.mCreatedDateTime
3702  || mCustomProperties != other.mCustomProperties
3703  || mRevision != other.mRevision) {
3704  return false;
3705  }
3706  }
3707  if (comparison & KAEvent::Compare::UserSettable)
3708  {
3709  if (mItemId != other.mItemId
3710  || mResourceId != other.mResourceId) {
3711  return false;
3712  }
3713  }
3714  if (comparison & KAEvent::Compare::CurrentState) {
3715  if (mNextMainDateTime != other.mNextMainDateTime
3716  || mMainExpired != other.mMainExpired
3717  || (mRepetition && mNextRepeat != other.mNextRepeat)) {
3718  return false;
3719  }
3720  }
3721  switch (mCategory) {
3722  case CalEvent::ACTIVE:
3723  if (mArchive != other.mArchive) {
3724  return false;
3725  }
3726  break;
3727  case CalEvent::TEMPLATE:
3728  if (mTemplateAfterTime != other.mTemplateAfterTime) {
3729  return false;
3730  }
3731  break;
3732  default:
3733  break;
3734  }
3735 
3736  switch (mActionSubType) {
3737  case KAEvent::COMMAND:
3738  if (mCommandScript != other.mCommandScript || mCommandXterm != other.mCommandXterm || mCommandDisplay != other.mCommandDisplay
3739  || mCommandError != other.mCommandError || mCommandHideError != other.mCommandHideError || mLogFile != other.mLogFile) {
3740  return false;
3741  }
3742  if (!mCommandDisplay) {
3743  break;
3744  }
3745  Q_FALLTHROUGH(); // fall through to MESSAGE
3746  case KAEvent::FILE:
3747  case KAEvent::MESSAGE:
3748  if (mReminderMinutes != other.mReminderMinutes
3749  || mBgColour != other.mBgColour
3750  || mFgColour != other.mFgColour
3751  || mUseDefaultFont != other.mUseDefaultFont
3752  || (!mUseDefaultFont && mFont != other.mFont)
3753  || mLateCancel != other.mLateCancel
3754  || (mLateCancel && mAutoClose != other.mAutoClose)
3755  || mDeferDefaultMinutes != other.mDeferDefaultMinutes
3756  || (mDeferDefaultMinutes && mDeferDefaultDateOnly != other.mDeferDefaultDateOnly)
3757  || mPreAction != other.mPreAction
3758  || mPostAction != other.mPostAction
3759  || mExtraActionOptions != other.mExtraActionOptions
3760  || mCommandError != other.mCommandError
3761  || mConfirmAck != other.mConfirmAck
3762  || mNotify != other.mNotify
3763  || mAkonadiItemId != other.mAkonadiItemId
3764  || mBeep != other.mBeep
3765  || mSpeak != other.mSpeak
3766  || mAudioFile != other.mAudioFile) {
3767  return false;
3768  }
3769  if (mReminderMinutes) {
3770  if (mReminderOnceOnly != other.mReminderOnceOnly) {
3771  return false;
3772  }
3773  if (comparison & KAEvent::Compare::CurrentState) {
3774  if (mReminderActive != other.mReminderActive
3775  || (mReminderActive && mReminderAfterTime != other.mReminderAfterTime)) {
3776  return false;
3777  }
3778  }
3779  }
3780  if (comparison & KAEvent::Compare::CurrentState) {
3781  if (mDeferral != other.mDeferral
3782  || (mDeferral != NO_DEFERRAL && mDeferralTime != other.mDeferralTime)) {
3783  return false;
3784  }
3785  }
3786  if (mAudioFile.isEmpty()) {
3787  break;
3788  }
3789  Q_FALLTHROUGH(); // fall through to AUDIO
3790  case KAEvent::AUDIO:
3791  if (mRepeatSoundPause != other.mRepeatSoundPause) {
3792  return false;
3793  }
3794  if (mSoundVolume >= 0) {
3795  if (mSoundVolume != other.mSoundVolume) {
3796  return false;
3797  }
3798  if (mFadeVolume >= 0) {
3799  if (mFadeVolume != other.mFadeVolume
3800  || mFadeSeconds != other.mFadeSeconds) {
3801  return false;
3802  }
3803  } else if (other.mFadeVolume >= 0) {
3804  return false;
3805  }
3806  } else if (other.mSoundVolume >= 0) {
3807  return false;
3808  }
3809  break;
3810  case KAEvent::EMAIL:
3811  if (mEmailFromIdentity != other.mEmailFromIdentity
3812  || mEmailAddresses != other.mEmailAddresses
3813  || mEmailSubject != other.mEmailSubject
3814  || mEmailAttachments != other.mEmailAttachments
3815  || mEmailBcc != other.mEmailBcc) {
3816  return false;
3817  }
3818  break;
3819  }
3820  return true;
3821 }
3822 
3824 {
3825  d->startChanges();
3826 }
3827 
3828 /******************************************************************************
3829 * Indicate that changes to the instance are complete.
3830 * This allows trigger times to be recalculated if any changes have occurred.
3831 */
3833 {
3834  d->endChanges();
3835 }
3836 
3837 void KAEventPrivate::endChanges()
3838 {
3839  if (mChangeCount > 0) {
3840  --mChangeCount;
3841  }
3842 }
3843 
3844 /******************************************************************************
3845 * Return a list of pointers to KAEvent objects.
3846 */
3848 {
3849  KAEvent::List ptrs;
3850  const int count = objList.count();
3851  ptrs.reserve(count);
3852  for (int i = 0; i < count; ++i) {
3853  ptrs += &objList[i];
3854  }
3855  return ptrs;
3856 }
3857 
3859 {
3860 #ifndef KDE_NO_DEBUG_OUTPUT
3861  d->dumpDebug();
3862 #endif
3863 }
3864 
3865 #ifndef KDE_NO_DEBUG_OUTPUT
3866 void KAEventPrivate::dumpDebug() const
3867 {
3868  qCDebug(KALARMCAL_LOG) << "KAEvent dump:";
3869  qCDebug(KALARMCAL_LOG) << "-- mEventID:" << mEventID;
3870  qCDebug(KALARMCAL_LOG) << "-- mActionSubType:" << (mActionSubType == KAEvent::MESSAGE ? "MESSAGE" : mActionSubType == KAEvent::FILE ? "FILE" : mActionSubType == KAEvent::COMMAND ? "COMMAND" : mActionSubType == KAEvent::EMAIL ? "EMAIL" : mActionSubType == KAEvent::AUDIO ? "AUDIO" : "??");
3871  qCDebug(KALARMCAL_LOG) << "-- mNextMainDateTime:" << mNextMainDateTime.toString();
3872  qCDebug(KALARMCAL_LOG) << "-- mCommandError:" << mCommandError;
3873  qCDebug(KALARMCAL_LOG) << "-- mAllTrigger:" << mAllTrigger.toString();
3874  qCDebug(KALARMCAL_LOG) << "-- mMainTrigger:" << mMainTrigger.toString();
3875  qCDebug(KALARMCAL_LOG) << "-- mAllWorkTrigger:" << mAllWorkTrigger.toString();
3876  qCDebug(KALARMCAL_LOG) << "-- mMainWorkTrigger:" << mMainWorkTrigger.toString();
3877  qCDebug(KALARMCAL_LOG) << "-- mCategory:" << mCategory;
3878  qCDebug(KALARMCAL_LOG) << "-- mName:" << mName;
3879  if (mCategory == CalEvent::TEMPLATE) {
3880  qCDebug(KALARMCAL_LOG) << "-- mTemplateAfterTime:" << mTemplateAfterTime;
3881  }
3882  qCDebug(KALARMCAL_LOG) << "-- mText:" << mText;
3883  if (mActionSubType == KAEvent::MESSAGE
3884  || mActionSubType == KAEvent::FILE
3885  || (mActionSubType == KAEvent::COMMAND && mCommandDisplay)) {
3886  if (mCommandDisplay) {
3887  qCDebug(KALARMCAL_LOG) << "-- mCommandScript:" << mCommandScript;
3888  }
3889  qCDebug(KALARMCAL_LOG) << "-- mBgColour:" << mBgColour.name();
3890  qCDebug(KALARMCAL_LOG) << "-- mFgColour:" << mFgColour.name();
3891  qCDebug(KALARMCAL_LOG) << "-- mUseDefaultFont:" << mUseDefaultFont;
3892  if (!mUseDefaultFont) {
3893  qCDebug(KALARMCAL_LOG) << "-- mFont:" << mFont.toString();
3894  }
3895  qCDebug(KALARMCAL_LOG) << "-- mSpeak:" << mSpeak;
3896  qCDebug(KALARMCAL_LOG) << "-- mAudioFile:" << mAudioFile;
3897  qCDebug(KALARMCAL_LOG) << "-- mPreAction:" << mPreAction;
3898  qCDebug(KALARMCAL_LOG) << "-- mExecPreActOnDeferral:" << (mExtraActionOptions & KAEvent::ExecPreActOnDeferral);
3899  qCDebug(KALARMCAL_LOG) << "-- mCancelOnPreActErr:" << (mExtraActionOptions & KAEvent::CancelOnPreActError);
3900  qCDebug(KALARMCAL_LOG) << "-- mDontShowPreActErr:" << (mExtraActionOptions & KAEvent::DontShowPreActError);
3901  qCDebug(KALARMCAL_LOG) << "-- mPostAction:" << mPostAction;
3902  qCDebug(KALARMCAL_LOG) << "-- mLateCancel:" << mLateCancel;
3903  qCDebug(KALARMCAL_LOG) << "-- mAutoClose:" << mAutoClose;
3904  qCDebug(KALARMCAL_LOG) << "-- mNotify:" << mNotify;
3905  } else if (mActionSubType == KAEvent::COMMAND) {
3906  qCDebug(KALARMCAL_LOG) << "-- mCommandScript:" << mCommandScript;
3907  qCDebug(KALARMCAL_LOG) << "-- mCommandXterm:" << mCommandXterm;
3908  qCDebug(KALARMCAL_LOG) << "-- mCommandDisplay:" << mCommandDisplay;
3909  qCDebug(KALARMCAL_LOG) << "-- mCommandHideError:" << mCommandHideError;
3910  qCDebug(KALARMCAL_LOG) << "-- mLogFile:" << mLogFile;
3911  } else if (mActionSubType == KAEvent::EMAIL) {
3912  qCDebug(KALARMCAL_LOG) << "-- mEmail: FromKMail:" << mEmailFromIdentity;
3913  qCDebug(KALARMCAL_LOG) << "-- Addresses:" << mEmailAddresses.join(QStringLiteral(","));
3914  qCDebug(KALARMCAL_LOG) << "-- Subject:" << mEmailSubject;
3915  qCDebug(KALARMCAL_LOG) << "-- Attachments:" << mEmailAttachments.join(QLatin1Char(','));
3916  qCDebug(KALARMCAL_LOG) << "-- Bcc:" << mEmailBcc;
3917  } else if (mActionSubType == KAEvent::AUDIO) {
3918  qCDebug(KALARMCAL_LOG) << "-- mAudioFile:" << mAudioFile;
3919  }
3920  qCDebug(KALARMCAL_LOG) << "-- mBeep:" << mBeep;
3921  if (mActionSubType == KAEvent::AUDIO || !mAudioFile.isEmpty()) {
3922  if (mSoundVolume >= 0) {
3923  qCDebug(KALARMCAL_LOG) << "-- mSoundVolume:" << mSoundVolume;
3924  if (mFadeVolume >= 0) {
3925  qCDebug(KALARMCAL_LOG) << "-- mFadeVolume:" << mFadeVolume;
3926  qCDebug(KALARMCAL_LOG) << "-- mFadeSeconds:" << mFadeSeconds;
3927  } else {
3928  qCDebug(KALARMCAL_LOG) << "-- mFadeVolume:-:";
3929  }
3930  } else {
3931  qCDebug(KALARMCAL_LOG) << "-- mSoundVolume:-:";
3932  }
3933  qCDebug(KALARMCAL_LOG) << "-- mRepeatSoundPause:" << mRepeatSoundPause;
3934  }
3935  qCDebug(KALARMCAL_LOG) << "-- mAkonadiItemId:" << mAkonadiItemId;
3936  qCDebug(KALARMCAL_LOG) << "-- mCopyToKOrganizer:" << mCopyToKOrganizer;
3937  qCDebug(KALARMCAL_LOG) << "-- mExcludeHolidays:" << mExcludeHolidays;
3938  qCDebug(KALARMCAL_LOG) << "-- mWorkTimeOnly:" << mWorkTimeOnly;
3939  qCDebug(KALARMCAL_LOG) << "-- mStartDateTime:" << mStartDateTime.toString();
3940 // qCDebug(KALARMCAL_LOG) << "-- mCreatedDateTime:" << mCreatedDateTime;
3941  qCDebug(KALARMCAL_LOG) << "-- mRepeatAtLogin:" << mRepeatAtLogin;
3942 // if (mRepeatAtLogin)
3943 // qCDebug(KALARMCAL_LOG) << "-- mAtLoginDateTime:" << mAtLoginDateTime;
3944  qCDebug(KALARMCAL_LOG) << "-- mArchiveRepeatAtLogin:" << mArchiveRepeatAtLogin;
3945  qCDebug(KALARMCAL_LOG) << "-- mConfirmAck:" << mConfirmAck;
3946  qCDebug(KALARMCAL_LOG) << "-- mEnabled:" << mEnabled;
3947  qCDebug(KALARMCAL_LOG) << "-- mItemId:" << mItemId;
3948  qCDebug(KALARMCAL_LOG) << "-- mResourceId:" << mResourceId;
3949  qCDebug(KALARMCAL_LOG) << "-- mCompatibility:" << mCompatibility;
3950  qCDebug(KALARMCAL_LOG) << "-- mReadOnly:" << mReadOnly;
3951  if (mReminderMinutes) {
3952  qCDebug(KALARMCAL_LOG) << "-- mReminderMinutes:" << mReminderMinutes;
3953  qCDebug(KALARMCAL_LOG) << "-- mReminderActive:" << (mReminderActive == ACTIVE_REMINDER ? "active" : mReminderActive == HIDDEN_REMINDER ? "hidden" : "no");
3954  qCDebug(KALARMCAL_LOG) << "-- mReminderOnceOnly:" << mReminderOnceOnly;
3955  }
3956  if (mDeferral != NO_DEFERRAL) {
3957  qCDebug(KALARMCAL_LOG) << "-- mDeferral:" << (mDeferral == NORMAL_DEFERRAL ? "normal" : "reminder");
3958  qCDebug(KALARMCAL_LOG) << "-- mDeferralTime:" << mDeferralTime.toString();
3959  }
3960  qCDebug(KALARMCAL_LOG) << "-- mDeferDefaultMinutes:" << mDeferDefaultMinutes;
3961  if (mDeferDefaultMinutes) {
3962  qCDebug(KALARMCAL_LOG) << "-- mDeferDefaultDateOnly:" << mDeferDefaultDateOnly;
3963  }
3964  if (mDisplaying) {
3965  qCDebug(KALARMCAL_LOG) << "-- mDisplayingTime:" << mDisplayingTime.toString();
3966  qCDebug(KALARMCAL_LOG) << "-- mDisplayingFlags:" << mDisplayingFlags;
3967  qCDebug(KALARMCAL_LOG) << "-- mDisplayingDefer:" << mDisplayingDefer;
3968  qCDebug(KALARMCAL_LOG) << "-- mDisplayingEdit:" << mDisplayingEdit;
3969  }
3970  qCDebug(KALARMCAL_LOG) << "-- mRevision:" << mRevision;
3971  qCDebug(KALARMCAL_LOG) << "-- mRecurrence:" << mRecurrence;
3972  if (!mRepetition) {
3973  qCDebug(KALARMCAL_LOG) << "-- mRepetition: 0";
3974  } else if (mRepetition.isDaily()) {
3975  qCDebug(KALARMCAL_LOG) << "-- mRepetition: count:" << mRepetition.count() << ", interval:" << mRepetition.intervalDays() << "days";
3976  } else {
3977  qCDebug(KALARMCAL_LOG) << "-- mRepetition: count:" << mRepetition.count() << ", interval:" << mRepetition.intervalMinutes() << "minutes";
3978  }
3979  qCDebug(KALARMCAL_LOG) << "-- mNextRepeat:" << mNextRepeat;
3980  qCDebug(KALARMCAL_LOG) << "-- mAlarmCount:" << mAlarmCount;
3981  qCDebug(KALARMCAL_LOG) << "-- mMainExpired:" << mMainExpired;
3982  qCDebug(KALARMCAL_LOG) << "-- mDisplaying:" << mDisplaying;
3983  qCDebug(KALARMCAL_LOG) << "KAEvent dump end";
3984 }
3985 #endif
3986 
3987 /******************************************************************************
3988 * Fetch the start and next date/time for a KCalendarCore::Event.
3989 * Reply = next main date/time.
3990 */
3991 DateTime KAEventPrivate::readDateTime(const Event::Ptr &event, bool localZone, bool dateOnly, DateTime &start)
3992 {
3993  start = DateTime(event->dtStart());
3994  if (dateOnly) {
3995  // A date-only event is indicated by the X-KDE-KALARM-FLAGS:DATE property, not
3996  // by a date-only start date/time (for the reasons given in updateKCalEvent()).
3997  start.setDateOnly(true);
3998  }
3999  if (localZone) {
4000  // The local system time zone is indicated by the X-KDE-KALARM-FLAGS:LOCAL
4001  // property, because QDateTime values with time spec Qt::LocalTime are not
4002  // stored correctly in the calendar file.
4004  }
4005  DateTime next = start;
4006  const int SZ_YEAR = 4; // number of digits in year value
4007  const int SZ_MONTH = 2; // number of digits in month value
4008  const int SZ_DAY = 2; // number of digits in day value
4009  const int SZ_DATE = SZ_YEAR + SZ_MONTH + SZ_DAY; // total size of date value
4010  const int IX_TIME = SZ_DATE + 1; // offset to time value
4011  const int SZ_HOUR = 2; // number of digits in hour value
4012  const int SZ_MIN = 2; // number of digits in minute value
4013  const int SZ_SEC = 2; // number of digits in second value
4014  const int SZ_TIME = SZ_HOUR + SZ_MIN + SZ_SEC; // total size of time value
4015  const QString prop = event->customProperty(KACalendar::APPNAME, KAEventPrivate::NEXT_RECUR_PROPERTY);
4016  if (prop.length() >= SZ_DATE) {
4017  // The next due recurrence time is specified
4018  const QDate d(prop.leftRef(SZ_YEAR).toInt(),
4019  prop.midRef(SZ_YEAR, SZ_MONTH).toInt(),
4020  prop.midRef(SZ_YEAR + SZ_MONTH, SZ_DAY).toInt());
4021  if (d.isValid()) {
4022  if (dateOnly && prop.length() == SZ_DATE) {
4023  next.setDate(d);
4024  } else if (!dateOnly && prop.length() == IX_TIME + SZ_TIME && prop[SZ_DATE] == QLatin1Char('T')) {
4025  const QTime t(prop.midRef(IX_TIME, SZ_HOUR).toInt(),
4026  prop.midRef(IX_TIME + SZ_HOUR, SZ_MIN).toInt(),
4027  prop.midRef(IX_TIME + SZ_HOUR + SZ_MIN, SZ_SEC).toInt());
4028  if (t.isValid()) {
4029  next.setDate(d);
4030  next.setTime(t);
4031  }
4032  }
4033  if (next < start) {
4034  next = start; // ensure next recurrence time is valid
4035  }
4036  }
4037  }
4038  return next;
4039 }
4040 
4041 /******************************************************************************
4042 * Parse the alarms for a KCalendarCore::Event.
4043 * Reply = map of alarm data, indexed by KAAlarm::Type
4044 */
4045 void KAEventPrivate::readAlarms(const Event::Ptr &event, AlarmMap *alarmMap, bool cmdDisplay)
4046 {
4047  const Alarm::List alarms = event->alarms();
4048 
4049  // Check if it's an audio event with no display alarm
4050  bool audioOnly = false;
4051  for (const Alarm::Ptr &alarm : alarms) {
4052  bool done = false;
4053  switch (alarm->type()) {
4054  case Alarm::Display:
4055  case Alarm::Procedure:
4056  audioOnly = false;
4057  done = true; // exit from the 'for' loop
4058  break;
4059  case Alarm::Audio:
4060  audioOnly = true;
4061  break;
4062  default:
4063  break;
4064  }
4065  if (done) {
4066  break;
4067  }
4068  }
4069 
4070  for (const Alarm::Ptr &alarm : alarms) {
4071  // Parse the next alarm's text
4072  AlarmData data;
4073  readAlarm(alarm, data, audioOnly, cmdDisplay);
4074  if (data.type != INVALID_ALARM) {
4075  alarmMap->insert(data.type, data);
4076  }
4077  }
4078 }
4079 
4080 /******************************************************************************
4081 * Parse a KCalendarCore::Alarm.
4082 * If 'audioMain' is true, the event contains an audio alarm but no display alarm.
4083 * Reply = alarm ID (sequence number)
4084 */
4085 void KAEventPrivate::readAlarm(const Alarm::Ptr &alarm, AlarmData &data, bool audioMain, bool cmdDisplay)
4086 {
4087  // Parse the next alarm's text
4088  data.alarm = alarm;
4089  data.displayingFlags = 0;
4090  data.isEmailText = false;
4091  data.speak = false;
4092  data.hiddenReminder = false;
4093  data.timedDeferral = false;
4094  data.nextRepeat = 0;
4095  data.repeatSoundPause = -1;
4096  if (alarm->repeatCount()) {
4097  bool ok;
4098  const QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::NEXT_REPEAT_PROPERTY);
4099  int n = static_cast<int>(property.toUInt(&ok));
4100  if (ok) {
4101  data.nextRepeat = n;
4102  }
4103  }
4104  QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY);
4105  const QStringList flags = property.split(KAEventPrivate::SC, Qt::SkipEmptyParts);
4106  switch (alarm->type()) {
4107  case Alarm::Procedure:
4108  data.action = KAAlarm::COMMAND;
4109  data.cleanText = alarm->programFile();
4110  data.commandScript = data.cleanText.isEmpty(); // blank command indicates a script
4111  if (!alarm->programArguments().isEmpty()) {
4112  if (!data.commandScript) {
4113  data.cleanText += QLatin1Char(' ');
4114  }
4115  data.cleanText += alarm->programArguments();
4116  }
4117  data.extraActionOptions = {};
4118  if (flags.contains(KAEventPrivate::EXEC_ON_DEFERRAL_FLAG)) {
4119  data.extraActionOptions |= KAEvent::ExecPreActOnDeferral;
4120  }
4121  if (flags.contains(KAEventPrivate::CANCEL_ON_ERROR_FLAG)) {
4122  data.extraActionOptions |= KAEvent::CancelOnPreActError;
4123  }
4124  if (flags.contains(KAEventPrivate::DONT_SHOW_ERROR_FLAG)) {
4125  data.extraActionOptions |= KAEvent::DontShowPreActError;
4126  }
4127  if (!cmdDisplay) {
4128  break;
4129  }
4130  // fall through to Display
4131  Q_FALLTHROUGH();
4132  case Alarm::Display: {
4133  if (alarm->type() == Alarm::Display) {
4134  data.action = KAAlarm::MESSAGE;
4135  data.cleanText = AlarmText::fromCalendarText(alarm->text(), data.isEmailText);
4136  }
4137  const QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::FONT_COLOUR_PROPERTY);
4138  const QStringList list = property.split(QLatin1Char(';'), Qt::KeepEmptyParts);
4139  data.bgColour = QColor(255, 255, 255); // white
4140  data.fgColour = QColor(0, 0, 0); // black
4141  const int n = list.count();
4142  if (n > 0) {
4143  if (!list[0].isEmpty()) {
4144  QColor c(list[0]);
4145  if (c.isValid()) {
4146  data.bgColour = c;
4147  }
4148  }
4149  if (n > 1 && !list[1].isEmpty()) {
4150  QColor c(list[1]);
4151  if (c.isValid()) {
4152  data.fgColour = c;
4153  }
4154  }
4155  }
4156  data.defaultFont = (n <= 2 || list[2].isEmpty());
4157  if (!data.defaultFont) {
4158  data.font.fromString(list[2]);
4159  }
4160  break;
4161  }
4162  case Alarm::Email: {
4163  data.action = KAAlarm::EMAIL;
4164  data.cleanText = alarm->mailText();
4165  const int i = flags.indexOf(KAEventPrivate::EMAIL_ID_FLAG);
4166  data.emailFromId = (i >= 0 && i + 1 < flags.count()) ? flags[i + 1].toUInt() : 0;
4167  break;
4168  }
4169  case Alarm::Audio: {
4170  data.action = KAAlarm::AUDIO;
4171  data.cleanText = alarm->audioFile();
4172  data.repeatSoundPause = (alarm->repeatCount() == -2) ? alarm->snoozeTime().asSeconds()
4173  : (alarm->repeatCount() == -1) ? 0 : -1;
4174  data.soundVolume = -1;
4175  data.fadeVolume = -1;
4176  data.fadeSeconds = 0;
4177  QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::VOLUME_PROPERTY);
4178  if (!property.isEmpty()) {
4179  bool ok;
4180  float fadeVolume;
4181  int fadeSecs = 0;
4182  const QStringList list = property.split(QLatin1Char(';'), Qt::KeepEmptyParts);
4183  data.soundVolume = list[0].toFloat(&ok);
4184  if (!ok || data.soundVolume > 1.0f) {
4185  data.soundVolume = -1;
4186  }
4187  if (data.soundVolume >= 0 && list.count() >= 3) {
4188  fadeVolume = list[1].toFloat(&ok);
4189  if (ok) {
4190  fadeSecs = static_cast<int>(list[2].toUInt(&ok));
4191  }
4192  if (ok && fadeVolume >= 0 && fadeVolume <= 1.0f && fadeSecs > 0) {
4193  data.fadeVolume = fadeVolume;
4194  data.fadeSeconds = fadeSecs;
4195  }
4196  }
4197  }
4198  if (!audioMain) {
4199  data.type = AUDIO_ALARM;
4200  data.speak = flags.contains(KAEventPrivate::SPEAK_FLAG);
4201  return;
4202  }
4203  break;
4204  }
4205  case Alarm::Invalid:
4206  data.type = INVALID_ALARM;
4207  return;
4208  }
4209 
4210  bool atLogin = false;
4211  bool reminder = false;
4212  bool deferral = false;
4213  bool dateDeferral = false;
4214  bool repeatSound = false;
4215  data.type = MAIN_ALARM;
4216  property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY);
4217  const QStringList types = property.split(QLatin1Char(','), Qt::SkipEmptyParts);
4218  for (int i = 0, end = types.count(); i < end; ++i) {
4219  const QString type = types[i];
4220  if (type == KAEventPrivate::AT_LOGIN_TYPE) {
4221  atLogin = true;
4222  } else if (type == KAEventPrivate::FILE_TYPE && data.action == KAAlarm::MESSAGE) {
4223  data.action = KAAlarm::FILE;
4224  } else if (type == KAEventPrivate::REMINDER_TYPE) {
4225  reminder = true;
4226  } else if (type == KAEventPrivate::TIME_DEFERRAL_TYPE) {
4227  deferral = true;
4228  } else if (type == KAEventPrivate::DATE_DEFERRAL_TYPE) {
4229  dateDeferral = deferral = true;
4230  } else if (type == KAEventPrivate::DISPLAYING_TYPE) {
4231  data.type = DISPLAYING_ALARM;
4232  } else if (type == KAEventPrivate::PRE_ACTION_TYPE && data.action == KAAlarm::COMMAND) {
4233  data.type = PRE_ACTION_ALARM;
4234  } else if (type == KAEventPrivate::POST_ACTION_TYPE && data.action == KAAlarm::COMMAND) {
4235  data.type = POST_ACTION_ALARM;
4236  } else if (type == KAEventPrivate::SOUND_REPEAT_TYPE && data.action == KAAlarm::AUDIO) {
4237  repeatSound = true;
4238  if (i + 1 < end) {
4239  bool ok;
4240  uint n = types[i + 1].toUInt(&ok);
4241  if (ok) {
4242  data.repeatSoundPause = n;
4243  ++i;
4244  }
4245  }
4246  }
4247  }
4248  if (repeatSound && data.repeatSoundPause < 0) {
4249  data.repeatSoundPause = 0;
4250  } else if (!repeatSound) {
4251  data.repeatSoundPause = -1;
4252  }
4253 
4254  if (reminder) {
4255  if (data.type == MAIN_ALARM) {
4256  data.type = deferral ? DEFERRED_REMINDER_ALARM : REMINDER_ALARM;
4257  data.timedDeferral = (deferral && !dateDeferral);
4258  if (data.type == REMINDER_ALARM
4259  && flags.contains(KAEventPrivate::HIDDEN_REMINDER_FLAG)) {
4260  data.hiddenReminder = true;
4261  }
4262  } else if (data.type == DISPLAYING_ALARM) {
4263  data.displayingFlags = dateDeferral ? REMINDER | DATE_DEFERRAL
4264  : deferral ? REMINDER | TIME_DEFERRAL : REMINDER;
4265  }
4266  } else if (deferral) {
4267  if (data.type == MAIN_ALARM) {
4268  data.type = DEFERRED_ALARM;
4269  data.timedDeferral = !dateDeferral;
4270  } else if (data.type == DISPLAYING_ALARM) {
4271  data.displayingFlags = dateDeferral ? DATE_DEFERRAL : TIME_DEFERRAL;
4272  }
4273  }
4274  if (atLogin) {
4275  if (data.type == MAIN_ALARM) {
4276  data.type = AT_LOGIN_ALARM;
4277  } else if (data.type == DISPLAYING_ALARM) {
4278  data.displayingFlags = KAEvent::REPEAT_AT_LOGIN;
4279  }
4280  }
4281 //qCDebug(KALARMCAL_LOG)<<"text="<<alarm->text()<<", time="<<alarm->time().toString()<<", valid time="<<alarm->time().isValid();
4282 }
4283 
4284 QSharedPointer<const HolidayRegion> KAEventPrivate::holidays()
4285 {
4286  if (!mHolidays) {
4287  mHolidays.reset(new HolidayRegion());
4288  }
4289  return mHolidays;
4290 }
4291 
4292 inline void KAEventPrivate::set_deferral(DeferType type)
4293 {
4294  if (type) {
4295  if (mDeferral == NO_DEFERRAL) {
4296  ++mAlarmCount;
4297  }
4298  } else {
4299  if (mDeferral != NO_DEFERRAL) {
4300  --mAlarmCount;
4301  }
4302  }
4303  mDeferral = type;
4304 }
4305 
4306 /******************************************************************************
4307 * Calculate the next trigger times of the alarm.
4308 * This should only be called when changes have actually occurred which might
4309 * affect the event's trigger times.
4310 * mMainTrigger is set to the next scheduled recurrence/sub-repetition, or the
4311 * deferral time if a deferral is pending.
4312 * mAllTrigger is the same as mMainTrigger, but takes account of reminders.
4313 * mMainWorkTrigger is set to the next scheduled recurrence/sub-repetition
4314 * which occurs in working hours, if working-time-only is set.
4315 * mAllWorkTrigger is the same as mMainWorkTrigger, but takes account of reminders.
4316 */
4317 void KAEventPrivate::calcTriggerTimes() const
4318 {
4319  if (mChangeCount) {
4320  return;
4321  }
4322 #pragma message("May need to set date-only alarms to after start-of-day time in working-time checks")
4323  holidays(); // initialise mHolidays if necessary
4324  bool recurs = (checkRecur() != KARecurrence::NO_RECUR);
4325  if ((recurs && mWorkTimeOnly && mWorkTimeOnly != mWorkTimeIndex)
4326  || (recurs && mExcludeHolidays && mExcludeHolidayRegion->regionCode() != mHolidays->regionCode())) {
4327  // It's a work time alarm, and work days/times have changed, or
4328  // it excludes holidays, and the holidays definition has changed.
4329  mTriggerChanged = true;
4330  } else if (!mTriggerChanged) {
4331  return;
4332  }
4333  mTriggerChanged = false;
4334  if (recurs && mWorkTimeOnly) {
4335  mWorkTimeOnly = mWorkTimeIndex; // note which work time definition was used in calculation
4336  }
4337  if (recurs && mExcludeHolidays) {
4338  mExcludeHolidayRegion = mHolidays; // note which holiday definition was used in calculation
4339  }
4340  bool excludeHolidays = mExcludeHolidays && mExcludeHolidayRegion->isValid();
4341 
4342  if (mCategory == CalEvent::ARCHIVED || mCategory == CalEvent::TEMPLATE) {
4343  // It's a template or archived
4344  mAllTrigger = mMainTrigger = mAllWorkTrigger = mMainWorkTrigger = KADateTime();
4345  } else if (mDeferral == NORMAL_DEFERRAL) {
4346  // For a deferred alarm, working time setting is ignored
4347  mAllTrigger = mMainTrigger = mAllWorkTrigger = mMainWorkTrigger = mDeferralTime;
4348  } else {
4349  mMainTrigger = mainDateTime(true); // next recurrence or sub-repetition
4350  mAllTrigger = (mDeferral == REMINDER_DEFERRAL) ? mDeferralTime
4351  : (mReminderActive != ACTIVE_REMINDER) ? mMainTrigger
4352  : (mReminderMinutes < 0) ? mReminderAfterTime
4353  : mMainTrigger.addMins(-mReminderMinutes);
4354  // It's not deferred.
4355  // If only-during-working-time is set and it recurs, it won't actually trigger
4356  // unless it falls during working hours.
4357  if ((!mWorkTimeOnly && !excludeHolidays)
4358  || !recurs
4359  || !excludedByWorkTimeOrHoliday(mMainTrigger.kDateTime())) {
4360  // It only occurs once, or it complies with any working hours/holiday
4361  // restrictions.
4362  mMainWorkTrigger = mMainTrigger;
4363  mAllWorkTrigger = mAllTrigger;
4364  } else if (mWorkTimeOnly) {
4365  // The alarm is restricted to working hours.
4366  // Finding the next occurrence during working hours can sometimes take a long time,
4367  // so mark the next actual trigger as invalid until the calculation completes.
4368  // Note that reminders are only triggered if the main alarm is during working time.
4369  if (!excludeHolidays) {
4370  // There are no holiday restrictions.
4371  calcNextWorkingTime(mMainTrigger);
4372  } else if (mHolidays->isValid()) {
4373  // Holidays are excluded.
4374  DateTime nextTrigger = mMainTrigger;
4375  KADateTime kdt;
4376  for (int i = 0; i < 20; ++i) {
4377  calcNextWorkingTime(nextTrigger);
4378  if (!mHolidays->isHoliday(mMainWorkTrigger.date())) {
4379  return; // found a non-holiday occurrence
4380  }
4381  kdt = mMainWorkTrigger.effectiveKDateTime();
4382  kdt.setTime(QTime(23, 59, 59));
4383  const KAEvent::OccurType type = nextOccurrence(kdt, nextTrigger, KAEvent::RETURN_REPETITION);
4384  if (!nextTrigger.isValid()) {
4385  break;
4386  }
4387  if (!excludedByWorkTimeOrHoliday(nextTrigger.kDateTime())) {
4388  const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm
4389  mMainWorkTrigger = nextTrigger;
4390  mAllWorkTrigger = (type & KAEvent::OCCURRENCE_REPEAT) ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder);
4391  return; // found a non-holiday occurrence
4392  }
4393  }
4394  mMainWorkTrigger = mAllWorkTrigger = DateTime();
4395  }
4396  } else if (excludeHolidays && mHolidays->isValid()) {
4397  // Holidays are excluded.
4398  DateTime nextTrigger = mMainTrigger;
4399  KADateTime kdt;
4400  for (int i = 0; i < 20; ++i) {
4401  kdt = nextTrigger.effectiveKDateTime();
4402  kdt.setTime(QTime(23, 59, 59));
4403  const KAEvent::OccurType type = nextOccurrence(kdt, nextTrigger, KAEvent::RETURN_REPETITION);
4404  if (!nextTrigger.isValid()) {
4405  break;
4406  }
4407  if (!mHolidays->isHoliday(nextTrigger.date())) {
4408  const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm
4409  mMainWorkTrigger = nextTrigger;
4410  mAllWorkTrigger = (type & KAEvent::OCCURRENCE_REPEAT) ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder);
4411  return; // found a non-holiday occurrence
4412  }
4413  }
4414  mMainWorkTrigger = mAllWorkTrigger = DateTime();
4415  }
4416  }
4417 }
4418 
4419 /******************************************************************************
4420 * Return the time of the next scheduled occurrence of the event during working
4421 * hours, for an alarm which is restricted to working hours.
4422 * On entry, 'nextTrigger' = the next recurrence or repetition (as returned by
4423 * mainDateTime(true) ).
4424 */
4425 void KAEventPrivate::calcNextWorkingTime(const DateTime &nextTrigger) const
4426 {
4427  qCDebug(KALARMCAL_LOG) << "next=" << nextTrigger.kDateTime().toString(QStringLiteral("%Y-%m-%d %H:%M"));
4428  mMainWorkTrigger = mAllWorkTrigger = DateTime();
4429 
4430  if (!mWorkDays.count(true)) {
4431  return; // no working days are defined
4432  }
4433  const KARecurrence::Type recurType = checkRecur();
4434  KADateTime kdt = nextTrigger.effectiveKDateTime();
4435  const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm
4436  // Check if it always falls on the same day(s) of the week.
4437  const RecurrenceRule *rrule = mRecurrence->defaultRRuleConst();
4438  if (!rrule) {
4439  return; // no recurrence rule!
4440  }
4441  unsigned allDaysMask = 0x7F; // mask bits for all days of week
4442  bool noWorkPos = false; // true if no recurrence day position is working day
4443  const QList<RecurrenceRule::WDayPos> pos = rrule->byDays();
4444  const int nDayPos = pos.count(); // number of day positions
4445  if (nDayPos) {
4446  noWorkPos = true;
4447  allDaysMask = 0;
4448  for (const RecurrenceRule::WDayPos &p : pos) {
4449  const int day = p.day() - 1; // Monday = 0
4450  if (mWorkDays.testBit(day)) {
4451  noWorkPos = false; // found a working day occurrence
4452  }
4453  allDaysMask |= 1 << day;
4454  }
4455  if (noWorkPos && !mRepetition) {
4456  return; // never occurs on a working day
4457  }
4458  }
4459  DateTime newdt;
4460 
4461  if (mStartDateTime.isDateOnly()) {
4462  // It's a date-only alarm.
4463  // Sub-repetitions also have to be date-only.
4464  const int repeatFreq = mRepetition.intervalDays();
4465  const bool weeklyRepeat = mRepetition && !(repeatFreq % 7);
4466  const Duration interval = mRecurrence->regularInterval();
4467  if ((!interval.isNull() && !(interval.asDays() % 7))
4468  || nDayPos == 1) {
4469  // It recurs on the same day each week
4470  if (!mRepetition || weeklyRepeat) {
4471  return; // any repetitions are also weekly
4472  }
4473 
4474  // It's a weekly recurrence with a non-weekly sub-repetition.
4475  // Check one cycle of repetitions for the next one that lands
4476  // on a working day.
4477  KADateTime dt(nextTrigger.kDateTime().addDays(1));
4478  dt.setTime(QTime(0, 0, 0));
4479  previousOccurrence(dt, newdt, false);
4480  if (!newdt.isValid()) {
4481  return; // this should never happen
4482  }
4483  kdt = newdt.effectiveKDateTime();
4484  const int day = kdt.date().dayOfWeek() - 1; // Monday = 0
4485  for (int repeatNum = mNextRepeat + 1; ; ++repeatNum) {
4486  if (repeatNum > mRepetition.count()) {
4487  repeatNum = 0;
4488  }
4489  if (repeatNum == mNextRepeat) {
4490  break;
4491  }
4492  if (!repeatNum) {
4494  if (mWorkDays.testBit(day)) {
4495  mMainWorkTrigger = newdt;
4496  mAllWorkTrigger = mMainWorkTrigger.addMins(-reminder);
4497  return;
4498  }
4499  kdt = newdt.effectiveKDateTime();
4500  } else {
4501  const int inc = repeatFreq * repeatNum;
4502  if (mWorkDays.testBit((day + inc) % 7)) {
4503  kdt = kdt.addDays(inc);
4504  kdt.setDateOnly(true);
4505  mMainWorkTrigger = mAllWorkTrigger = kdt;
4506  return;
4507  }
4508  }
4509  }
4510  return;
4511  }
4512  if (!mRepetition || weeklyRepeat) {
4513  // It's a date-only alarm with either no sub-repetition or a
4514  // sub-repetition which always falls on the same day of the week
4515  // as the recurrence (if any).
4516  unsigned days = 0;
4517  for (; ;) {
4518  kdt.setTime(QTime(23, 59, 59));
4520  if (!newdt.isValid()) {
4521  return;
4522  }
4523  kdt = newdt.effectiveKDateTime();
4524  const int day = kdt.date().dayOfWeek() - 1;
4525  if (mWorkDays.testBit(day)) {
4526  break; // found a working day occurrence
4527  }
4528  // Prevent indefinite looping (which should never happen anyway)
4529  if ((days & allDaysMask) == allDaysMask) {
4530  return; // found a recurrence on every possible day of the week!?!
4531  }
4532  days |= 1 << day;
4533  }
4534  kdt.setDateOnly(true);
4535  mMainWorkTrigger = kdt;
4536  mAllWorkTrigger = kdt.addSecs(-60 * reminder);
4537  return;
4538  }
4539 
4540  // It's a date-only alarm which recurs on different days of the week,
4541  // as does the sub-repetition.
4542  // Find the previous recurrence (as opposed to sub-repetition)
4543  unsigned days = 1 << (kdt.date().dayOfWeek() - 1);
4544  KADateTime dt(nextTrigger.kDateTime().addDays(1));
4545  dt.setTime(QTime(0, 0, 0));
4546  previousOccurrence(dt, newdt, false);
4547  if (!newdt.isValid()) {
4548  return; // this should never happen
4549  }
4550  kdt = newdt.effectiveKDateTime();
4551  int day = kdt.date().dayOfWeek() - 1; // Monday = 0
4552  for (int repeatNum = mNextRepeat; ; repeatNum = 0) {
4553  while (++repeatNum <= mRepetition.count()) {
4554  const int inc = repeatFreq * repeatNum;
4555  if (mWorkDays.testBit((day + inc) % 7)) {
4556  kdt = kdt.addDays(inc);
4557  kdt.setDateOnly(true);
4558  mMainWorkTrigger = mAllWorkTrigger = kdt;
4559  return;
4560  }
4561  if ((days & allDaysMask) == allDaysMask) {
4562  return; // found an occurrence on every possible day of the week!?!
4563  }
4564  days |= 1 << day;
4565  }
4567  if (!newdt.isValid()) {
4568  return;
4569  }
4570  kdt = newdt.effectiveKDateTime();
4571  day = kdt.date().dayOfWeek() - 1;
4572  if (mWorkDays.testBit(day)) {
4573  kdt.setDateOnly(true);
4574  mMainWorkTrigger = kdt;
4575  mAllWorkTrigger = kdt.addSecs(-60 * reminder);
4576  return;
4577  }
4578  if ((days & allDaysMask) == allDaysMask) {
4579  return; // found an occurrence on every possible day of the week!?!
4580  }
4581  days |= 1 << day;
4582  }
4583  return;
4584  }
4585 
4586  // It's a date-time alarm.
4587 
4588  /* Check whether the recurrence or sub-repetition occurs at the same time
4589  * every day. Note that because of seasonal time changes, a recurrence
4590  * defined in terms of minutes will vary its time of day even if its value
4591  * is a multiple of a day (24*60 minutes). Sub-repetitions are considered
4592  * to repeat at the same time of day regardless of time changes if they
4593  * are multiples of a day, which doesn't strictly conform to the iCalendar
4594  * format because this only allows their interval to be recorded in seconds.
4595  */
4596  const bool recurTimeVaries = (recurType == KARecurrence::MINUTELY);
4597  const bool repeatTimeVaries = (mRepetition && !mRepetition.isDaily());
4598 
4599  if (!recurTimeVaries && !repeatTimeVaries) {
4600  // The alarm always occurs at the same time of day.
4601  // Check whether it can ever occur during working hours.
4602  if (!mayOccurDailyDuringWork(kdt)) {
4603  return; // never occurs during working hours
4604  }
4605 
4606  // Find the next working day it occurs on
4607  bool repetition = false;
4608  unsigned days = 0;
4609  for (; ;) {
4611  if (!newdt.isValid()) {
4612  return;
4613  }
4614  repetition = (type & KAEvent::OCCURRENCE_REPEAT);
4615  kdt = newdt.effectiveKDateTime();
4616  const int day = kdt.date().dayOfWeek() - 1;
4617  if (mWorkDays.testBit(day)) {
4618  break; // found a working day occurrence
4619  }
4620  // Prevent indefinite looping (which should never happen anyway)
4621  if (!repetition) {
4622  if ((days & allDaysMask) == allDaysMask) {
4623  return; // found a recurrence on every possible day of the week!?!
4624  }
4625  days |= 1 << day;
4626  }
4627  }
4628  mMainWorkTrigger = nextTrigger;
4629  mMainWorkTrigger.setDate(kdt.date());
4630  mAllWorkTrigger = repetition ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder);
4631  return;
4632  }
4633 
4634  // The alarm occurs at different times of day.
4635  // We may need to check for a full annual cycle of seasonal time changes, in
4636  // case it only occurs during working hours after a time change.
4637  const QTimeZone tz = kdt.timeZone();
4638  // Get time zone transitions for the next 10 years.
4639  const QDateTime endTransitionsTime = QDateTime::currentDateTimeUtc().addYears(10);
4640  const QTimeZone::OffsetDataList tzTransitions = tz.transitions(mStartDateTime.qDateTime(), endTransitionsTime);
4641 
4642  if (recurTimeVaries) {
4643  /* The alarm recurs at regular clock intervals, at different times of day.
4644  * Note that for this type of recurrence, it's necessary to avoid the
4645  * performance overhead of Recurrence class calls since these can in the
4646  * worst case cause the program to hang for a significant length of time.
4647  * In this case, we can calculate the next recurrence by simply adding the
4648  * recurrence interval, since KAlarm offers no facility to regularly miss
4649  * recurrences. (But exception dates/times need to be taken into account.)
4650  */
4651  KADateTime kdtRecur;
4652  int repeatFreq = 0;
4653  int repeatNum = 0;
4654  if (mRepetition) {
4655  // It's a repetition inside a recurrence, each of which occurs
4656  // at different times of day (bearing in mind that the repetition
4657  // may occur at daily intervals after each recurrence).
4658  // Find the previous recurrence (as opposed to sub-repetition)
4659  repeatFreq = mRepetition.intervalSeconds();
4660  previousOccurrence(kdt.addSecs(1), newdt, false);
4661  if (!newdt.isValid()) {
4662  return; // this should never happen
4663  }
4664  kdtRecur = newdt.effectiveKDateTime();
4665  repeatNum = kdtRecur.secsTo(kdt) / repeatFreq;
4666  kdt = kdtRecur.addSecs(repeatNum * repeatFreq);
4667  } else {
4668  // There is no sub-repetition.
4669  // (N.B. Sub-repetitions can't exist without a recurrence.)
4670  // Check until the original time wraps round, but ensure that
4671  // if there are seasonal time changes, that all other subsequent
4672  // time offsets within the next year are checked.
4673  // This does not guarantee to find the next working time,
4674  // particularly if there are exceptions, but it's a
4675  // reasonable try.
4676  kdtRecur = kdt;
4677  }
4678  QTime firstTime = kdtRecur.time();
4679  int firstOffset = kdtRecur.utcOffset();
4680  int currentOffset = firstOffset;
4681  int dayRecur = kdtRecur.date().dayOfWeek() - 1; // Monday = 0
4682  int firstDay = dayRecur;
4683  QDate finalDate;
4684  const bool subdaily = (repeatFreq < 24 * 3600);
4685 // int period = mRecurrence->frequency() % (24*60); // it is by definition a MINUTELY recurrence
4686 // int limit = (24*60 + period - 1) / period; // number of times until recurrence wraps round
4687  int transitionIndex = -1;
4688  for (int n = 0; n < 7 * 24 * 60; ++n) {
4689  if (mRepetition) {
4690  // Check the sub-repetitions for this recurrence
4691  for (; ;) {
4692  // Find the repeat count to the next start of the working day
4693  const int inc = subdaily ? nextWorkRepetition(kdt) : 1;
4694  repeatNum += inc;
4695  if (repeatNum > mRepetition.count()) {
4696  break;
4697  }
4698  kdt = kdt.addSecs(inc * repeatFreq);
4699  const QTime t = kdt.time();
4700  if (t >= mWorkDayStart && t < mWorkDayEnd) {
4701  if (mWorkDays.testBit(kdt.date().dayOfWeek() - 1)) {
4702  mMainWorkTrigger = mAllWorkTrigger = kdt;
4703  return;
4704  }
4705  }
4706  }
4707  repeatNum = 0;
4708  }
4709  nextOccurrence(kdtRecur, newdt, KAEvent::IGNORE_REPETITION);
4710  if (!newdt.isValid()) {
4711  return;
4712  }
4713  kdtRecur = newdt.effectiveKDateTime();
4714  dayRecur = kdtRecur.date().dayOfWeek() - 1; // Monday = 0
4715  const QTime t = kdtRecur.time();
4716  if (t >= mWorkDayStart && t < mWorkDayEnd) {
4717  if (mWorkDays.testBit(dayRecur)) {
4718  mMainWorkTrigger = kdtRecur;
4719  mAllWorkTrigger = kdtRecur.addSecs(-60 * reminder);
4720  return;
4721  }
4722  }
4723  if (kdtRecur.utcOffset() != currentOffset) {
4724  currentOffset = kdtRecur.utcOffset();
4725  }
4726  if (t == firstTime && dayRecur == firstDay && currentOffset == firstOffset) {
4727  // We've wrapped round to the starting day and time.
4728  // If there are seasonal time changes, check for up
4729  // to the next year in other time offsets in case the
4730  // alarm occurs inside working hours then.
4731  if (!finalDate.isValid()) {
4732  finalDate = kdtRecur.date();
4733  }
4734  const int i = KAEventPrivate::transitionIndex(kdtRecur.toUtc().qDateTime(), tzTransitions);
4735  if (i < 0) {
4736  return;
4737  }
4738  if (i > transitionIndex) {
4739  transitionIndex = i;
4740  }
4741  if (++transitionIndex >= tzTransitions.count()) {
4742  return;
4743  }
4744  previousOccurrence(KADateTime(tzTransitions[transitionIndex].atUtc), newdt, KAEvent::IGNORE_REPETITION);
4745  kdtRecur = newdt.effectiveKDateTime();
4746  if (finalDate.daysTo(kdtRecur.date()) > 365) {
4747  return;
4748  }
4749  firstTime = kdtRecur.time();
4750  firstOffset = kdtRecur.utcOffset();
4751  currentOffset = firstOffset;
4752  firstDay = kdtRecur.date().dayOfWeek() - 1;
4753  }
4754  kdt = kdtRecur;
4755  }
4756 //qCDebug(KALARMCAL_LOG)<<"-----exit loop: count="<<limit<<endl;
4757  return; // too many iterations
4758  }
4759 
4760  if (repeatTimeVaries) {
4761  /* There's a sub-repetition which occurs at different times of
4762  * day, inside a recurrence which occurs at the same time of day.
4763  * We potentially need to check recurrences starting on each day.
4764  * Then, it is still possible that a working time sub-repetition
4765  * could occur immediately after a seasonal time change.
4766  */
4767  // Find the previous recurrence (as opposed to sub-repetition)
4768  const int repeatFreq = mRepetition.intervalSeconds();
4769  previousOccurrence(kdt.addSecs(1), newdt, false);
4770  if (!newdt.isValid()) {
4771  return; // this should never happen
4772  }
4773  KADateTime kdtRecur = newdt.effectiveKDateTime();
4774  const bool recurDuringWork = (kdtRecur.time() >= mWorkDayStart && kdtRecur.time() < mWorkDayEnd);
4775 
4776  // Use the previous recurrence as a base for checking whether
4777  // our tests have wrapped round to the same time/day of week.
4778  const bool subdaily = (repeatFreq < 24 * 3600);
4779  unsigned days = 0;
4780  bool checkTimeChangeOnly = false;
4781  int transitionIndex = -1;
4782  for (int limit = 10; --limit >= 0;) {
4783  // Check the next seasonal time change (for an arbitrary 10 times,
4784  // even though that might not guarantee the correct result)
4785  QDate dateRecur = kdtRecur.date();
4786  int repeatNum = kdtRecur.secsTo(kdt) / repeatFreq;
4787  kdt = kdtRecur.addSecs(repeatNum * repeatFreq);
4788 
4789  // Find the next recurrence, which sets the limit on possible sub-repetitions.
4790  // Note that for a monthly recurrence, for example, a sub-repetition could
4791  // be defined which is longer than the recurrence interval in short months.
4792  // In these cases, the sub-repetition is truncated by the following
4793  // recurrence.
4794  nextOccurrence(kdtRecur, newdt, KAEvent::IGNORE_REPETITION);
4795  KADateTime kdtNextRecur = newdt.effectiveKDateTime();
4796 
4797  int repeatsToCheck = mRepetition.count();
4798  int repeatsDuringWork = 0; // 0=unknown, 1=does, -1=never
4799  for (; ;) {
4800  // Check the sub-repetitions for this recurrence
4801  if (repeatsDuringWork >= 0) {
4802  for (; ;) {
4803  // Find the repeat count to the next start of the working day
4804  int inc = subdaily ? nextWorkRepetition(kdt) : 1;
4805  repeatNum += inc;
4806  const bool pastEnd = (repeatNum > mRepetition.count());
4807  if (pastEnd) {
4808  inc -= repeatNum - mRepetition.count();
4809  }
4810  repeatsToCheck -= inc;
4811  kdt = kdt.addSecs(inc * repeatFreq);
4812  if (kdtNextRecur.isValid() && kdt >= kdtNextRecur) {
4813  // This sub-repetition is past the next recurrence,
4814  // so start the check again from the next recurrence.
4815  repeatsToCheck = mRepetition.count();
4816  break;
4817  }
4818  if (pastEnd) {
4819  break;
4820  }
4821  const QTime t = kdt.time();
4822  if (t >= mWorkDayStart && t < mWorkDayEnd) {
4823  if (mWorkDays.testBit(kdt.date().dayOfWeek() - 1)) {
4824  mMainWorkTrigger = mAllWorkTrigger = kdt;
4825  return;
4826  }
4827  repeatsDuringWork = 1;
4828  } else if (!repeatsDuringWork && repeatsToCheck <= 0) {
4829  // Sub-repetitions never occur during working hours
4830  repeatsDuringWork = -1;
4831  break;
4832  }
4833  }
4834  }
4835  repeatNum = 0;
4836  if (repeatsDuringWork < 0 && !recurDuringWork) {
4837  break; // it never occurs during working hours
4838  }
4839 
4840  // Check the next recurrence
4841  if (!kdtNextRecur.isValid()) {
4842  return;
4843  }
4844  if (checkTimeChangeOnly || (days & allDaysMask) == allDaysMask) {
4845  break; // found a recurrence on every possible day of the week!?!
4846  }
4847  kdtRecur = kdtNextRecur;
4848  nextOccurrence(kdtRecur, newdt, KAEvent::IGNORE_REPETITION);
4849  kdtNextRecur = newdt.effectiveKDateTime();
4850  dateRecur = kdtRecur.date();
4851  const int dayRecur = dateRecur.dayOfWeek() - 1; // Monday = 0
4852  if (recurDuringWork && mWorkDays.testBit(dayRecur)) {
4853  mMainWorkTrigger = kdtRecur;
4854  mAllWorkTrigger = kdtRecur.addSecs(-60 * reminder);
4855  return;
4856  }
4857  days |= 1 << dayRecur;
4858  kdt = kdtRecur;
4859  }
4860 
4861  // Find the next recurrence before a seasonal time change,
4862  // and ensure the time change is after the last one processed.
4863  checkTimeChangeOnly = true;
4864  const int i = KAEventPrivate::transitionIndex(kdtRecur.toUtc().qDateTime(), tzTransitions);
4865  if (i < 0) {
4866  return;
4867  }
4868  if (i > transitionIndex) {
4869  transitionIndex = i;
4870  }
4871  if (++transitionIndex >= tzTransitions.count()) {
4872  return;
4873  }
4874  kdt = KADateTime(tzTransitions[transitionIndex].atUtc);
4876  kdtRecur = newdt.effectiveKDateTime();
4877  }
4878  return; // not found - give up
4879  }
4880 }
4881 
4882 /******************************************************************************
4883 * Find the repeat count to the next start of a working day.
4884 * This allows for possible daylight saving time changes during the repetition.
4885 * Use for repetitions which occur at different times of day.
4886 */
4887 int KAEventPrivate::nextWorkRepetition(const KADateTime &pre) const
4888 {
4889  KADateTime nextWork(pre);
4890  if (pre.time() < mWorkDayStart) {
4891  nextWork.setTime(mWorkDayStart);
4892  } else {
4893  const int preDay = pre.date().dayOfWeek() - 1; // Monday = 0
4894  for (int n = 1; ; ++n) {
4895  if (n >= 7) {
4896  return mRepetition.count() + 1; // should never happen
4897  }
4898  if (mWorkDays.testBit((preDay + n) % 7)) {
4899  nextWork = nextWork.addDays(n);
4900  nextWork.setTime(mWorkDayStart);
4901  break;
4902  }
4903  }
4904  }
4905  return (pre.secsTo(nextWork) - 1) / mRepetition.intervalSeconds() + 1;
4906 }
4907 
4908 /******************************************************************************
4909 * Check whether an alarm which recurs at the same time of day can possibly
4910 * occur during working hours.
4911 * This does not determine whether it actually does, but rather whether it could
4912 * potentially given enough repetitions.
4913 * Reply = false if it can never occur during working hours, true if it might.
4914 */
4915 bool KAEventPrivate::mayOccurDailyDuringWork(const KADateTime &kdt) const
4916 {
4917  if (!kdt.isDateOnly()
4918  && (kdt.time() < mWorkDayStart || kdt.time() >= mWorkDayEnd)) {
4919  return false; // its time is outside working hours
4920  }
4921  // Check if it always occurs on the same day of the week
4922  const Duration interval = mRecurrence->regularInterval();
4923  if (!interval.isNull() && interval.isDaily() && !(interval.asDays() % 7)) {
4924  // It recurs weekly
4925  if (!mRepetition || (mRepetition.isDaily() && !(mRepetition.intervalDays() % 7))) {
4926  return false; // any repetitions are also weekly
4927  }
4928  // Repetitions are daily. Check if any occur on working days
4929  // by checking the first recurrence and up to 6 repetitions.
4930  int day = mRecurrence->startDateTime().date().dayOfWeek() - 1; // Monday = 0
4931  const int repeatDays = mRepetition.intervalDays();
4932  const int maxRepeat = (mRepetition.count() < 6) ? mRepetition.count() : 6;
4933  for (int i = 0; !mWorkDays.testBit(day); ++i, day = (day + repeatDays) % 7) {
4934  if (i >= maxRepeat) {
4935  return false; // no working day occurrences
4936  }
4937  }
4938  }
4939  return true;
4940 }
4941 
4942 /******************************************************************************
4943 * Set the specified alarm to be an audio alarm with the given file name.
4944 */
4945 void KAEventPrivate::setAudioAlarm(const Alarm::Ptr &alarm) const
4946 {
4947  alarm->setAudioAlarm(mAudioFile); // empty for a beep or for speaking
4948  if (mSoundVolume >= 0) {
4949  alarm->setCustomProperty(KACalendar::APPNAME, VOLUME_PROPERTY,
4950  QStringLiteral("%1;%2;%3").arg(QString::number(mSoundVolume, 'f', 2), QString::number(mFadeVolume, 'f', 2), QString::number(mFadeSeconds)));
4951  }
4952 }
4953 
4954 /******************************************************************************
4955 * Get the date/time of the next recurrence of the event, after the specified
4956 * date/time.
4957 * 'result' = date/time of next occurrence, or invalid date/time if none.
4958 */
4959 KAEvent::OccurType KAEventPrivate::nextRecurrence(const KADateTime &preDateTime, DateTime &result) const
4960 {
4961  const KADateTime recurStart = mRecurrence->startDateTime();
4962  KADateTime pre = preDateTime.toTimeSpec(mStartDateTime.timeSpec());
4963  if (mStartDateTime.isDateOnly() && !pre.isDateOnly() && pre.time() < DateTime::startOfDay()) {
4964  pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come
4966  }
4967  const KADateTime dt = mRecurrence->getNextDateTime(pre);
4968  result = dt;
4969  result.setDateOnly(mStartDateTime.isDateOnly());
4970  if (!dt.isValid()) {
4971  return KAEvent::NO_OCCURRENCE;
4972  }
4973  if (dt == recurStart) {
4975  }
4976  if (mRecurrence->duration() >= 0 && dt == mRecurrence->endDateTime()) {
4977  return KAEvent::LAST_RECURRENCE;
4978  }
4980 }
4981 
4982 /******************************************************************************
4983 * Validate the event's recurrence data, correcting any inconsistencies (which
4984 * should never occur!).
4985 * Reply = recurrence period type.
4986 */
4987 KARecurrence::Type KAEventPrivate::checkRecur() const
4988 {
4989  if (mRecurrence) {
4990  KARecurrence::Type type = mRecurrence->type();
4991  switch (type) {
4992  case KARecurrence::MINUTELY: // hourly
4993  case KARecurrence::DAILY: // daily
4994  case KARecurrence::WEEKLY: // weekly on multiple days of week
4995  case KARecurrence::MONTHLY_DAY: // monthly on multiple dates in month
4996  case KARecurrence::MONTHLY_POS: // monthly on multiple nth day of week
4997  case KARecurrence::ANNUAL_DATE: // annually on multiple months (day of month = start date)
4998  case KARecurrence::ANNUAL_POS: // annually on multiple nth day of week in multiple months
4999  return type;
5000  default:
5001  if (mRecurrence) {
5002  const_cast<KAEventPrivate *>(this)->clearRecur(); // this shouldn't ever be necessary!!
5003  }
5004  break;
5005  }
5006  }
5007  if (mRepetition) { // can't have a repetition without a recurrence
5008  const_cast<KAEventPrivate *>(this)->clearRecur(); // this shouldn't ever be necessary!!
5009  }
5010  return KARecurrence::NO_RECUR;
5011 }
5012 
5013 /******************************************************************************
5014 * If the calendar was written by a previous version of KAlarm, do any
5015 * necessary format conversions on the events to ensure that when the calendar
5016 * is saved, no information is lost or corrupted.
5017 * Reply = true if any conversions were done.
5018 */
5019 bool KAEvent::convertKCalEvents(const Calendar::Ptr &calendar, int calendarVersion)
5020 {
5021  // KAlarm pre-0.9 codes held in the alarm's DESCRIPTION property
5022  static const QChar SEPARATOR = QLatin1Char(';');
5023  static const QChar LATE_CANCEL_CODE = QLatin1Char('C');
5024  static const QChar AT_LOGIN_CODE = QLatin1Char('L'); // subsidiary alarm at every login
5025  static const QChar DEFERRAL_CODE = QLatin1Char('D'); // extra deferred alarm
5026  static const QString TEXT_PREFIX = QStringLiteral("TEXT:");
5027  static const QString FILE_PREFIX = QStringLiteral("FILE:");
5028  static const QString COMMAND_PREFIX = QStringLiteral("CMD:");
5029 
5030  // KAlarm pre-0.9.2 codes held in the event's CATEGORY property
5031  static const QString BEEP_CATEGORY = QStringLiteral("BEEP");
5032 
5033  // KAlarm pre-1.1.1 LATECANCEL category with no parameter
5034  static const QString LATE_CANCEL_CAT = QStringLiteral("LATECANCEL");
5035 
5036  // KAlarm pre-1.3.0 TMPLDEFTIME category with no parameter
5037  static const QString TEMPL_DEF_TIME_CAT = QStringLiteral("TMPLDEFTIME");
5038 
5039  // KAlarm pre-1.3.1 XTERM category
5040  static const QString EXEC_IN_XTERM_CAT = QStringLiteral("XTERM");
5041 
5042  // KAlarm pre-1.9.0 categories
5043  static const QString DATE_ONLY_CATEGORY = QStringLiteral("DATE");
5044  static const QString EMAIL_BCC_CATEGORY = QStringLiteral("BCC");
5045  static const QString CONFIRM_ACK_CATEGORY = QStringLiteral("ACKCONF");
5046  static const QString KORGANIZER_CATEGORY = QStringLiteral("KORG");
5047  static const QString DEFER_CATEGORY = QStringLiteral("DEFER;");
5048  static const QString ARCHIVE_CATEGORY = QStringLiteral("SAVE");
5049  static const QString ARCHIVE_CATEGORIES = QStringLiteral("SAVE:");
5050  static const QString LATE_CANCEL_CATEGORY = QStringLiteral("LATECANCEL;");
5051  static const QString AUTO_CLOSE_CATEGORY = QStringLiteral("LATECLOSE;");
5052  static const QString TEMPL_AFTER_TIME_CATEGORY = QStringLiteral("TMPLAFTTIME;");
5053  static const QString KMAIL_SERNUM_CATEGORY = QStringLiteral("KMAIL:");
5054  static const QString LOG_CATEGORY = QStringLiteral("LOG:");
5055 
5056  // KAlarm pre-1.5.0/1.9.9 properties
5057  static const QByteArray KMAIL_ID_PROPERTY("KMAILID"); // X-KDE-KALARM-KMAILID property
5058 
5059  // KAlarm pre-2.6.0 properties
5060  static const QByteArray ARCHIVE_PROPERTY("ARCHIVE"); // X-KDE-KALARM-ARCHIVE property
5061  static const QString ARCHIVE_REMINDER_ONCE_TYPE = QStringLiteral("ONCE");
5062  static const QString REMINDER_ONCE_TYPE = QStringLiteral("REMINDER_ONCE");
5063  static const QByteArray EMAIL_ID_PROPERTY("EMAILID"); // X-KDE-KALARM-EMAILID property
5064  static const QByteArray SPEAK_PROPERTY("SPEAK"); // X-KDE-KALARM-SPEAK property
5065  static const QByteArray CANCEL_ON_ERROR_PROPERTY("ERRCANCEL");// X-KDE-KALARM-ERRCANCEL property
5066  static const QByteArray DONT_SHOW_ERROR_PROPERTY("ERRNOSHOW");// X-KDE-KALARM-ERRNOSHOW property
5067 
5068  bool adjustSummerTime = false;
5069  if (calendarVersion == -Version(0, 5, 7)) {
5070  // The calendar file was written by the KDE 3.0.0 version of KAlarm 0.5.7.
5071  // Summer time was ignored when converting to UTC.
5072  calendarVersion = -calendarVersion;
5073  adjustSummerTime = true;
5074  }
5075 
5076  if (calendarVersion >= currentCalendarVersion()) {
5077  return false;
5078  }
5079 
5080  qCDebug(KALARMCAL_LOG) << "Adjusting version" << calendarVersion;
5081  const bool pre_0_7 = (calendarVersion < Version(0, 7, 0));
5082  const bool pre_0_9 = (calendarVersion < Version(0, 9, 0));
5083  const bool pre_0_9_2 = (calendarVersion < Version(0, 9, 2));
5084  const bool pre_1_1_1 = (calendarVersion < Version(1, 1, 1));
5085  const bool pre_1_2_1 = (calendarVersion < Version(1, 2, 1));
5086  const bool pre_1_3_0 = (calendarVersion < Version(1, 3, 0));
5087  const bool pre_1_3_1 = (calendarVersion < Version(1, 3, 1));
5088  const bool pre_1_4_14 = (calendarVersion < Version(1, 4, 14));
5089  const bool pre_1_5_0 = (calendarVersion < Version(1, 5, 0));
5090  const bool pre_1_9_0 = (calendarVersion < Version(1, 9, 0));
5091  const bool pre_1_9_2 = (calendarVersion < Version(1, 9, 2));
5092  const bool pre_1_9_7 = (calendarVersion < Version(1, 9, 7));
5093  const bool pre_1_9_9 = (calendarVersion < Version(1, 9, 9));
5094  const bool pre_1_9_10 = (calendarVersion < Version(1, 9, 10));
5095  const bool pre_2_2_9 = (calendarVersion < Version(2, 2, 9));
5096  const bool pre_2_3_0 = (calendarVersion < Version(2, 3, 0));
5097  const bool pre_2_3_2 = (calendarVersion < Version(2, 3, 2));
5098  const bool pre_2_7_0 = (calendarVersion < Version(2, 7, 0));
5099  Q_ASSERT(currentCalendarVersion() == Version(2, 7, 0));
5100 
5101  const QTimeZone localZone = QTimeZone::systemTimeZone();
5102 
5103  bool converted = false;
5104  const Event::List events = calendar->rawEvents();
5105  for (Event::Ptr event : events) {
5106  const Alarm::List alarms = event->alarms();
5107  if (alarms.isEmpty()) {
5108  continue; // KAlarm isn't interested in events without alarms
5109  }
5110  event->startUpdates(); // prevent multiple update notifications
5111  const bool readOnly = event->isReadOnly();
5112  if (readOnly) {
5113  event->setReadOnly(false);
5114  }
5115  QStringList cats = event->categories();
5116  bool addLateCancel = false;
5118 
5119  if (pre_0_7 && event->allDay()) {
5120  // It's a KAlarm pre-0.7 calendar file.
5121  // Ensure that when the calendar is saved, the alarm time isn't lost.
5122  event->setAllDay(false);
5123  }
5124 
5125  if (pre_0_9) {
5126  /*
5127  * It's a KAlarm pre-0.9 calendar file.
5128  * All alarms were of type DISPLAY. Instead of the X-KDE-KALARM-TYPE
5129  * alarm property, characteristics were stored as a prefix to the
5130  * alarm DESCRIPTION property, as follows:
5131  * SEQNO;[FLAGS];TYPE:TEXT
5132  * where
5133  * SEQNO = sequence number of alarm within the event
5134  * FLAGS = C for late-cancel, L for repeat-at-login, D for deferral
5135  * TYPE = TEXT or FILE or CMD
5136  * TEXT = message text, file name/URL or command
5137  */
5138  for (Alarm::Ptr alarm : alarms) {
5139  bool atLogin = false;
5140  bool deferral = false;
5141  bool lateCancel = false;
5143  const QString txt = alarm->text();
5144  const int length = txt.length();
5145  int i = 0;
5146  if (txt[0].isDigit()) {
5147  while (++i < length && txt[i].isDigit()) {
5148  ;
5149  }
5150  if (i < length && txt[i++] == SEPARATOR) {
5151  while (i < length) {
5152  const QChar ch = txt[i++];
5153  if (ch == SEPARATOR) {
5154  break;
5155  }
5156  if (ch == LATE_CANCEL_CODE) {
5157  lateCancel = true;
5158  } else if (ch == AT_LOGIN_CODE) {
5159  atLogin = true;
5160  } else if (ch == DEFERRAL_CODE) {
5161  deferral = true;
5162  }
5163  }
5164  } else {
5165  i = 0; // invalid prefix
5166  }
5167  }
5168  if (txt.indexOf(TEXT_PREFIX, i) == i) {
5169  i += TEXT_PREFIX.length();
5170  } else if (txt.indexOf(FILE_PREFIX, i) == i) {
5171  action = KAAlarm::FILE;
5172  i += FILE_PREFIX.length();
5173  } else if (txt.indexOf(COMMAND_PREFIX, i) == i) {
5174  action = KAAlarm::COMMAND;
5175  i += COMMAND_PREFIX.length();
5176  } else {
5177  i = 0;
5178  }
5179  const QString altxt = txt.mid(i);
5180 
5181  QStringList types;
5182  switch (action) {
5183  case KAAlarm::FILE:
5184  types += KAEventPrivate::FILE_TYPE;
5185  // fall through to MESSAGE
5186  Q_FALLTHROUGH();
5187  case KAAlarm::MESSAGE:
5188  alarm->setDisplayAlarm(altxt);
5189  break;
5190  case KAAlarm::COMMAND:
5191  setProcedureAlarm(alarm, altxt);
5192  break;
5193  case KAAlarm::EMAIL: // email alarms were introduced in KAlarm 0.9
5194  case KAAlarm::AUDIO: // audio alarms (with no display) were introduced in KAlarm 2.3.2
5195  break;
5196  }
5197  if (atLogin) {
5198  types += KAEventPrivate::AT_LOGIN_TYPE;
5199  lateCancel = false;
5200  } else if (deferral) {
5201  types += KAEventPrivate::TIME_DEFERRAL_TYPE;
5202  }
5203  if (lateCancel) {
5204  addLateCancel = true;
5205  }
5206  if (types.count() > 0) {
5207  alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY, types.join(QLatin1Char(',')));
5208  }
5209 
5210  if (pre_0_7 && alarm->repeatCount() > 0 && alarm->snoozeTime().value() > 0) {
5211  // It's a KAlarm pre-0.7 calendar file.
5212  // Minutely recurrences were stored differently.
5213  Recurrence *recur = event->recurrence();
5214  if (recur && recur->recurs()) {
5215  recur->setMinutely(alarm->snoozeTime().asSeconds() / 60);
5216  recur->setDuration(alarm->repeatCount() + 1);
5217  alarm->setRepeatCount(0);
5218  alarm->setSnoozeTime(0);
5219  }
5220  }
5221 
5222  if (adjustSummerTime) {
5223  // The calendar file was written by the KDE 3.0.0 version of KAlarm 0.5.7.
5224  // Summer time was ignored when converting to UTC.
5225  KADateTime dt(alarm->time());
5226  const qint64 t64 = dt.toSecsSinceEpoch();
5227  const time_t t = (static_cast<quint64>(t64) >= uint(-1)) ? uint(-1) : static_cast<uint>(t64);
5228  const struct tm *dtm = localtime(&t);
5229  if (dtm->tm_isdst) {
5230  dt = dt.addSecs(-3600);
5231  alarm->setTime(dt.qDateTime());
5232  }
5233  }
5234  }
5235  }
5236 
5237  if (pre_0_9_2) {
5238  /*
5239  * It's a KAlarm pre-0.9.2 calendar file.
5240  * For the archive calendar, set the CREATED time to the DTEND value.
5241  * Convert date-only DTSTART to date/time, and add category "DATE".
5242  * Set the DTEND time to the DTSTART time.
5243  * Convert all alarm times to DTSTART offsets.
5244  * For display alarms, convert the first unlabelled category to an
5245  * X-KDE-KALARM-FONTCOLOR property.
5246  * Convert BEEP category into an audio alarm with no audio file.
5247  */
5248  if (CalEvent::status(event) == CalEvent::ARCHIVED) {
5249  event->setCreated(event->dtEnd());
5250  }
5251  QDateTime start = event->dtStart();
5252  if (event->allDay()) {
5253  start.setTime(QTime(0, 0));
5254  flags += KAEventPrivate::DATE_ONLY_FLAG;
5255  }
5256  event->setDtEnd(QDateTime());
5257 
5258  for (Alarm::Ptr alarm : alarms) {
5259  alarm->setStartOffset(start.secsTo(alarm->time()));
5260  }
5261 
5262  if (!cats.isEmpty()) {
5263  for (const Alarm::Ptr &alarm : alarms) { //clazy:exclude=range-loop Can't use reference because 'alarms' is const
5264  if (alarm->type() == Alarm::Display) {
5265  alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FONT_COLOUR_PROPERTY,
5266  QStringLiteral("%1;;").arg(cats.at(0)));
5267  }
5268  }
5269  cats.removeAt(0);
5270  }
5271 
5272  {
5273  int i = cats.indexOf(BEEP_CATEGORY);
5274  if (i >= 0) {
5275  cats.removeAt(i);
5276 
5277  Alarm::Ptr alarm = event->newAlarm();
5278  alarm->setEnabled(true);
5279  alarm->setAudioAlarm();
5280  QDateTime dt = event->dtStart(); // default
5281 
5282  // Parse and order the alarms to know which one's date/time to use
5283  KAEventPrivate::AlarmMap alarmMap;
5284  KAEventPrivate::readAlarms(event, &alarmMap);
5286  if (it != alarmMap.constEnd()) {
5287  dt = it.value().alarm->time();
5288  break;
5289  }
5290  alarm->setStartOffset(start.secsTo(dt));
5291  break;
5292  }
5293  }
5294  }
5295 
5296  if (pre_1_1_1) {
5297  /*
5298  * It's a KAlarm pre-1.1.1 calendar file.
5299  * Convert simple LATECANCEL category to LATECANCEL:n where n = minutes late.
5300  */
5301  int i;
5302  while ((i = cats.indexOf(LATE_CANCEL_CAT)) >= 0) {
5303  cats.removeAt(i);
5304  addLateCancel = true;
5305  }
5306  }
5307 
5308  if (pre_1_2_1) {
5309  /*
5310  * It's a KAlarm pre-1.2.1 calendar file.
5311  * Convert email display alarms from translated to untranslated header prefixes.
5312  */
5313  for (Alarm::Ptr alarm : alarms) {
5314  if (alarm->type() == Alarm::Display) {
5315  const QString oldtext = alarm->text();
5316  const QString newtext = AlarmText::toCalendarText(oldtext);
5317  if (oldtext != newtext) {
5318  alarm->setDisplayAlarm(newtext);
5319  }
5320  }
5321  }
5322  }
5323 
5324  if (pre_1_3_0) {
5325  /*
5326  * It's a KAlarm pre-1.3.0 calendar file.
5327  * Convert simple TMPLDEFTIME category to TMPLAFTTIME:n where n = minutes after.
5328  */
5329  int i;
5330  while ((i = cats.indexOf(TEMPL_DEF_TIME_CAT)) >= 0) {
5331  cats.removeAt(i);
5332  (flags += KAEventPrivate::TEMPL_AFTER_TIME_FLAG) += QStringLiteral("0");
5333  }
5334  }
5335 
5336  if (pre_1_3_1) {
5337  /*
5338  * It's a KAlarm pre-1.3.1 calendar file.
5339  * Convert simple XTERM category to LOG:xterm:
5340  */
5341  int i;
5342  while ((i = cats.indexOf(EXEC_IN_XTERM_CAT)) >= 0) {
5343  cats.removeAt(i);
5344  event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::LOG_PROPERTY, KAEventPrivate::xtermURL);
5345  }
5346  }
5347 
5348  if (pre_1_9_0) {
5349  /*
5350  * It's a KAlarm pre-1.9 calendar file.
5351  * Add the X-KDE-KALARM-STATUS custom property.
5352  * Convert KAlarm categories to custom fields.
5353  */
5354  CalEvent::setStatus(event, CalEvent::status(event));
5355  for (int i = 0; i < cats.count();) {
5356  const QString cat = cats.at(i);
5357  if (cat == DATE_ONLY_CATEGORY) {
5358  flags += KAEventPrivate::DATE_ONLY_FLAG;
5359  } else if (cat == CONFIRM_ACK_CATEGORY) {
5360  flags += KAEventPrivate::CONFIRM_ACK_FLAG;
5361  } else if (cat == EMAIL_BCC_CATEGORY) {
5362  flags += KAEventPrivate::EMAIL_BCC_FLAG;
5363  } else if (cat == KORGANIZER_CATEGORY) {
5364  flags += KAEventPrivate::KORGANIZER_FLAG;
5365  } else if (cat.startsWith(DEFER_CATEGORY)) {
5366  (flags += KAEventPrivate::DEFER_FLAG) += cat.mid(DEFER_CATEGORY.length());
5367  } else if (cat.startsWith(TEMPL_AFTER_TIME_CATEGORY)) {
5368  (flags += KAEventPrivate::TEMPL_AFTER_TIME_FLAG) += cat.mid(TEMPL_AFTER_TIME_CATEGORY.length());
5369  } else if (cat.startsWith(LATE_CANCEL_CATEGORY)) {
5370  (flags += KAEventPrivate::LATE_CANCEL_FLAG) += cat.mid(LATE_CANCEL_CATEGORY.length());
5371  } else if (cat.startsWith(AUTO_CLOSE_CATEGORY)) {
5372  (flags += KAEventPrivate::AUTO_CLOSE_FLAG) += cat.mid(AUTO_CLOSE_CATEGORY.length());
5373  } else if (cat.startsWith(KMAIL_SERNUM_CATEGORY)) {
5374  (flags += KAEventPrivate::KMAIL_ITEM_FLAG) += cat.mid(KMAIL_SERNUM_CATEGORY.length());
5375  } else if (cat == ARCHIVE_CATEGORY) {
5376  event->setCustomProperty(