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