File indexing completed on 2024-11-24 04:42:23
0001 /* 0002 * kaevent.cpp - represents KAlarm calendar events 0003 * This file is part of kalarmprivate library, which provides access to KAlarm 0004 * calendar data. 0005 * Program: kalarm 0006 * SPDX-FileCopyrightText: 2001-2023 David Jarvie <djarvie@kde.org> 0007 * 0008 * SPDX-License-Identifier: LGPL-2.0-or-later 0009 */ 0010 0011 #include "kaevent.h" 0012 0013 #include "alarmtext.h" 0014 #include "holidays.h" 0015 #include "identities.h" 0016 #include "version.h" 0017 #include "kalarmcal_debug.h" 0018 0019 #include <KLocalizedString> 0020 0021 using namespace KCalendarCore; 0022 0023 namespace KAlarmCal 0024 { 0025 0026 //============================================================================= 0027 0028 using EmailAddress = KCalendarCore::Person; 0029 class EmailAddressList : public KCalendarCore::Person::List 0030 { 0031 public: 0032 EmailAddressList() : KCalendarCore::Person::List() {} 0033 explicit EmailAddressList(const KCalendarCore::Person::List& list) 0034 { 0035 operator=(list); 0036 } 0037 EmailAddressList& operator=(const KCalendarCore::Person::List&); 0038 operator QStringList() const; 0039 QString join(const QString& separator) const; 0040 QStringList pureAddresses() const; 0041 QString pureAddresses(const QString& separator) const; 0042 private: 0043 QString address(int index) const; 0044 }; 0045 0046 //============================================================================= 0047 0048 class Q_DECL_HIDDEN KAAlarm::Private 0049 { 0050 public: 0051 Private(); 0052 0053 Action mActionType; // alarm action type 0054 Type mType{Type::Invalid}; // alarm type 0055 DateTime mNextMainDateTime; // next time to display the alarm, excluding repetitions 0056 Repetition mRepetition; // sub-repetition count and interval 0057 int mNextRepeat{0}; // repetition count of next due sub-repetition 0058 bool mRepeatAtLogin{false}; // whether to repeat the alarm at every login 0059 bool mRecurs; // there is a recurrence rule for the alarm 0060 bool mDeferred{false}; // whether the alarm is an extra deferred/deferred-reminder alarm 0061 bool mTimedDeferral; // if mDeferred = true: true if the deferral is timed, false if date-only 0062 }; 0063 0064 //============================================================================= 0065 0066 class Q_DECL_HIDDEN KAEventPrivate : public QSharedData 0067 { 0068 public: 0069 // Read-only internal flags additional to KAEvent::Flags enum values. 0070 // NOTE: If any values are added to those in KAEvent::Flags, ensure 0071 // that these values don't overlap them. 0072 enum 0073 { 0074 REMINDER = 0x200000, 0075 DEFERRAL = 0x400000, 0076 TIMED_FLAG = 0x800000, 0077 DATE_DEFERRAL = DEFERRAL, 0078 TIME_DEFERRAL = DEFERRAL | TIMED_FLAG, 0079 DISPLAYING_ = 0x1000000, 0080 READ_ONLY_FLAGS = 0x1E00000 //!< mask for all read-only internal values 0081 }; 0082 enum class ReminderType // current active state of reminder 0083 { 0084 None, // reminder is not due 0085 Active, // reminder is due 0086 Hidden // reminder-after is disabled due to main alarm being deferred past it 0087 }; 0088 enum class DeferType 0089 { 0090 None = 0, // there is no deferred alarm 0091 Normal, // the main alarm, a recurrence or a repeat is deferred 0092 Reminder // a reminder alarm is deferred 0093 }; 0094 // Alarm types. 0095 // This uses the same scheme as KAAlarm::Type, with some extra values. 0096 // Note that the actual enum values need not be the same as in KAAlarm::Type. 0097 enum AlarmType 0098 { 0099 INVALID_ALARM = 0, // Not an alarm 0100 MAIN_ALARM = 1, // THE real alarm. Must be the first in the enumeration. 0101 REMINDER_ALARM = 0x02, // Reminder in advance of/after the main alarm 0102 DEFERRED_ALARM = 0x04, // Deferred alarm 0103 DEFERRED_REMINDER_ALARM = REMINDER_ALARM | DEFERRED_ALARM, // Deferred reminder alarm 0104 // The following values must be greater than the preceding ones, to 0105 // ensure that in ordered processing they are processed afterwards. 0106 AT_LOGIN_ALARM = 0x10, // Additional repeat-at-login trigger 0107 DISPLAYING_ALARM = 0x20, // Copy of the alarm currently being displayed 0108 // The following are extra internal KAEvent values 0109 AUDIO_ALARM = 0x30, // sound to play when displaying the alarm 0110 PRE_ACTION_ALARM = 0x40, // command to execute before displaying the alarm 0111 POST_ACTION_ALARM = 0x50 // command to execute after the alarm window is closed 0112 }; 0113 0114 struct AlarmData 0115 { 0116 Alarm::Ptr alarm; 0117 QString cleanText; // text or audio file name 0118 QFont font; 0119 QColor bgColour, fgColour; 0120 float soundVolume; 0121 float fadeVolume; 0122 int fadeSeconds; 0123 int repeatSoundPause; 0124 int nextRepeat; 0125 uint emailFromId; 0126 KAEventPrivate::AlarmType type; 0127 KAAlarm::Action action; 0128 int displayingFlags; 0129 KAEvent::ExtraActionOptions extraActionOptions; 0130 bool speak; 0131 bool defaultFont; 0132 bool isEmailText; 0133 bool commandScript; 0134 bool timedDeferral; 0135 bool hiddenReminder; 0136 }; 0137 using AlarmMap = QMap<AlarmType, AlarmData>; 0138 0139 KAEventPrivate(); 0140 KAEventPrivate(const KADateTime&, const QString& name, const QString& message, 0141 const QColor& bg, const QColor& fg, const QFont& f, 0142 KAEvent::SubAction, int lateCancel, KAEvent::Flags flags, 0143 bool changesPending = false); 0144 explicit KAEventPrivate(const KCalendarCore::Event::Ptr&); 0145 KAEventPrivate(const KAEventPrivate&); 0146 ~KAEventPrivate() 0147 { 0148 delete mRecurrence; 0149 } 0150 KAEventPrivate& operator=(const KAEventPrivate& e) 0151 { 0152 if (&e != this) 0153 copy(e); 0154 return *this; 0155 } 0156 void setAudioFile(const QString& filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile); 0157 KAEvent::OccurType setNextOccurrence(const KADateTime& preDateTime); 0158 void setFirstRecurrence(); 0159 void setCategory(CalEvent::Type); 0160 void setRepeatAtLogin(bool); 0161 void setRepeatAtLoginTrue(bool clearReminder); 0162 void setReminder(int minutes, bool onceOnly); 0163 void activateReminderAfter(const DateTime& mainAlarmTime); 0164 void defer(const DateTime&, bool reminder, bool adjustRecurrence = false); 0165 void cancelDefer(); 0166 bool setDisplaying(const KAEventPrivate&, KAAlarm::Type, ResourceId, const KADateTime& dt, bool showEdit, bool showDefer); 0167 void reinstateFromDisplaying(const KCalendarCore::Event::Ptr&, ResourceId&, bool& showEdit, bool& showDefer); 0168 void startChanges() 0169 { 0170 ++mChangeCount; 0171 } 0172 void endChanges(); 0173 void removeExpiredAlarm(KAAlarm::Type); 0174 bool compare(const KAEventPrivate&, KAEvent::Comparison) const; 0175 KAAlarm alarm(KAAlarm::Type) const; 0176 KAAlarm firstAlarm() const; 0177 KAAlarm nextAlarm(KAAlarm::Type) const; 0178 bool updateKCalEvent(const KCalendarCore::Event::Ptr&, KAEvent::UidAction, bool setCustomProperties = true) const; 0179 DateTime mainDateTime(bool withRepeats = false) const 0180 { 0181 return (withRepeats && mNextRepeat && mRepetition) 0182 ? DateTime(mRepetition.duration(mNextRepeat).end(mNextMainDateTime.qDateTime())) : mNextMainDateTime; 0183 } 0184 DateTime mainEndRepeatTime() const 0185 { 0186 return mRepetition ? DateTime(mRepetition.duration().end(mNextMainDateTime.qDateTime())) : mNextMainDateTime; 0187 } 0188 DateTime deferralLimit(KAEvent::DeferLimit* = nullptr) const; 0189 KAEvent::Flags flags() const; 0190 bool excludedByWorkTimeOrHoliday(const KADateTime& dt) const; 0191 bool setRepetition(const Repetition&); 0192 DateTime nextDateTime(KAEvent::NextTypes) const; 0193 bool occursAfter(const KADateTime& preDateTime, bool includeRepetitions) const; 0194 KAEvent::OccurType nextOccurrence(const KADateTime& preDateTime, DateTime& result, KAEvent::Repeats = KAEvent::Repeats::Ignore) const; 0195 KAEvent::OccurType previousOccurrence(const KADateTime& afterDateTime, DateTime& result, bool includeRepetitions = false) const; 0196 void setRecurrence(const KARecurrence&); 0197 bool setRecur(KCalendarCore::RecurrenceRule::PeriodType, int freq, int count, QDate end, KARecurrence::Feb29Type = KARecurrence::Feb29_None); 0198 bool setRecur(KCalendarCore::RecurrenceRule::PeriodType, int freq, int count, const KADateTime& end, KARecurrence::Feb29Type = KARecurrence::Feb29_None); 0199 KARecurrence::Type checkRecur() const; 0200 void clearRecur(); 0201 void calcTriggerTimes() const; 0202 #ifdef KDE_NO_DEBUG_OUTPUT 0203 void dumpDebug() const { } 0204 #else 0205 void dumpDebug() const; 0206 #endif 0207 static bool convertRepetition(const KCalendarCore::Event::Ptr&); 0208 static bool convertStartOfDay(const KCalendarCore::Event::Ptr&); 0209 static DateTime readDateTime(const KCalendarCore::Event::Ptr&, bool localZone, bool dateOnly, DateTime& start); 0210 static void readAlarms(const KCalendarCore::Event::Ptr&, AlarmMap*, bool cmdDisplay = false); 0211 static void readAlarm(const KCalendarCore::Alarm::Ptr&, AlarmData&, bool audioMain, bool cmdDisplay = false); 0212 0213 private: 0214 void copy(const KAEventPrivate&); 0215 bool mayOccurDailyDuringWork(const KADateTime&) const; 0216 int nextWorkRepetition(const KADateTime& pre) const; 0217 void calcNextWorkingTime(const DateTime& nextTrigger) const; 0218 DateTime nextWorkingTime() const; 0219 KAEvent::OccurType nextRecurrence(const KADateTime& preDateTime, DateTime& result) const; 0220 void setAudioAlarm(const KCalendarCore::Alarm::Ptr&) const; 0221 KCalendarCore::Alarm::Ptr initKCalAlarm(const KCalendarCore::Event::Ptr&, const DateTime&, const QStringList& types, AlarmType = INVALID_ALARM) const; 0222 KCalendarCore::Alarm::Ptr initKCalAlarm(const KCalendarCore::Event::Ptr&, int startOffsetSecs, const QStringList& types, AlarmType = INVALID_ALARM) const; 0223 inline void set_deferral(DeferType); 0224 inline void activate_reminder(bool activate); 0225 static int transitionIndex(const QDateTime& utc, const QTimeZone::OffsetDataList& transitions); 0226 0227 public: 0228 static QFont mDefaultFont; // default alarm message font 0229 static const Holidays mDummyHolidays; // empty holiday data to avoid initial mHolidays null pointer 0230 static const Holidays* mHolidays; // holiday data to use 0231 static QBitArray mWorkDays; // working days of the week 0232 static QTime mWorkDayStart; // start time of the working day 0233 static QTime mWorkDayEnd; // end time of the working day 0234 static KADateTime::Spec mWorkDayTimeSpec; // time spec for start and end of working day 0235 static int mWorkTimeIndex; // incremented every time working days/times are changed 0236 mutable DateTime mAllTrigger; // next trigger time, including reminders, ignoring working hours 0237 mutable DateTime mMainTrigger; // next trigger time, ignoring reminders and working hours 0238 mutable DateTime mAllWorkTrigger; // next trigger time, taking account of reminders and working hours 0239 mutable DateTime mMainWorkTrigger; // next trigger time, ignoring reminders but taking account of working hours 0240 mutable KAEvent::CmdErr mCommandError{KAEvent::CmdErr::None}; // command execution error last time the alarm triggered 0241 0242 QString mEventID; // UID: KCalendarCore::Event unique ID 0243 QMap<QByteArray, QString> mCustomProperties; // KCalendarCore::Event's non-KAlarm custom properties 0244 mutable ResourceId mResourceId{-1}; // ID of resource containing the event, or for a displaying event, 0245 // saved resource ID (not the resource the event is in) 0246 QString mName; // name of the alarm 0247 QString mText; // message text, file URL, command, email body [or audio file for KAAlarm] 0248 QString mAudioFile; // ATTACH: audio file to play 0249 QString mPreAction; // command to execute before alarm is displayed 0250 QString mPostAction; // command to execute after alarm window is closed 0251 DateTime mStartDateTime; // DTSTART and DTEND: start and end time for event 0252 KADateTime mCreatedDateTime; // CREATED: date event was created, or saved in archive calendar 0253 DateTime mNextMainDateTime; // next time to display the alarm, excluding repetitions 0254 KADateTime mAtLoginDateTime; // repeat-at-login end time 0255 DateTime mDeferralTime; // extra time to trigger alarm (if alarm or reminder deferred) 0256 DateTime mDisplayingTime; // date/time shown in the alarm currently being displayed 0257 int mDisplayingFlags; // type of alarm which is currently being displayed (for display alarm) 0258 int mReminderMinutes{0};// how long in advance reminder is to be, or 0 if none (<0 for reminder AFTER the alarm) 0259 DateTime mReminderAfterTime; // if mReminderActive true, time to trigger reminder AFTER the main alarm, or invalid if not pending 0260 ReminderType mReminderActive{ReminderType::None}; // whether a reminder is due (before next, or after last, main alarm/recurrence) 0261 int mDeferDefaultMinutes{0}; // default number of minutes for deferral dialog, or 0 to select time control 0262 bool mDeferDefaultDateOnly{false}; // select date-only by default in deferral dialog 0263 int mRevision{0}; // SEQUENCE: revision number of the original alarm, or 0 0264 KARecurrence* mRecurrence{nullptr}; // RECUR: recurrence specification, or 0 if none 0265 Repetition mRepetition; // sub-repetition count and interval 0266 int mNextRepeat{0}; // repetition count of next due sub-repetition 0267 int mAlarmCount{0}; // number of alarms: count of !mMainExpired, mRepeatAtLogin, mDeferral, mReminderActive, mDisplaying 0268 DeferType mDeferral{DeferType::None}; // whether the alarm is an extra deferred/deferred-reminder alarm 0269 KAEvent::EmailId mEmailId{-1}; // if email text, message's Akonadi item ID 0270 int mTemplateAfterTime{-1}; // time not specified: use n minutes after default time, or -1 (applies to templates only) 0271 QColor mBgColour; // background colour of alarm message 0272 QColor mFgColour; // foreground colour of alarm message, or invalid for default 0273 QFont mFont; // font of alarm message (ignored if mUseDefaultFont true) 0274 uint mEmailFromIdentity{0}; // standard email identity uoid for 'From' field, or empty 0275 EmailAddressList mEmailAddresses; // ATTENDEE: addresses to send email to 0276 QString mEmailSubject; // SUMMARY: subject line of email 0277 QStringList mEmailAttachments; // ATTACH: email attachment file names 0278 mutable QTime mTriggerStartOfDay; // start of day time used by calcTriggerTimes() 0279 mutable int mChangeCount{0}; // >0 = inhibit calling calcTriggerTimes() 0280 mutable bool mTriggerChanged{false}; // true if need to recalculate trigger times 0281 QString mLogFile; // alarm output is to be logged to this URL 0282 float mSoundVolume{-1.0f}; // volume for sound file (range 0 - 1), or < 0 for unspecified 0283 float mFadeVolume{-1.0f}; // initial volume for sound file (range 0 - 1), or < 0 for no fade 0284 int mFadeSeconds{0}; // fade time (seconds) for sound file, or 0 if none 0285 int mRepeatSoundPause{-1}; // seconds to pause between sound file repetitions, or -1 if no repetition 0286 int mLateCancel{0}; // how many minutes late will cancel the alarm, or 0 for no cancellation 0287 bool mWakeFromSuspend{false}; // wake system from suspend when alarm is due 0288 bool mExcludeHolidays{false}; // don't trigger alarms on holidays 0289 mutable QString mExcludeHolidayRegion; // holiday region code used to exclude alarms on holidays (= mHolidays region when trigger calculated) 0290 mutable int mWorkTimeOnly{0}; // non-zero to trigger alarm only during working hours (= mWorkTimeIndex when trigger calculated) 0291 KAEvent::SubAction mActionSubType; // sub-action type for the event's main alarm 0292 CalEvent::Type mCategory{CalEvent::EMPTY}; // event category (active, archived, template, ...) 0293 KAEvent::ExtraActionOptions mExtraActionOptions; // options for pre- or post-alarm actions 0294 KACalendar::Compat mCompatibility{KACalendar::Current}; // event's storage format compatibility 0295 bool mReadOnly{false}; // event is read-only in its original calendar file 0296 bool mConfirmAck{false}; // alarm acknowledgement requires confirmation by user 0297 bool mUseDefaultFont; // use default message font, not mFont 0298 bool mCommandScript{false}; // the command text is a script, not a shell command line 0299 bool mCommandXterm{false}; // command alarm is to be executed in a terminal window 0300 bool mCommandDisplay{false}; // command output is to be displayed in an alarm window 0301 bool mCommandHideError{false}; // don't show command execution errors to user 0302 bool mEmailBcc{false}; // blind copy the email to the user 0303 bool mBeep{false}; // whether to beep when the alarm is displayed 0304 bool mSpeak{false}; // whether to speak the message when the alarm is displayed 0305 bool mCopyToKOrganizer{false}; // KOrganizer should hold a copy of the event 0306 bool mReminderOnceOnly{false}; // the reminder is output only for the first recurrence 0307 bool mAutoClose{false}; // whether to close the alarm window after the late-cancel period 0308 bool mNotify{false}; // alarm should be shown by the notification system, not in a window 0309 bool mMainExpired; // main alarm has expired (in which case a deferral alarm will exist) 0310 bool mRepeatAtLogin{false}; // whether to repeat the alarm at every login 0311 bool mArchiveRepeatAtLogin{false}; // if now archived, original event was repeat-at-login 0312 bool mArchive{false}; // event has triggered in the past, so archive it when closed 0313 bool mDisplaying{false}; // whether the alarm is currently being displayed (i.e. in displaying calendar) 0314 bool mDisplayingDefer{false}; // show Defer button (applies to displaying calendar only) 0315 bool mDisplayingEdit{false}; // show Edit button (applies to displaying calendar only) 0316 bool mEnabled; // false if event is disabled 0317 0318 public: 0319 static const QByteArray FLAGS_PROPERTY; 0320 static const QString DATE_ONLY_FLAG; 0321 static const QString LOCAL_ZONE_FLAG; 0322 static const QString EMAIL_BCC_FLAG; 0323 static const QString CONFIRM_ACK_FLAG; 0324 static const QString KORGANIZER_FLAG; 0325 static const QString WAKE_SUSPEND_FLAG; 0326 static const QString EXCLUDE_HOLIDAYS_FLAG; 0327 static const QString WORK_TIME_ONLY_FLAG; 0328 static const QString REMINDER_ONCE_FLAG; 0329 static const QString DEFER_FLAG; 0330 static const QString LATE_CANCEL_FLAG; 0331 static const QString AUTO_CLOSE_FLAG; 0332 static const QString NOTIFY_FLAG; 0333 static const QString TEMPL_AFTER_TIME_FLAG; 0334 static const QString KMAIL_ITEM_FLAG; 0335 static const QString ARCHIVE_FLAG; 0336 static const QByteArray NEXT_RECUR_PROPERTY; 0337 static const QByteArray REPEAT_PROPERTY; 0338 static const QByteArray LOG_PROPERTY; 0339 static const QString xtermURL; 0340 static const QString displayURL; 0341 static const QByteArray TYPE_PROPERTY; 0342 static const QString FILE_TYPE; 0343 static const QString AT_LOGIN_TYPE; 0344 static const QString REMINDER_TYPE; 0345 static const QString REMINDER_ONCE_TYPE; 0346 static const QString TIME_DEFERRAL_TYPE; 0347 static const QString DATE_DEFERRAL_TYPE; 0348 static const QString DISPLAYING_TYPE; 0349 static const QString PRE_ACTION_TYPE; 0350 static const QString POST_ACTION_TYPE; 0351 static const QString SOUND_REPEAT_TYPE; 0352 static const QByteArray NEXT_REPEAT_PROPERTY; 0353 static const QString HIDDEN_REMINDER_FLAG; 0354 static const QByteArray FONT_COLOUR_PROPERTY; 0355 static const QByteArray VOLUME_PROPERTY; 0356 static const QString EMAIL_ID_FLAG; 0357 static const QString SPEAK_FLAG; 0358 static const QString EXEC_ON_DEFERRAL_FLAG; 0359 static const QString CANCEL_ON_ERROR_FLAG; 0360 static const QString DONT_SHOW_ERROR_FLAG; 0361 static const QString DISABLED_STATUS; 0362 static const QString DISP_DEFER; 0363 static const QString DISP_EDIT; 0364 static const QString CMD_ERROR_VALUE; 0365 static const QString CMD_ERROR_PRE_VALUE; 0366 static const QString CMD_ERROR_POST_VALUE; 0367 static const QString SC; 0368 }; 0369 0370 //============================================================================= 0371 0372 // KAlarm version which first used the current calendar/event format. 0373 // If this changes, KAEvent::convertKCalEvents() must be changed correspondingly. 0374 // The string version is the KAlarm version string used in the calendar file. 0375 0376 QByteArray KAEvent::currentCalendarVersionString() 0377 { 0378 return {"2.7.0"}; // This is NOT the KAlarmCal library .so version! 0379 } 0380 int KAEvent::currentCalendarVersion() 0381 { 0382 return Version(2, 7, 0); // This is NOT the KAlarmCal library .so version! 0383 } 0384 0385 // Custom calendar properties. 0386 // Note that all custom property names are prefixed with X-KDE-KALARM- in the calendar file. 0387 0388 // Event properties 0389 const QByteArray KAEventPrivate::FLAGS_PROPERTY("FLAGS"); // X-KDE-KALARM-FLAGS property 0390 const QString KAEventPrivate::DATE_ONLY_FLAG = QStringLiteral("DATE"); 0391 const QString KAEventPrivate::LOCAL_ZONE_FLAG = QStringLiteral("LOCAL"); 0392 const QString KAEventPrivate::EMAIL_BCC_FLAG = QStringLiteral("BCC"); 0393 const QString KAEventPrivate::CONFIRM_ACK_FLAG = QStringLiteral("ACKCONF"); 0394 const QString KAEventPrivate::KORGANIZER_FLAG = QStringLiteral("KORG"); 0395 const QString KAEventPrivate::WAKE_SUSPEND_FLAG = QStringLiteral("WAKESUSPEND"); 0396 const QString KAEventPrivate::EXCLUDE_HOLIDAYS_FLAG = QStringLiteral("EXHOLIDAYS"); 0397 const QString KAEventPrivate::WORK_TIME_ONLY_FLAG = QStringLiteral("WORKTIME"); 0398 const QString KAEventPrivate::REMINDER_ONCE_FLAG = QStringLiteral("ONCE"); 0399 const QString KAEventPrivate::DEFER_FLAG = QStringLiteral("DEFER"); // default defer interval for this alarm 0400 const QString KAEventPrivate::LATE_CANCEL_FLAG = QStringLiteral("LATECANCEL"); 0401 const QString KAEventPrivate::AUTO_CLOSE_FLAG = QStringLiteral("LATECLOSE"); 0402 const QString KAEventPrivate::NOTIFY_FLAG = QStringLiteral("NOTIFY"); 0403 const QString KAEventPrivate::TEMPL_AFTER_TIME_FLAG = QStringLiteral("TMPLAFTTIME"); 0404 const QString KAEventPrivate::KMAIL_ITEM_FLAG = QStringLiteral("KMAIL"); 0405 const QString KAEventPrivate::ARCHIVE_FLAG = QStringLiteral("ARCHIVE"); 0406 0407 const QByteArray KAEventPrivate::NEXT_RECUR_PROPERTY("NEXTRECUR"); // X-KDE-KALARM-NEXTRECUR property 0408 const QByteArray KAEventPrivate::REPEAT_PROPERTY("REPEAT"); // X-KDE-KALARM-REPEAT property 0409 const QByteArray KAEventPrivate::LOG_PROPERTY("LOG"); // X-KDE-KALARM-LOG property 0410 const QString KAEventPrivate::xtermURL = QStringLiteral("xterm:"); 0411 const QString KAEventPrivate::displayURL = QStringLiteral("display:"); 0412 0413 // - General alarm properties 0414 const QByteArray KAEventPrivate::TYPE_PROPERTY("TYPE"); // X-KDE-KALARM-TYPE property 0415 const QString KAEventPrivate::FILE_TYPE = QStringLiteral("FILE"); 0416 const QString KAEventPrivate::AT_LOGIN_TYPE = QStringLiteral("LOGIN"); 0417 const QString KAEventPrivate::REMINDER_TYPE = QStringLiteral("REMINDER"); 0418 const QString KAEventPrivate::TIME_DEFERRAL_TYPE = QStringLiteral("DEFERRAL"); 0419 const QString KAEventPrivate::DATE_DEFERRAL_TYPE = QStringLiteral("DATE_DEFERRAL"); 0420 const QString KAEventPrivate::DISPLAYING_TYPE = QStringLiteral("DISPLAYING"); // used only in displaying calendar 0421 const QString KAEventPrivate::PRE_ACTION_TYPE = QStringLiteral("PRE"); 0422 const QString KAEventPrivate::POST_ACTION_TYPE = QStringLiteral("POST"); 0423 const QString KAEventPrivate::SOUND_REPEAT_TYPE = QStringLiteral("SOUNDREPEAT"); 0424 const QByteArray KAEventPrivate::NEXT_REPEAT_PROPERTY("NEXTREPEAT"); // X-KDE-KALARM-NEXTREPEAT property 0425 const QString KAEventPrivate::HIDDEN_REMINDER_FLAG = QStringLiteral("HIDE"); 0426 // - Display alarm properties 0427 const QByteArray KAEventPrivate::FONT_COLOUR_PROPERTY("FONTCOLOR"); // X-KDE-KALARM-FONTCOLOR property 0428 // - Email alarm properties 0429 const QString KAEventPrivate::EMAIL_ID_FLAG = QStringLiteral("EMAILID"); 0430 // - Audio alarm properties 0431 const QByteArray KAEventPrivate::VOLUME_PROPERTY("VOLUME"); // X-KDE-KALARM-VOLUME property 0432 const QString KAEventPrivate::SPEAK_FLAG = QStringLiteral("SPEAK"); 0433 // - Command alarm properties 0434 const QString KAEventPrivate::EXEC_ON_DEFERRAL_FLAG = QStringLiteral("EXECDEFER"); 0435 const QString KAEventPrivate::CANCEL_ON_ERROR_FLAG = QStringLiteral("ERRCANCEL"); 0436 const QString KAEventPrivate::DONT_SHOW_ERROR_FLAG = QStringLiteral("ERRNOSHOW"); 0437 0438 // Event status strings 0439 const QString KAEventPrivate::DISABLED_STATUS = QStringLiteral("DISABLED"); 0440 0441 // Displaying event ID identifier 0442 const QString KAEventPrivate::DISP_DEFER = QStringLiteral("DEFER"); 0443 const QString KAEventPrivate::DISP_EDIT = QStringLiteral("EDIT"); 0444 0445 // Command error strings 0446 const QString KAEventPrivate::CMD_ERROR_VALUE = QStringLiteral("MAIN"); 0447 const QString KAEventPrivate::CMD_ERROR_PRE_VALUE = QStringLiteral("PRE"); 0448 const QString KAEventPrivate::CMD_ERROR_POST_VALUE = QStringLiteral("POST"); 0449 0450 const QString KAEventPrivate::SC = QStringLiteral(";"); 0451 0452 QFont KAEventPrivate::mDefaultFont; 0453 const Holidays KAEventPrivate::mDummyHolidays; 0454 const Holidays* KAEventPrivate::mHolidays{&mDummyHolidays}; 0455 QBitArray KAEventPrivate::mWorkDays(7); 0456 QTime KAEventPrivate::mWorkDayStart(9, 0, 0); 0457 QTime KAEventPrivate::mWorkDayEnd(17, 0, 0); 0458 KADateTime::Spec KAEventPrivate::mWorkDayTimeSpec(KADateTime::LocalZone); 0459 int KAEventPrivate::mWorkTimeIndex = 1; 0460 0461 static void setProcedureAlarm(const Alarm::Ptr&, const QString& commandLine); 0462 static QString reminderToString(int minutes); 0463 0464 /*============================================================================= 0465 = Class KAEvent 0466 = Corresponds to a KCalendarCore::Event instance. 0467 =============================================================================*/ 0468 0469 inline void KAEventPrivate::activate_reminder(bool activate) 0470 { 0471 if (activate && mReminderActive != ReminderType::Active && mReminderMinutes) 0472 { 0473 if (mReminderActive == ReminderType::None) 0474 ++mAlarmCount; 0475 mReminderActive = ReminderType::Active; 0476 } 0477 else if (!activate && mReminderActive != ReminderType::None) 0478 { 0479 mReminderActive = ReminderType::None; 0480 mReminderAfterTime = DateTime(); 0481 --mAlarmCount; 0482 } 0483 } 0484 0485 Q_GLOBAL_STATIC_WITH_ARGS(QSharedDataPointer<KAEventPrivate>, 0486 emptyKAEventPrivate, (new KAEventPrivate)) 0487 0488 KAEvent::KAEvent() 0489 : d(*emptyKAEventPrivate) 0490 { } 0491 0492 KAEventPrivate::KAEventPrivate() = default; 0493 0494 /****************************************************************************** 0495 * Initialise the instance with the specified parameters. 0496 */ 0497 KAEvent::KAEvent(const KADateTime& dt, const QString& name, const QString& message, 0498 const QColor& bg, const QColor& fg, const QFont& f, 0499 SubAction action, int lateCancel, Flags flags, bool changesPending) 0500 : d(new KAEventPrivate(dt, name, message, bg, fg, f, action, lateCancel, flags, changesPending)) 0501 { 0502 } 0503 0504 KAEventPrivate::KAEventPrivate(const KADateTime& dateTime, const QString& name, const QString& text, 0505 const QColor& bg, const QColor& fg, const QFont& font, 0506 KAEvent::SubAction action, int lateCancel, KAEvent::Flags flags, 0507 bool changesPending) 0508 : mName(name) 0509 , mStartDateTime(dateTime) 0510 , mAlarmCount(1) 0511 , mBgColour(bg) 0512 , mFgColour(fg) 0513 , mFont(font) 0514 , mLateCancel(lateCancel) // do this before setting flags 0515 , mCategory(CalEvent::ACTIVE) 0516 { 0517 if (flags & KAEvent::ANY_TIME) 0518 mStartDateTime.setDateOnly(true); 0519 mNextMainDateTime = mStartDateTime; 0520 switch (action) 0521 { 0522 case KAEvent::SubAction::Message: 0523 case KAEvent::SubAction::File: 0524 case KAEvent::SubAction::Command: 0525 case KAEvent::SubAction::Email: 0526 case KAEvent::SubAction::Audio: 0527 mActionSubType = static_cast<KAEvent::SubAction>(action); 0528 break; 0529 default: 0530 mActionSubType = KAEvent::SubAction::Message; 0531 break; 0532 } 0533 mText = (mActionSubType == KAEvent::SubAction::Command) ? text.trimmed() 0534 : (mActionSubType == KAEvent::SubAction::Audio) ? QString() : text; 0535 mAudioFile = (mActionSubType == KAEvent::SubAction::Audio) ? text : QString(); 0536 set_deferral((flags & DEFERRAL) ? DeferType::Normal : DeferType::None); 0537 mRepeatAtLogin = flags & KAEvent::REPEAT_AT_LOGIN; 0538 mConfirmAck = flags & KAEvent::CONFIRM_ACK; 0539 mUseDefaultFont = flags & KAEvent::DEFAULT_FONT; 0540 mCommandScript = flags & KAEvent::SCRIPT; 0541 mCommandXterm = flags & KAEvent::EXEC_IN_XTERM; 0542 mCommandDisplay = flags & KAEvent::DISPLAY_COMMAND; 0543 mCommandHideError = flags & KAEvent::DONT_SHOW_ERROR; 0544 mCopyToKOrganizer = flags & KAEvent::COPY_KORGANIZER; 0545 mWakeFromSuspend = flags & KAEvent::WAKE_SUSPEND; 0546 mExcludeHolidays = flags & KAEvent::EXCL_HOLIDAYS; 0547 mExcludeHolidayRegion = mHolidays->regionCode(); 0548 mWorkTimeOnly = flags & KAEvent::WORK_TIME_ONLY; 0549 mEmailBcc = flags & KAEvent::EMAIL_BCC; 0550 mEnabled = !(flags & KAEvent::DISABLED); 0551 mDisplaying = flags & DISPLAYING_; 0552 mReminderOnceOnly = flags & KAEvent::REMINDER_ONCE; 0553 mAutoClose = (flags & KAEvent::AUTO_CLOSE) && mLateCancel; 0554 mNotify = flags & KAEvent::NOTIFY; 0555 mRepeatSoundPause = (flags & KAEvent::REPEAT_SOUND) ? 0 : -1; 0556 mSpeak = (flags & KAEvent::SPEAK) && action != KAEvent::SubAction::Audio; 0557 mBeep = (flags & KAEvent::BEEP) && action != KAEvent::SubAction::Audio && !mSpeak; 0558 if (mRepeatAtLogin) 0559 { // do this after setting other flags 0560 ++mAlarmCount; 0561 setRepeatAtLoginTrue(false); 0562 } 0563 0564 mMainExpired = false; 0565 mChangeCount = changesPending ? 1 : 0; 0566 mTriggerChanged = true; 0567 } 0568 0569 /****************************************************************************** 0570 * Initialise the KAEvent from a KCalendarCore::Event. 0571 */ 0572 KAEvent::KAEvent(const KCalendarCore::Event::Ptr& event) 0573 : d(new KAEventPrivate(event)) 0574 { 0575 } 0576 0577 KAEventPrivate::KAEventPrivate(const KCalendarCore::Event::Ptr& event) 0578 { 0579 startChanges(); 0580 // Extract status from the event 0581 mEventID = event->uid(); 0582 mRevision = event->revision(); 0583 mName = event->summary(); 0584 mBgColour = QColor(255, 255, 255); // missing/invalid colour - return white background 0585 mFgColour = QColor(0, 0, 0); // and black foreground 0586 mReadOnly = event->isReadOnly(); 0587 mUseDefaultFont = true; 0588 mEnabled = true; 0589 QString param; 0590 bool ok; 0591 mCategory = CalEvent::status(event, ¶m); 0592 if (mCategory == CalEvent::DISPLAYING) 0593 { 0594 // It's a displaying calendar event - set values specific to displaying alarms 0595 const QStringList params = param.split(SC, Qt::KeepEmptyParts); 0596 int n = params.count(); 0597 if (n) 0598 { 0599 const qlonglong id = params[0].toLongLong(&ok); 0600 if (ok) 0601 mResourceId = id; // original resource ID which contained the event 0602 for (int i = 1; i < n; ++i) 0603 { 0604 if (params[i] == DISP_DEFER) 0605 mDisplayingDefer = true; 0606 if (params[i] == DISP_EDIT) 0607 mDisplayingEdit = true; 0608 } 0609 } 0610 } 0611 // Store the non-KAlarm custom properties of the event 0612 const QByteArray kalarmKey = "X-KDE-" + KACalendar::APPNAME + '-'; 0613 mCustomProperties = event->customProperties(); 0614 for (QMap<QByteArray, QString>::Iterator it = mCustomProperties.begin(); it != mCustomProperties.end();) 0615 { 0616 if (it.key().startsWith(kalarmKey)) 0617 it = mCustomProperties.erase(it); 0618 else 0619 ++it; 0620 } 0621 0622 bool dateOnly = false; 0623 bool localZone = false; 0624 QStringList flags = event->customProperty(KACalendar::APPNAME, FLAGS_PROPERTY).split(SC, Qt::SkipEmptyParts); 0625 flags << QString() << QString(); // to avoid having to check for end of list 0626 for (int i = 0, end = flags.count() - 1; i < end; ++i) 0627 { 0628 QString flag = flags.at(i); 0629 if (flag == DATE_ONLY_FLAG) 0630 dateOnly = true; 0631 else if (flag == LOCAL_ZONE_FLAG) 0632 localZone = true; 0633 else if (flag == CONFIRM_ACK_FLAG) 0634 mConfirmAck = true; 0635 else if (flag == EMAIL_BCC_FLAG) 0636 mEmailBcc = true; 0637 else if (flag == KORGANIZER_FLAG) 0638 mCopyToKOrganizer = true; 0639 else if (flag == WAKE_SUSPEND_FLAG) 0640 mWakeFromSuspend = true; 0641 else if (flag == EXCLUDE_HOLIDAYS_FLAG) 0642 { 0643 mExcludeHolidays = true; 0644 mExcludeHolidayRegion = mHolidays->regionCode(); 0645 } 0646 else if (flag == WORK_TIME_ONLY_FLAG) 0647 mWorkTimeOnly = 1; 0648 else if (flag == NOTIFY_FLAG) 0649 mNotify = true; 0650 else if (flag == KMAIL_ITEM_FLAG) 0651 { 0652 const KAEvent::EmailId id = flags.at(i + 1).toLongLong(&ok); 0653 if (!ok) 0654 continue; 0655 mEmailId = id; 0656 ++i; 0657 } 0658 else if (flag == KAEventPrivate::ARCHIVE_FLAG) 0659 mArchive = true; 0660 else if (flag == KAEventPrivate::AT_LOGIN_TYPE) 0661 mArchiveRepeatAtLogin = true; 0662 else if (flag == KAEventPrivate::REMINDER_TYPE) 0663 { 0664 flag = flags.at(++i); 0665 if (flag == KAEventPrivate::REMINDER_ONCE_FLAG) 0666 { 0667 mReminderOnceOnly = true; 0668 flag = flags.at(++i); 0669 } 0670 const int len = flag.length() - 1; 0671 mReminderMinutes = -QStringView(flag).left(len).toInt(); // -> 0 if conversion fails 0672 switch (flag.at(len).toLatin1()) 0673 { 0674 case 'M': break; 0675 case 'H': mReminderMinutes *= 60; break; 0676 case 'D': mReminderMinutes *= 1440; break; 0677 default: mReminderMinutes = 0; break; 0678 } 0679 } 0680 else if (flag == DEFER_FLAG) 0681 { 0682 QString mins = flags.at(i + 1); 0683 if (mins.endsWith(QLatin1Char('D'))) 0684 { 0685 mDeferDefaultDateOnly = true; 0686 mins.chop(1); 0687 } 0688 const int n = static_cast<int>(mins.toUInt(&ok)); 0689 if (!ok) 0690 continue; 0691 mDeferDefaultMinutes = n; 0692 ++i; 0693 } 0694 else if (flag == TEMPL_AFTER_TIME_FLAG) 0695 { 0696 const int n = static_cast<int>(flags.at(i + 1).toUInt(&ok)); 0697 if (!ok) 0698 continue; 0699 mTemplateAfterTime = n; 0700 ++i; 0701 } 0702 else if (flag == LATE_CANCEL_FLAG) 0703 { 0704 mLateCancel = static_cast<int>(flags.at(i + 1).toUInt(&ok)); 0705 if (ok) 0706 ++i; 0707 if (!ok || !mLateCancel) 0708 mLateCancel = 1; // invalid parameter defaults to 1 minute 0709 } 0710 else if (flag == AUTO_CLOSE_FLAG) 0711 { 0712 mLateCancel = static_cast<int>(flags.at(i + 1).toUInt(&ok)); 0713 if (ok) 0714 ++i; 0715 if (!ok || !mLateCancel) 0716 mLateCancel = 1; // invalid parameter defaults to 1 minute 0717 mAutoClose = true; 0718 } 0719 } 0720 0721 QString prop = event->customProperty(KACalendar::APPNAME, LOG_PROPERTY); 0722 if (!prop.isEmpty()) 0723 { 0724 if (prop == xtermURL) 0725 mCommandXterm = true; 0726 else if (prop == displayURL) 0727 mCommandDisplay = true; 0728 else 0729 mLogFile = prop; 0730 } 0731 prop = event->customProperty(KACalendar::APPNAME, REPEAT_PROPERTY); 0732 if (!prop.isEmpty()) 0733 { 0734 // This property is used only when the main alarm has expired. 0735 // If a main alarm is found, this property is ignored (see below). 0736 const QStringList list = prop.split(QLatin1Char(':')); 0737 if (list.count() >= 2) 0738 { 0739 const int interval = static_cast<int>(list[0].toUInt()); 0740 const int count = static_cast<int>(list[1].toUInt()); 0741 if (interval && count) 0742 { 0743 if (interval % (24 * 60)) 0744 mRepetition.set(Duration(interval * 60, Duration::Seconds), count); 0745 else 0746 mRepetition.set(Duration(interval / (24 * 60), Duration::Days), count); 0747 } 0748 } 0749 } 0750 mNextMainDateTime = readDateTime(event, localZone, dateOnly, mStartDateTime); 0751 mCreatedDateTime = KADateTime(event->created()); 0752 if (dateOnly && !mRepetition.isDaily()) 0753 mRepetition.set(Duration(mRepetition.intervalDays(), Duration::Days)); 0754 if (event->customStatus() == DISABLED_STATUS) 0755 mEnabled = false; 0756 0757 // Extract status from the event's alarms. 0758 // First set up defaults. 0759 mActionSubType = KAEvent::SubAction::Message; 0760 mMainExpired = true; 0761 0762 // Extract data from all the event's alarms and index the alarms by sequence number 0763 AlarmMap alarmMap; 0764 readAlarms(event, &alarmMap, mCommandDisplay); 0765 0766 // Incorporate the alarms' details into the overall event 0767 mAlarmCount = 0; // initialise as invalid 0768 DateTime alTime; 0769 bool set = false; 0770 bool isEmailText = false; 0771 bool setDeferralTime = false; 0772 Duration deferralOffset; 0773 for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it) 0774 { 0775 const AlarmData& data = it.value(); 0776 const DateTime dateTime(data.alarm->hasStartOffset() ? data.alarm->startOffset().end(mNextMainDateTime.effectiveDateTime()) : data.alarm->time()); 0777 switch (data.type) 0778 { 0779 case MAIN_ALARM: 0780 mMainExpired = false; 0781 alTime = dateTime; 0782 alTime.setDateOnly(mStartDateTime.isDateOnly()); 0783 mRepetition.set(0, 0); // ignore X-KDE-KALARM-REPEAT if main alarm exists 0784 if (data.alarm->repeatCount() && !data.alarm->snoozeTime().isNull()) 0785 { 0786 mRepetition.set(data.alarm->snoozeTime(), data.alarm->repeatCount()); // values may be adjusted in setRecurrence() 0787 mNextRepeat = data.nextRepeat; 0788 } 0789 if (data.action != KAAlarm::Action::Audio) 0790 break; 0791 [[fallthrough]]; // Fall through to AUDIO_ALARM 0792 case AUDIO_ALARM: 0793 mAudioFile = data.cleanText; 0794 mSpeak = data.speak && mAudioFile.isEmpty(); 0795 mBeep = !mSpeak && mAudioFile.isEmpty(); 0796 mSoundVolume = (!mBeep && !mSpeak) ? data.soundVolume : -1; 0797 mFadeVolume = (mSoundVolume >= 0 && data.fadeSeconds > 0) ? data.fadeVolume : -1; 0798 mFadeSeconds = (mFadeVolume >= 0) ? data.fadeSeconds : 0; 0799 mRepeatSoundPause = (!mBeep && !mSpeak) ? data.repeatSoundPause : -1; 0800 break; 0801 case AT_LOGIN_ALARM: 0802 mRepeatAtLogin = true; 0803 mAtLoginDateTime = dateTime.kDateTime(); 0804 alTime = mAtLoginDateTime; 0805 break; 0806 case REMINDER_ALARM: 0807 --mAlarmCount; // reminder alarms only contribute to the alarm count if active 0808 // N.B. there can be a start offset but no valid date/time (e.g. in template) 0809 if (data.alarm->startOffset().asSeconds() / 60) 0810 { 0811 mReminderActive = ReminderType::Active; 0812 if (mReminderMinutes < 0) 0813 { 0814 mReminderAfterTime = dateTime; // the reminder is AFTER the main alarm 0815 mReminderAfterTime.setDateOnly(dateOnly); 0816 if (data.hiddenReminder) 0817 mReminderActive = ReminderType::Hidden; 0818 } 0819 } 0820 break; 0821 case DEFERRED_REMINDER_ALARM: 0822 case DEFERRED_ALARM: 0823 mDeferral = (data.type == DEFERRED_REMINDER_ALARM) ? DeferType::Reminder : DeferType::Normal; 0824 if (data.timedDeferral) 0825 { 0826 // Don't use start-of-day time for applying timed deferral alarm offset 0827 mDeferralTime = DateTime(data.alarm->hasStartOffset() ? data.alarm->startOffset().end(mNextMainDateTime.calendarDateTime()) : data.alarm->time()); 0828 } 0829 else 0830 { 0831 mDeferralTime = dateTime; 0832 mDeferralTime.setDateOnly(true); 0833 } 0834 if (data.alarm->hasStartOffset()) 0835 deferralOffset = data.alarm->startOffset(); 0836 break; 0837 case DISPLAYING_ALARM: 0838 { 0839 mDisplaying = true; 0840 mDisplayingFlags = data.displayingFlags; 0841 const bool dateonly = (mDisplayingFlags & DEFERRAL) ? !(mDisplayingFlags & TIMED_FLAG) 0842 : mStartDateTime.isDateOnly(); 0843 mDisplayingTime = dateTime; 0844 mDisplayingTime.setDateOnly(dateonly); 0845 alTime = mDisplayingTime; 0846 break; 0847 } 0848 case PRE_ACTION_ALARM: 0849 mPreAction = data.cleanText; 0850 mExtraActionOptions = data.extraActionOptions; 0851 break; 0852 case POST_ACTION_ALARM: 0853 mPostAction = data.cleanText; 0854 break; 0855 case INVALID_ALARM: 0856 default: 0857 break; 0858 } 0859 0860 bool noSetNextTime = false; 0861 switch (data.type) 0862 { 0863 case DEFERRED_REMINDER_ALARM: 0864 case DEFERRED_ALARM: 0865 if (!set) 0866 { 0867 // The recurrence has to be evaluated before we can 0868 // calculate the time of a deferral alarm. 0869 setDeferralTime = true; 0870 noSetNextTime = true; 0871 } 0872 [[fallthrough]]; // fall through to REMINDER_ALARM 0873 case REMINDER_ALARM: 0874 case AT_LOGIN_ALARM: 0875 case DISPLAYING_ALARM: 0876 if (!set && !noSetNextTime) 0877 mNextMainDateTime = alTime; 0878 [[fallthrough]]; // fall through to MAIN_ALARM 0879 case MAIN_ALARM: 0880 // Ensure that the basic fields are set up even if there is no main 0881 // alarm in the event (if it has expired and then been deferred) 0882 if (!set) 0883 { 0884 mActionSubType = static_cast<KAEvent::SubAction>(data.action); 0885 mText = (mActionSubType == KAEvent::SubAction::Command) ? data.cleanText.trimmed() : data.cleanText; 0886 switch (data.action) 0887 { 0888 case KAAlarm::Action::Command: 0889 mCommandScript = data.commandScript; 0890 if (data.extraActionOptions & KAEvent::DontShowPreActError) 0891 mCommandHideError = true; 0892 if (!mCommandDisplay) 0893 break; 0894 [[fallthrough]]; // fall through to MESSAGE 0895 case KAAlarm::Action::Message: 0896 mFont = data.font; 0897 mUseDefaultFont = data.defaultFont; 0898 if (data.isEmailText) 0899 isEmailText = true; 0900 [[fallthrough]]; // fall through to File 0901 case KAAlarm::Action::File: 0902 mBgColour = data.bgColour; 0903 mFgColour = data.fgColour; 0904 break; 0905 case KAAlarm::Action::Email: 0906 mEmailFromIdentity = data.emailFromId; 0907 mEmailAddresses = data.alarm->mailAddresses(); 0908 mEmailSubject = data.alarm->mailSubject(); 0909 mEmailAttachments = data.alarm->mailAttachments(); 0910 break; 0911 case KAAlarm::Action::Audio: 0912 // Already mostly handled above 0913 mRepeatSoundPause = data.repeatSoundPause; 0914 break; 0915 default: 0916 break; 0917 } 0918 set = true; 0919 } 0920 if (data.action == KAAlarm::Action::File && mActionSubType == KAEvent::SubAction::Message) 0921 mActionSubType = KAEvent::SubAction::File; 0922 ++mAlarmCount; 0923 break; 0924 case AUDIO_ALARM: 0925 case PRE_ACTION_ALARM: 0926 case POST_ACTION_ALARM: 0927 case INVALID_ALARM: 0928 default: 0929 break; 0930 } 0931 } 0932 if (!isEmailText) 0933 mEmailId = -1; 0934 0935 Recurrence* recur = event->recurrence(); 0936 if (recur && recur->recurs()) 0937 { 0938 const int nextRepeat = mNextRepeat; // setRecurrence() clears mNextRepeat 0939 setRecurrence(*recur); 0940 if (nextRepeat <= mRepetition.count()) 0941 mNextRepeat = nextRepeat; 0942 } 0943 else if (mRepetition) 0944 { 0945 // Convert a repetition with no recurrence into a recurrence 0946 if (mRepetition.isDaily()) 0947 setRecur(RecurrenceRule::rDaily, mRepetition.intervalDays(), mRepetition.count() + 1, QDate()); 0948 else 0949 setRecur(RecurrenceRule::rMinutely, mRepetition.intervalMinutes(), mRepetition.count() + 1, KADateTime()); 0950 mRepetition.set(0, 0); 0951 mTriggerChanged = true; 0952 } 0953 0954 if (mRepeatAtLogin) 0955 { 0956 mArchiveRepeatAtLogin = false; 0957 if (mReminderMinutes > 0) 0958 { 0959 mReminderMinutes = 0; // pre-alarm reminder not allowed for at-login alarm 0960 mReminderActive = ReminderType::None; 0961 } 0962 setRepeatAtLoginTrue(false); // clear other incompatible statuses 0963 } 0964 0965 // Adjust the alarm count if there is an active reminder alarm. 0966 if (mReminderActive != ReminderType::None) 0967 ++mAlarmCount; 0968 0969 if (mMainExpired && !deferralOffset.isNull() && checkRecur() != KARecurrence::NO_RECUR) 0970 { 0971 // Adjust the deferral time for an expired recurrence, since the 0972 // offset is relative to the first actual occurrence. 0973 DateTime dt = mRecurrence->getNextDateTime(mStartDateTime.addDays(-1).kDateTime()); 0974 dt.setDateOnly(mStartDateTime.isDateOnly()); 0975 if (mDeferralTime.isDateOnly()) 0976 { 0977 mDeferralTime = DateTime(deferralOffset.end(dt.qDateTime())); 0978 mDeferralTime.setDateOnly(true); 0979 } 0980 else 0981 mDeferralTime = DateTime(deferralOffset.end(dt.effectiveDateTime())); 0982 } 0983 if (mDeferral != DeferType::None) 0984 { 0985 if (setDeferralTime) 0986 mNextMainDateTime = mDeferralTime; 0987 } 0988 mTriggerChanged = true; 0989 endChanges(); 0990 } 0991 0992 KAEventPrivate::KAEventPrivate(const KAEventPrivate& other) 0993 : QSharedData(other) 0994 , mRecurrence(nullptr) 0995 { 0996 copy(other); 0997 } 0998 0999 KAEvent::KAEvent(const KAEvent& other) = default; 1000 1001 KAEvent::~KAEvent() = default; 1002 1003 KAEvent& KAEvent::operator=(const KAEvent& other) 1004 { 1005 if (&other != this) 1006 d = other.d; 1007 return *this; 1008 } 1009 1010 /****************************************************************************** 1011 * Copies the data from another instance. 1012 */ 1013 void KAEventPrivate::copy(const KAEventPrivate& event) 1014 { 1015 mAllTrigger = event.mAllTrigger; 1016 mMainTrigger = event.mMainTrigger; 1017 mAllWorkTrigger = event.mAllWorkTrigger; 1018 mMainWorkTrigger = event.mMainWorkTrigger; 1019 mCommandError = event.mCommandError; 1020 mEventID = event.mEventID; 1021 mCustomProperties = event.mCustomProperties; 1022 mResourceId = event.mResourceId; 1023 mName = event.mName; 1024 mText = event.mText; 1025 mAudioFile = event.mAudioFile; 1026 mPreAction = event.mPreAction; 1027 mPostAction = event.mPostAction; 1028 mStartDateTime = event.mStartDateTime; 1029 mCreatedDateTime = event.mCreatedDateTime; 1030 mNextMainDateTime = event.mNextMainDateTime; 1031 mAtLoginDateTime = event.mAtLoginDateTime; 1032 mDeferralTime = event.mDeferralTime; 1033 mDisplayingTime = event.mDisplayingTime; 1034 mDisplayingFlags = event.mDisplayingFlags; 1035 mReminderMinutes = event.mReminderMinutes; 1036 mReminderAfterTime = event.mReminderAfterTime; 1037 mReminderActive = event.mReminderActive; 1038 mDeferDefaultMinutes = event.mDeferDefaultMinutes; 1039 mDeferDefaultDateOnly = event.mDeferDefaultDateOnly; 1040 mRevision = event.mRevision; 1041 mRepetition = event.mRepetition; 1042 mNextRepeat = event.mNextRepeat; 1043 mAlarmCount = event.mAlarmCount; 1044 mDeferral = event.mDeferral; 1045 mEmailId = event.mEmailId; 1046 mTemplateAfterTime = event.mTemplateAfterTime; 1047 mBgColour = event.mBgColour; 1048 mFgColour = event.mFgColour; 1049 mFont = event.mFont; 1050 mEmailFromIdentity = event.mEmailFromIdentity; 1051 mEmailAddresses = event.mEmailAddresses; 1052 mEmailSubject = event.mEmailSubject; 1053 mEmailAttachments = event.mEmailAttachments; 1054 mLogFile = event.mLogFile; 1055 mSoundVolume = event.mSoundVolume; 1056 mFadeVolume = event.mFadeVolume; 1057 mFadeSeconds = event.mFadeSeconds; 1058 mRepeatSoundPause = event.mRepeatSoundPause; 1059 mLateCancel = event.mLateCancel; 1060 mWakeFromSuspend = event.mWakeFromSuspend; 1061 mExcludeHolidays = event.mExcludeHolidays; 1062 mExcludeHolidayRegion = event.mExcludeHolidayRegion; 1063 mWorkTimeOnly = event.mWorkTimeOnly; 1064 mActionSubType = event.mActionSubType; 1065 mCategory = event.mCategory; 1066 mExtraActionOptions = event.mExtraActionOptions; 1067 mCompatibility = event.mCompatibility; 1068 mReadOnly = event.mReadOnly; 1069 mConfirmAck = event.mConfirmAck; 1070 mUseDefaultFont = event.mUseDefaultFont; 1071 mCommandScript = event.mCommandScript; 1072 mCommandXterm = event.mCommandXterm; 1073 mCommandDisplay = event.mCommandDisplay; 1074 mCommandHideError = event.mCommandHideError; 1075 mEmailBcc = event.mEmailBcc; 1076 mBeep = event.mBeep; 1077 mSpeak = event.mSpeak; 1078 mCopyToKOrganizer = event.mCopyToKOrganizer; 1079 mReminderOnceOnly = event.mReminderOnceOnly; 1080 mAutoClose = event.mAutoClose; 1081 mNotify = event.mNotify; 1082 mMainExpired = event.mMainExpired; 1083 mRepeatAtLogin = event.mRepeatAtLogin; 1084 mArchiveRepeatAtLogin = event.mArchiveRepeatAtLogin; 1085 mArchive = event.mArchive; 1086 mDisplaying = event.mDisplaying; 1087 mDisplayingDefer = event.mDisplayingDefer; 1088 mDisplayingEdit = event.mDisplayingEdit; 1089 mEnabled = event.mEnabled; 1090 mChangeCount = 0; 1091 mTriggerChanged = event.mTriggerChanged; 1092 delete mRecurrence; 1093 if (event.mRecurrence) 1094 mRecurrence = new KARecurrence(*event.mRecurrence); 1095 else 1096 mRecurrence = nullptr; 1097 } 1098 1099 /****************************************************************************** 1100 * Update an existing KCalendarCore::Event with the KAEventPrivate data. 1101 * If 'setCustomProperties' is true, all the KCalendarCore::Event's existing 1102 * custom properties are cleared and replaced with the KAEvent's custom 1103 * properties. If false, the KCalendarCore::Event's non-KAlarm custom properties 1104 * are left untouched. 1105 */ 1106 bool KAEvent::updateKCalEvent(const KCalendarCore::Event::Ptr& e, UidAction u, bool setCustomProperties) const 1107 { 1108 return d->updateKCalEvent(e, u, setCustomProperties); 1109 } 1110 1111 bool KAEventPrivate::updateKCalEvent(const Event::Ptr& ev, KAEvent::UidAction uidact, bool setCustomProperties) const 1112 { 1113 // If it's an archived event, the event start date/time will be adjusted to its original 1114 // value instead of its next occurrence, and the expired main alarm will be reinstated. 1115 const bool archived = (mCategory == CalEvent::ARCHIVED); 1116 1117 if (!ev 1118 || (uidact == KAEvent::UidAction::Check && !mEventID.isEmpty() && mEventID != ev->uid()) 1119 || (!mAlarmCount && (!archived || !mMainExpired))) 1120 return false; 1121 1122 ev->startUpdates(); // prevent multiple update notifications 1123 checkRecur(); // ensure recurrence/repetition data is consistent 1124 const bool readOnly = ev->isReadOnly(); 1125 if (uidact == KAEvent::UidAction::Set) 1126 ev->setUid(mEventID); 1127 ev->setReadOnly(mReadOnly); 1128 ev->setTransparency(Event::Transparent); 1129 1130 // Set up event-specific data 1131 ev->setSummary(mName); 1132 1133 // Set up custom properties. 1134 if (setCustomProperties) 1135 ev->setCustomProperties(mCustomProperties); 1136 ev->removeCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY); 1137 ev->removeCustomProperty(KACalendar::APPNAME, NEXT_RECUR_PROPERTY); 1138 ev->removeCustomProperty(KACalendar::APPNAME, REPEAT_PROPERTY); 1139 ev->removeCustomProperty(KACalendar::APPNAME, LOG_PROPERTY); 1140 1141 QString param; 1142 if (mCategory == CalEvent::DISPLAYING) 1143 { 1144 param = QString::number(mResourceId); // original resource ID which contained the event 1145 if (mDisplayingDefer) 1146 param += SC + DISP_DEFER; 1147 if (mDisplayingEdit) 1148 param += SC + DISP_EDIT; 1149 } 1150 CalEvent::setStatus(ev, mCategory, param); 1151 QStringList flags; 1152 if (mStartDateTime.isDateOnly()) 1153 flags += DATE_ONLY_FLAG; 1154 if (mStartDateTime.timeType() == KADateTime::LocalZone) 1155 flags += LOCAL_ZONE_FLAG; 1156 if (mConfirmAck) 1157 flags += CONFIRM_ACK_FLAG; 1158 if (mEmailBcc) 1159 flags += EMAIL_BCC_FLAG; 1160 if (mCopyToKOrganizer) 1161 flags += KORGANIZER_FLAG; 1162 if (mWakeFromSuspend) 1163 flags += WAKE_SUSPEND_FLAG; 1164 if (mExcludeHolidays) 1165 flags += EXCLUDE_HOLIDAYS_FLAG; 1166 if (mWorkTimeOnly) 1167 flags += WORK_TIME_ONLY_FLAG; 1168 if (mNotify) 1169 flags += NOTIFY_FLAG; 1170 if (mLateCancel) 1171 (flags += (mAutoClose ? AUTO_CLOSE_FLAG : LATE_CANCEL_FLAG)) += QString::number(mLateCancel); 1172 if (mReminderMinutes) 1173 { 1174 flags += REMINDER_TYPE; 1175 if (mReminderOnceOnly) 1176 flags += REMINDER_ONCE_FLAG; 1177 flags += reminderToString(-mReminderMinutes); 1178 } 1179 if (mDeferDefaultMinutes) 1180 { 1181 QString ddparam = QString::number(mDeferDefaultMinutes); 1182 if (mDeferDefaultDateOnly) 1183 ddparam += QLatin1Char('D'); 1184 (flags += DEFER_FLAG) += ddparam; 1185 } 1186 if (mCategory == CalEvent::TEMPLATE && mTemplateAfterTime >= 0) 1187 (flags += TEMPL_AFTER_TIME_FLAG) += QString::number(mTemplateAfterTime); 1188 if (mEmailId >= 0) 1189 (flags += KMAIL_ITEM_FLAG) += QString::number(mEmailId); 1190 if (mArchive && !archived) 1191 { 1192 flags += ARCHIVE_FLAG; 1193 if (mArchiveRepeatAtLogin) 1194 flags += AT_LOGIN_TYPE; 1195 } 1196 if (!flags.isEmpty()) 1197 ev->setCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY, flags.join(SC)); 1198 1199 if (mCommandXterm) 1200 ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, xtermURL); 1201 else if (mCommandDisplay) 1202 ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, displayURL); 1203 else if (!mLogFile.isEmpty()) 1204 ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, mLogFile); 1205 1206 ev->setCustomStatus(mEnabled ? QString() : DISABLED_STATUS); 1207 ev->setRevision(mRevision); 1208 ev->clearAlarms(); 1209 1210 /* Always set DTSTART as date/time, and use the category "DATE" to indicate 1211 * a date-only event, instead of calling setAllDay(). This is necessary to 1212 * allow a time zone to be specified for a date-only event. Also, KAlarm 1213 * allows the alarm to float within the 24-hour period defined by the 1214 * start-of-day time (which is user-dependent and therefore can't be 1215 * written into the calendar) rather than midnight to midnight, and there 1216 * is no RFC2445 conformant way to specify this. 1217 * RFC2445 states that alarm trigger times specified in absolute terms 1218 * (rather than relative to DTSTART or DTEND) can only be specified as a 1219 * UTC DATE-TIME value. So always use a time relative to DTSTART instead of 1220 * an absolute time. 1221 */ 1222 ev->setDtStart(mStartDateTime.calendarDateTime()); 1223 ev->setAllDay(false); 1224 ev->setDtEnd(QDateTime()); 1225 1226 const DateTime dtMain = archived ? mStartDateTime : mNextMainDateTime; 1227 int ancillaryType = 0; // 0 = invalid, 1 = time, 2 = offset 1228 DateTime ancillaryTime; // time for ancillary alarms (pre-action, extra audio, etc) 1229 int ancillaryOffset = 0; // start offset for ancillary alarms 1230 if (!mMainExpired || archived) 1231 { 1232 /* The alarm offset must always be zero for the main alarm. To determine 1233 * which recurrence is due, the property X-KDE-KALARM_NEXTRECUR is used. 1234 * If the alarm offset was non-zero, exception dates and rules would not 1235 * work since they apply to the event time, not the alarm time. 1236 */ 1237 if (!archived && checkRecur() != KARecurrence::NO_RECUR) 1238 { 1239 QDateTime dt = mNextMainDateTime.kDateTime().toTimeSpec(mStartDateTime.timeSpec()).qDateTime(); 1240 ev->setCustomProperty(KACalendar::APPNAME, NEXT_RECUR_PROPERTY, 1241 QLocale::c().toString(dt, mNextMainDateTime.isDateOnly() ? QStringLiteral("yyyyMMdd") : QStringLiteral("yyyyMMddThhmmss"))); 1242 } 1243 // Add the main alarm 1244 initKCalAlarm(ev, 0, QStringList(), MAIN_ALARM); 1245 ancillaryOffset = 0; 1246 ancillaryType = dtMain.isValid() ? 2 : 0; 1247 } 1248 else if (mRepetition) 1249 { 1250 // Alarm repetition is normally held in the main alarm, but since 1251 // the main alarm has expired, store in a custom property. 1252 const QString repparam = QStringLiteral("%1:%2").arg(mRepetition.intervalMinutes()).arg(mRepetition.count()); 1253 ev->setCustomProperty(KACalendar::APPNAME, REPEAT_PROPERTY, repparam); 1254 } 1255 1256 // Add subsidiary alarms 1257 if (mRepeatAtLogin || (mArchiveRepeatAtLogin && archived)) 1258 { 1259 DateTime dtl; 1260 if (mArchiveRepeatAtLogin) 1261 dtl = mStartDateTime.calendarKDateTime().addDays(-1); 1262 else if (mAtLoginDateTime.isValid()) 1263 dtl = mAtLoginDateTime; 1264 else if (mStartDateTime.isDateOnly()) 1265 dtl = DateTime(KADateTime::currentLocalDate().addDays(-1), mStartDateTime.timeSpec()); 1266 else 1267 dtl = KADateTime::currentUtcDateTime(); 1268 initKCalAlarm(ev, dtl, QStringList(AT_LOGIN_TYPE)); 1269 if (!ancillaryType && dtl.isValid()) 1270 { 1271 ancillaryTime = dtl; 1272 ancillaryType = 1; 1273 } 1274 } 1275 1276 // Find the base date/time for calculating alarm offsets 1277 DateTime nextDateTime = mNextMainDateTime; 1278 if (mMainExpired) 1279 { 1280 if (checkRecur() == KARecurrence::NO_RECUR) 1281 nextDateTime = mStartDateTime; 1282 else if (!archived) 1283 { 1284 // It's a deferral of an expired recurrence. 1285 // Need to ensure that the alarm offset is to an occurrence 1286 // which isn't excluded by an exception - otherwise, it will 1287 // never be triggered. So choose the first recurrence which 1288 // isn't an exception. 1289 KADateTime dt = mRecurrence->getNextDateTime(mStartDateTime.addDays(-1).kDateTime()); 1290 dt.setDateOnly(mStartDateTime.isDateOnly()); 1291 nextDateTime = dt; 1292 } 1293 } 1294 1295 if (mReminderMinutes && (mReminderActive != ReminderType::None || archived)) 1296 { 1297 int startOffset; 1298 if (mReminderMinutes < 0 && mReminderActive != ReminderType::None) 1299 { 1300 // A reminder AFTER the main alarm is active or disabled 1301 startOffset = nextDateTime.calendarKDateTime().secsTo(mReminderAfterTime.calendarKDateTime()); 1302 } 1303 else 1304 { 1305 // A reminder BEFORE the main alarm is active 1306 startOffset = -mReminderMinutes * 60; 1307 } 1308 initKCalAlarm(ev, startOffset, QStringList(REMINDER_TYPE)); 1309 // Don't set ancillary time if the reminder AFTER is hidden by a deferral 1310 if (!ancillaryType && (mReminderActive == ReminderType::Active || archived)) 1311 { 1312 ancillaryOffset = startOffset; 1313 ancillaryType = 2; 1314 } 1315 } 1316 if (mDeferral != DeferType::None) 1317 { 1318 int startOffset; 1319 QStringList list; 1320 if (mDeferralTime.isDateOnly()) 1321 { 1322 startOffset = nextDateTime.secsTo(mDeferralTime.calendarKDateTime()); 1323 list += DATE_DEFERRAL_TYPE; 1324 } 1325 else 1326 { 1327 startOffset = nextDateTime.calendarKDateTime().secsTo(mDeferralTime.calendarKDateTime()); 1328 list += TIME_DEFERRAL_TYPE; 1329 } 1330 if (mDeferral == DeferType::Reminder) 1331 list += REMINDER_TYPE; 1332 initKCalAlarm(ev, startOffset, list); 1333 if (!ancillaryType && mDeferralTime.isValid()) 1334 { 1335 ancillaryOffset = startOffset; 1336 ancillaryType = 2; 1337 } 1338 } 1339 if (mDisplaying && mCategory != CalEvent::TEMPLATE) 1340 { 1341 QStringList list(DISPLAYING_TYPE); 1342 if (mDisplayingFlags & KAEvent::REPEAT_AT_LOGIN) 1343 list += AT_LOGIN_TYPE; 1344 else if (mDisplayingFlags & DEFERRAL) 1345 { 1346 if (mDisplayingFlags & TIMED_FLAG) 1347 list += TIME_DEFERRAL_TYPE; 1348 else 1349 list += DATE_DEFERRAL_TYPE; 1350 } 1351 if (mDisplayingFlags & REMINDER) 1352 list += REMINDER_TYPE; 1353 initKCalAlarm(ev, mDisplayingTime, list); 1354 if (!ancillaryType && mDisplayingTime.isValid()) 1355 { 1356 ancillaryTime = mDisplayingTime; 1357 ancillaryType = 1; 1358 } 1359 } 1360 if ((mBeep || mSpeak || !mAudioFile.isEmpty()) && mActionSubType != KAEvent::SubAction::Audio) 1361 { 1362 // A sound is specified 1363 if (ancillaryType == 2) 1364 initKCalAlarm(ev, ancillaryOffset, QStringList(), AUDIO_ALARM); 1365 else 1366 initKCalAlarm(ev, ancillaryTime, QStringList(), AUDIO_ALARM); 1367 } 1368 if (!mPreAction.isEmpty()) 1369 { 1370 // A pre-display action is specified 1371 if (ancillaryType == 2) 1372 initKCalAlarm(ev, ancillaryOffset, QStringList(PRE_ACTION_TYPE), PRE_ACTION_ALARM); 1373 else 1374 initKCalAlarm(ev, ancillaryTime, QStringList(PRE_ACTION_TYPE), PRE_ACTION_ALARM); 1375 } 1376 if (!mPostAction.isEmpty()) 1377 { 1378 // A post-display action is specified 1379 if (ancillaryType == 2) 1380 initKCalAlarm(ev, ancillaryOffset, QStringList(POST_ACTION_TYPE), POST_ACTION_ALARM); 1381 else 1382 initKCalAlarm(ev, ancillaryTime, QStringList(POST_ACTION_TYPE), POST_ACTION_ALARM); 1383 } 1384 1385 if (mRecurrence) 1386 mRecurrence->writeRecurrence(*ev->recurrence()); 1387 else 1388 ev->clearRecurrence(); 1389 if (mCreatedDateTime.isValid()) 1390 ev->setCreated(mCreatedDateTime.qDateTime()); 1391 ev->setReadOnly(readOnly); 1392 ev->endUpdates(); // finally issue an update notification 1393 return true; 1394 } 1395 1396 /****************************************************************************** 1397 * Create a new alarm for a libkcal event, and initialise it according to the 1398 * alarm action. If 'types' is non-null, it is appended to the X-KDE-KALARM-TYPE 1399 * property value list. 1400 * NOTE: The variant taking a DateTime calculates the offset from mStartDateTime, 1401 * which is not suitable for an alarm in a recurring event. 1402 */ 1403 Alarm::Ptr KAEventPrivate::initKCalAlarm(const KCalendarCore::Event::Ptr& event, const DateTime& dt, const QStringList& types, AlarmType type) const 1404 { 1405 const int startOffset = dt.isDateOnly() ? mStartDateTime.secsTo(dt) 1406 : mStartDateTime.calendarKDateTime().secsTo(dt.calendarKDateTime()); 1407 return initKCalAlarm(event, startOffset, types, type); 1408 } 1409 1410 Alarm::Ptr KAEventPrivate::initKCalAlarm(const KCalendarCore::Event::Ptr& event, int startOffsetSecs, const QStringList& types, AlarmType type) const 1411 { 1412 QStringList alltypes; 1413 QStringList flags; 1414 Alarm::Ptr alarm = event->newAlarm(); 1415 alarm->setEnabled(true); 1416 if (type != MAIN_ALARM) 1417 { 1418 // RFC2445 specifies that absolute alarm times must be stored as a UTC DATE-TIME value. 1419 // Set the alarm time as an offset to DTSTART for the reasons described in updateKCalEvent(). 1420 alarm->setStartOffset(startOffsetSecs); 1421 } 1422 1423 switch (type) 1424 { 1425 case AUDIO_ALARM: 1426 setAudioAlarm(alarm); 1427 if (mSpeak) 1428 flags << KAEventPrivate::SPEAK_FLAG; 1429 if (mRepeatSoundPause >= 0) 1430 { 1431 // Alarm::setSnoozeTime() sets 5 seconds if duration parameter is zero, 1432 // so repeat count = -1 represents 0 pause, -2 represents non-zero pause. 1433 alarm->setRepeatCount(mRepeatSoundPause ? -2 : -1); 1434 alarm->setSnoozeTime(Duration(mRepeatSoundPause, Duration::Seconds)); 1435 } 1436 break; 1437 case PRE_ACTION_ALARM: 1438 setProcedureAlarm(alarm, mPreAction); 1439 if (mExtraActionOptions & KAEvent::ExecPreActOnDeferral) 1440 flags << KAEventPrivate::EXEC_ON_DEFERRAL_FLAG; 1441 if (mExtraActionOptions & KAEvent::CancelOnPreActError) 1442 flags << KAEventPrivate::CANCEL_ON_ERROR_FLAG; 1443 if (mExtraActionOptions & KAEvent::DontShowPreActError) 1444 flags << KAEventPrivate::DONT_SHOW_ERROR_FLAG; 1445 break; 1446 case POST_ACTION_ALARM: 1447 setProcedureAlarm(alarm, mPostAction); 1448 break; 1449 case MAIN_ALARM: 1450 alarm->setSnoozeTime(mRepetition.interval()); 1451 alarm->setRepeatCount(mRepetition.count()); 1452 if (mRepetition) 1453 alarm->setCustomProperty(KACalendar::APPNAME, NEXT_REPEAT_PROPERTY, 1454 QString::number(mNextRepeat)); 1455 [[fallthrough]]; // fall through to INVALID_ALARM 1456 case REMINDER_ALARM: 1457 case INVALID_ALARM: 1458 { 1459 if (types == QStringList(REMINDER_TYPE) 1460 && mReminderMinutes < 0 && mReminderActive == ReminderType::Hidden) 1461 { 1462 // It's a reminder AFTER the alarm which is currently disabled 1463 // due to the main alarm being deferred past it. 1464 flags << HIDDEN_REMINDER_FLAG; 1465 } 1466 bool display = false; 1467 switch (mActionSubType) 1468 { 1469 case KAEvent::SubAction::File: 1470 alltypes += FILE_TYPE; 1471 [[fallthrough]]; // fall through to Message 1472 case KAEvent::SubAction::Message: 1473 alarm->setDisplayAlarm(AlarmText::toCalendarText(mText)); 1474 display = true; 1475 break; 1476 case KAEvent::SubAction::Command: 1477 if (mCommandScript) 1478 alarm->setProcedureAlarm(QString(), mText); 1479 else 1480 setProcedureAlarm(alarm, mText); 1481 display = mCommandDisplay; 1482 if (mCommandHideError) 1483 flags += DONT_SHOW_ERROR_FLAG; 1484 break; 1485 case KAEvent::SubAction::Email: 1486 alarm->setEmailAlarm(mEmailSubject, mText, mEmailAddresses, mEmailAttachments); 1487 if (mEmailFromIdentity) 1488 flags << KAEventPrivate::EMAIL_ID_FLAG << QString::number(mEmailFromIdentity); 1489 break; 1490 case KAEvent::SubAction::Audio: 1491 setAudioAlarm(alarm); 1492 if (mRepeatSoundPause >= 0 && type == MAIN_ALARM) 1493 { 1494 // Indicate repeating sound in the main alarm by a non-standard 1495 // method, since it might have a sub-repetition too. 1496 alltypes << SOUND_REPEAT_TYPE << QString::number(mRepeatSoundPause); 1497 } 1498 break; 1499 } 1500 if (display && !mNotify) 1501 alarm->setCustomProperty(KACalendar::APPNAME, FONT_COLOUR_PROPERTY, 1502 QStringLiteral("%1;%2;%3").arg(mBgColour.name(), mFgColour.name(), mUseDefaultFont ? QString() : mFont.toString())); 1503 break; 1504 } 1505 case DEFERRED_ALARM: 1506 case DEFERRED_REMINDER_ALARM: 1507 case AT_LOGIN_ALARM: 1508 case DISPLAYING_ALARM: 1509 break; 1510 } 1511 alltypes += types; 1512 if (!alltypes.isEmpty()) 1513 alarm->setCustomProperty(KACalendar::APPNAME, TYPE_PROPERTY, alltypes.join(QLatin1Char(','))); 1514 if (!flags.isEmpty()) 1515 alarm->setCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY, flags.join(SC)); 1516 return alarm; 1517 } 1518 1519 /****************************************************************************** 1520 * Find the index to the last daylight savings time transition at or before a 1521 * given UTC time. 1522 * Returns index, or -1 if before the first transition. 1523 */ 1524 int KAEventPrivate::transitionIndex(const QDateTime& utc, const QTimeZone::OffsetDataList& transitions) 1525 { 1526 if (utc.timeSpec() != Qt::UTC || transitions.isEmpty()) 1527 return -1; 1528 int start = 0; 1529 int end = transitions.size() - 1; 1530 while (start != end) 1531 { 1532 int i = (start + end + 1) / 2; 1533 if (transitions[i].atUtc == utc) 1534 return i; 1535 if (transitions[i].atUtc > utc) 1536 { 1537 end = i - 1; 1538 if (end < 0) 1539 return -1; 1540 } 1541 else 1542 start = i; 1543 } 1544 return start; 1545 } 1546 1547 bool KAEvent::isValid() const 1548 { 1549 return d->mAlarmCount && (d->mAlarmCount != 1 || !d->mRepeatAtLogin); 1550 } 1551 1552 void KAEvent::setEnabled(bool enable) 1553 { 1554 d->mEnabled = enable; 1555 } 1556 1557 bool KAEvent::enabled() const 1558 { 1559 return d->mEnabled; 1560 } 1561 1562 void KAEvent::setReadOnly(bool ro) 1563 { 1564 d->mReadOnly = ro; 1565 } 1566 1567 bool KAEvent::isReadOnly() const 1568 { 1569 return d->mReadOnly; 1570 } 1571 1572 void KAEvent::setArchive() 1573 { 1574 d->mArchive = true; 1575 } 1576 1577 bool KAEvent::toBeArchived() const 1578 { 1579 return d->mArchive; 1580 } 1581 1582 bool KAEvent::mainExpired() const 1583 { 1584 return d->mMainExpired; 1585 } 1586 1587 bool KAEvent::expired() const 1588 { 1589 return (d->mDisplaying && d->mMainExpired) || d->mCategory == CalEvent::ARCHIVED; 1590 } 1591 1592 KAEvent::Flags KAEvent::flags() const 1593 { 1594 return d->flags(); 1595 } 1596 1597 KAEvent::Flags KAEventPrivate::flags() const 1598 { 1599 KAEvent::Flags result{}; 1600 if (mBeep) 1601 result |= KAEvent::BEEP; 1602 if (mRepeatSoundPause >= 0) 1603 result |= KAEvent::REPEAT_SOUND; 1604 if (mEmailBcc) 1605 result |= KAEvent::EMAIL_BCC; 1606 if (mStartDateTime.isDateOnly()) 1607 result |= KAEvent::ANY_TIME; 1608 if (mSpeak) 1609 result |= KAEvent::SPEAK; 1610 if (mRepeatAtLogin) 1611 result |= KAEvent::REPEAT_AT_LOGIN; 1612 if (mConfirmAck) 1613 result |= KAEvent::CONFIRM_ACK; 1614 if (mUseDefaultFont) 1615 result |= KAEvent::DEFAULT_FONT; 1616 if (mCommandScript) 1617 result |= KAEvent::SCRIPT; 1618 if (mCommandXterm) 1619 result |= KAEvent::EXEC_IN_XTERM; 1620 if (mCommandDisplay) 1621 result |= KAEvent::DISPLAY_COMMAND; 1622 if (mCommandHideError) 1623 result |= KAEvent::DONT_SHOW_ERROR; 1624 if (mCopyToKOrganizer) 1625 result |= KAEvent::COPY_KORGANIZER; 1626 if (mWakeFromSuspend) 1627 result |= KAEvent::WAKE_SUSPEND; 1628 if (mExcludeHolidays) 1629 result |= KAEvent::EXCL_HOLIDAYS; 1630 if (mWorkTimeOnly) 1631 result |= KAEvent::WORK_TIME_ONLY; 1632 if (mReminderOnceOnly) 1633 result |= KAEvent::REMINDER_ONCE; 1634 if (mAutoClose) 1635 result |= KAEvent::AUTO_CLOSE; 1636 if (mNotify) 1637 result |= KAEvent::NOTIFY; 1638 if (!mEnabled) 1639 result |= KAEvent::DISABLED; 1640 return result; 1641 } 1642 1643 /****************************************************************************** 1644 * Change the type of an event. 1645 * If it is being set to archived, set the archived indication in the event ID; 1646 * otherwise, remove the archived indication from the event ID. 1647 */ 1648 void KAEvent::setCategory(CalEvent::Type s) 1649 { 1650 d->setCategory(s); 1651 } 1652 1653 void KAEventPrivate::setCategory(CalEvent::Type s) 1654 { 1655 if (s == mCategory) 1656 return; 1657 mEventID = CalEvent::uid(mEventID, s); 1658 mCategory = s; 1659 mTriggerChanged = true; // templates and archived don't have trigger times 1660 } 1661 1662 CalEvent::Type KAEvent::category() const 1663 { 1664 return d->mCategory; 1665 } 1666 1667 void KAEvent::setEventId(const QString& id) 1668 { 1669 d->mEventID = id; 1670 } 1671 1672 QString KAEvent::id() const 1673 { 1674 return d->mEventID; 1675 } 1676 1677 void KAEvent::incrementRevision() 1678 { 1679 ++d->mRevision; 1680 } 1681 1682 int KAEvent::revision() const 1683 { 1684 return d->mRevision; 1685 } 1686 1687 void KAEvent::setResourceId(ResourceId id) 1688 { 1689 d->mResourceId = id; 1690 } 1691 1692 ResourceId KAEvent::resourceId() const 1693 { 1694 // A displaying alarm contains the event's original resource ID 1695 return d->mDisplaying ? -1 : d->mResourceId; 1696 } 1697 1698 void KAEvent::setCompatibility(KACalendar::Compat c) 1699 { 1700 d->mCompatibility = c; 1701 } 1702 1703 KACalendar::Compat KAEvent::compatibility() const 1704 { 1705 return d->mCompatibility; 1706 } 1707 1708 QMap<QByteArray, QString> KAEvent::customProperties() const 1709 { 1710 return d->mCustomProperties; 1711 } 1712 1713 KAEvent::SubAction KAEvent::actionSubType() const 1714 { 1715 return d->mActionSubType; 1716 } 1717 1718 KAEvent::Action KAEvent::actionTypes() const 1719 { 1720 switch (d->mActionSubType) 1721 { 1722 case SubAction::Message: 1723 case SubAction::File: return Action::Display; 1724 case SubAction::Command: return d->mCommandDisplay ? Action::DisplayCommand : Action::Command; 1725 case SubAction::Email: return Action::Email; 1726 case SubAction::Audio: return Action::Audio; 1727 default: return Action::None; 1728 } 1729 } 1730 1731 void KAEvent::setLateCancel(int minutes) 1732 { 1733 if (d->mRepeatAtLogin) 1734 minutes = 0; 1735 d->mLateCancel = minutes; 1736 if (!minutes) 1737 d->mAutoClose = false; 1738 } 1739 1740 int KAEvent::lateCancel() const 1741 { 1742 return d->mLateCancel; 1743 } 1744 1745 void KAEvent::setAutoClose(bool ac) 1746 { 1747 d->mAutoClose = ac; 1748 } 1749 1750 bool KAEvent::autoClose() const 1751 { 1752 return d->mAutoClose; 1753 } 1754 1755 void KAEvent::setNotify(bool useNotify) 1756 { 1757 d->mNotify = useNotify; 1758 } 1759 1760 bool KAEvent::notify() const 1761 { 1762 return d->mNotify; 1763 } 1764 1765 void KAEvent::setEmailId(EmailId id) 1766 { 1767 d->mEmailId = id; 1768 } 1769 1770 KAEvent::EmailId KAEvent::emailId() const 1771 { 1772 return d->mEmailId; 1773 } 1774 1775 void KAEvent::setName(const QString& newName) 1776 { 1777 d->mName = newName; 1778 } 1779 1780 QString KAEvent::name() const 1781 { 1782 return d->mName; 1783 } 1784 1785 QString KAEvent::cleanText() const 1786 { 1787 return d->mText; 1788 } 1789 1790 QString KAEvent::message() const 1791 { 1792 return (d->mActionSubType == SubAction::Message 1793 || d->mActionSubType == SubAction::Email) ? d->mText : QString(); 1794 } 1795 1796 QString KAEvent::displayMessage() const 1797 { 1798 return (d->mActionSubType == SubAction::Message) ? d->mText : QString(); 1799 } 1800 1801 QString KAEvent::fileName() const 1802 { 1803 return (d->mActionSubType == SubAction::File) ? d->mText : QString(); 1804 } 1805 1806 QColor KAEvent::bgColour() const 1807 { 1808 return d->mBgColour; 1809 } 1810 1811 QColor KAEvent::fgColour() const 1812 { 1813 return d->mFgColour; 1814 } 1815 1816 void KAEvent::setDefaultFont(const QFont& f) 1817 { 1818 KAEventPrivate::mDefaultFont = f; 1819 } 1820 1821 bool KAEvent::useDefaultFont() const 1822 { 1823 return d->mUseDefaultFont; 1824 } 1825 1826 QFont KAEvent::font() const 1827 { 1828 return d->mUseDefaultFont ? KAEventPrivate::mDefaultFont : d->mFont; 1829 } 1830 1831 QString KAEvent::command() const 1832 { 1833 return (d->mActionSubType == SubAction::Command) ? d->mText : QString(); 1834 } 1835 1836 bool KAEvent::commandScript() const 1837 { 1838 return d->mCommandScript; 1839 } 1840 1841 bool KAEvent::commandXterm() const 1842 { 1843 return d->mCommandXterm; 1844 } 1845 1846 bool KAEvent::commandDisplay() const 1847 { 1848 return d->mCommandDisplay; 1849 } 1850 1851 void KAEvent::setCommandError(CmdErr t) const 1852 { 1853 d->mCommandError = t; 1854 } 1855 1856 KAEvent::CmdErr KAEvent::commandError() const 1857 { 1858 return d->mCommandError; 1859 } 1860 1861 bool KAEvent::commandHideError() const 1862 { 1863 return d->mCommandHideError; 1864 } 1865 1866 void KAEvent::setLogFile(const QString& logfile) 1867 { 1868 d->mLogFile = logfile; 1869 if (!logfile.isEmpty()) 1870 d->mCommandDisplay = d->mCommandXterm = false; 1871 } 1872 1873 QString KAEvent::logFile() const 1874 { 1875 return d->mLogFile; 1876 } 1877 1878 bool KAEvent::confirmAck() const 1879 { 1880 return d->mConfirmAck; 1881 } 1882 1883 bool KAEvent::copyToKOrganizer() const 1884 { 1885 return d->mCopyToKOrganizer; 1886 } 1887 1888 void KAEvent::setEmail(uint from, const KCalendarCore::Person::List& addresses, const QString& subject, 1889 const QStringList& attachments) 1890 { 1891 d->mEmailFromIdentity = from; 1892 d->mEmailAddresses = addresses; 1893 d->mEmailSubject = subject; 1894 d->mEmailAttachments = attachments; 1895 } 1896 1897 QString KAEvent::emailMessage() const 1898 { 1899 return (d->mActionSubType == SubAction::Email) ? d->mText : QString(); 1900 } 1901 1902 uint KAEvent::emailFromId() const 1903 { 1904 return d->mEmailFromIdentity; 1905 } 1906 1907 KCalendarCore::Person::List KAEvent::emailAddressees() const 1908 { 1909 return d->mEmailAddresses; 1910 } 1911 1912 QStringList KAEvent::emailAddresses() const 1913 { 1914 return static_cast<QStringList>(d->mEmailAddresses); 1915 } 1916 1917 QString KAEvent::emailAddresses(const QString& sep) const 1918 { 1919 return d->mEmailAddresses.join(sep); 1920 } 1921 1922 QString KAEvent::joinEmailAddresses(const KCalendarCore::Person::List& addresses, const QString& separator) 1923 { 1924 return EmailAddressList(addresses).join(separator); 1925 } 1926 1927 QStringList KAEvent::emailPureAddresses() const 1928 { 1929 return d->mEmailAddresses.pureAddresses(); 1930 } 1931 1932 QString KAEvent::emailPureAddresses(const QString& sep) const 1933 { 1934 return d->mEmailAddresses.pureAddresses(sep); 1935 } 1936 1937 QString KAEvent::emailSubject() const 1938 { 1939 return d->mEmailSubject; 1940 } 1941 1942 QStringList KAEvent::emailAttachments() const 1943 { 1944 return d->mEmailAttachments; 1945 } 1946 1947 QString KAEvent::emailAttachments(const QString& sep) const 1948 { 1949 return d->mEmailAttachments.join(sep); 1950 } 1951 1952 bool KAEvent::emailBcc() const 1953 { 1954 return d->mEmailBcc; 1955 } 1956 1957 void KAEvent::setAudioFile(const QString& filename, float volume, float fadeVolume, int fadeSeconds, 1958 int repeatPause, bool allowEmptyFile) 1959 { 1960 d->setAudioFile(filename, volume, fadeVolume, fadeSeconds, repeatPause, allowEmptyFile); 1961 } 1962 1963 void KAEventPrivate::setAudioFile(const QString& filename, float volume, float fadeVolume, int fadeSeconds, 1964 int repeatPause, bool allowEmptyFile) 1965 { 1966 mAudioFile = filename; 1967 mSoundVolume = (!allowEmptyFile && filename.isEmpty()) ? -1 : volume; 1968 if (mSoundVolume >= 0) 1969 { 1970 mFadeVolume = (fadeSeconds > 0) ? fadeVolume : -1; 1971 mFadeSeconds = (mFadeVolume >= 0) ? fadeSeconds : 0; 1972 } 1973 else 1974 { 1975 mFadeVolume = -1; 1976 mFadeSeconds = 0; 1977 } 1978 mRepeatSoundPause = repeatPause; 1979 } 1980 1981 QString KAEvent::audioFile() const 1982 { 1983 return d->mAudioFile; 1984 } 1985 1986 float KAEvent::soundVolume() const 1987 { 1988 return d->mSoundVolume; 1989 } 1990 1991 float KAEvent::fadeVolume() const 1992 { 1993 return d->mSoundVolume >= 0 && d->mFadeSeconds ? d->mFadeVolume : -1; 1994 } 1995 1996 int KAEvent::fadeSeconds() const 1997 { 1998 return d->mSoundVolume >= 0 && d->mFadeVolume >= 0 ? d->mFadeSeconds : 0; 1999 } 2000 2001 bool KAEvent::repeatSound() const 2002 { 2003 return d->mRepeatSoundPause >= 0; 2004 } 2005 2006 int KAEvent::repeatSoundPause() const 2007 { 2008 return d->mRepeatSoundPause; 2009 } 2010 2011 bool KAEvent::beep() const 2012 { 2013 return d->mBeep; 2014 } 2015 2016 bool KAEvent::speak() const 2017 { 2018 return (d->mActionSubType == SubAction::Message 2019 || (d->mActionSubType == SubAction::Command && d->mCommandDisplay)) 2020 && d->mSpeak; 2021 } 2022 2023 /****************************************************************************** 2024 * Set the event to be an alarm template. 2025 */ 2026 void KAEvent::setTemplate(const QString& name, int afterTime) 2027 { 2028 d->setCategory(CalEvent::TEMPLATE); 2029 d->mName = name; 2030 d->mTemplateAfterTime = afterTime; 2031 d->mTriggerChanged = true; // templates and archived don't have trigger times 2032 } 2033 2034 bool KAEvent::isTemplate() const 2035 { 2036 return d->mCategory == CalEvent::TEMPLATE; 2037 } 2038 2039 bool KAEvent::usingDefaultTime() const 2040 { 2041 return d->mTemplateAfterTime == 0; 2042 } 2043 2044 int KAEvent::templateAfterTime() const 2045 { 2046 return d->mTemplateAfterTime; 2047 } 2048 2049 void KAEvent::setActions(const QString& pre, const QString& post, ExtraActionOptions options) 2050 { 2051 d->mPreAction = pre; 2052 d->mPostAction = post; 2053 d->mExtraActionOptions = options; 2054 } 2055 2056 QString KAEvent::preAction() const 2057 { 2058 return d->mPreAction; 2059 } 2060 2061 QString KAEvent::postAction() const 2062 { 2063 return d->mPostAction; 2064 } 2065 2066 KAEvent::ExtraActionOptions KAEvent::extraActionOptions() const 2067 { 2068 return d->mExtraActionOptions; 2069 } 2070 2071 /****************************************************************************** 2072 * Set a reminder. 2073 * 'minutes' = number of minutes BEFORE the main alarm. 2074 */ 2075 void KAEvent::setReminder(int minutes, bool onceOnly) 2076 { 2077 d->setReminder(minutes, onceOnly); 2078 } 2079 2080 void KAEventPrivate::setReminder(int minutes, bool onceOnly) 2081 { 2082 if (minutes > 0 && mRepeatAtLogin) 2083 minutes = 0; 2084 if (minutes != mReminderMinutes || (minutes && mReminderActive != ReminderType::Active)) 2085 { 2086 const ReminderType oldReminderActive = mReminderActive; 2087 mReminderMinutes = minutes; 2088 mReminderActive = minutes > 0 ? ReminderType::Active : ReminderType::None; 2089 mReminderOnceOnly = onceOnly; 2090 mReminderAfterTime = DateTime(); 2091 if (mReminderActive != ReminderType::None && oldReminderActive == ReminderType::None) 2092 ++mAlarmCount; 2093 else if (mReminderActive == ReminderType::None && oldReminderActive != ReminderType::None) 2094 --mAlarmCount; 2095 mTriggerChanged = true; 2096 } 2097 } 2098 2099 /****************************************************************************** 2100 * Activate the event's reminder which occurs AFTER the given main alarm time. 2101 * Reply = true if successful (i.e. reminder falls before the next main alarm). 2102 */ 2103 void KAEvent::activateReminderAfter(const DateTime& mainAlarmTime) 2104 { 2105 d->activateReminderAfter(mainAlarmTime); 2106 } 2107 2108 void KAEventPrivate::activateReminderAfter(const DateTime& mainAlarmTime) 2109 { 2110 if (mReminderMinutes >= 0 || mReminderActive == ReminderType::Active || !mainAlarmTime.isValid()) 2111 return; 2112 // There is a reminder AFTER the main alarm. 2113 if (checkRecur() != KARecurrence::NO_RECUR) 2114 { 2115 // For a recurring alarm, the given alarm time must be a recurrence, not a sub-repetition. 2116 DateTime next; 2117 //???? For some unknown reason, addSecs(-1) returns the recurrence after the next, 2118 //???? so addSecs(-60) is used instead. 2119 if (nextRecurrence(mainAlarmTime.addSecs(-60).effectiveKDateTime(), next) == KAEvent::OccurType::None 2120 || mainAlarmTime != next) 2121 return; 2122 } 2123 else if (!mRepeatAtLogin) 2124 { 2125 // For a non-recurring alarm, the given alarm time must be the main alarm time. 2126 if (mainAlarmTime != mStartDateTime) 2127 return; 2128 } 2129 2130 const DateTime reminderTime = mainAlarmTime.addMins(-mReminderMinutes); 2131 DateTime next; 2132 if (nextOccurrence(mainAlarmTime.effectiveKDateTime(), next, KAEvent::Repeats::Return) != KAEvent::OccurType::None 2133 && reminderTime >= next) 2134 return; // the reminder time is after the next occurrence of the main alarm 2135 2136 qCDebug(KALARMCAL_LOG) << "Setting reminder at" << reminderTime.effectiveKDateTime().toString(QStringLiteral("%Y-%m-%d %H:%M")); 2137 activate_reminder(true); 2138 mReminderAfterTime = reminderTime; 2139 } 2140 2141 int KAEvent::reminderMinutes() const 2142 { 2143 return d->mReminderMinutes; 2144 } 2145 2146 bool KAEvent::reminderActive() const 2147 { 2148 return d->mReminderActive == KAEventPrivate::ReminderType::Active; 2149 } 2150 2151 bool KAEvent::reminderOnceOnly() const 2152 { 2153 return d->mReminderOnceOnly; 2154 } 2155 2156 bool KAEvent::reminderDeferral() const 2157 { 2158 return d->mDeferral == KAEventPrivate::DeferType::Reminder; 2159 } 2160 2161 /****************************************************************************** 2162 * Defer the event to the specified time. 2163 * If the main alarm time has passed, the main alarm is marked as expired. 2164 * If 'adjustRecurrence' is true, ensure that the next scheduled recurrence is 2165 * after the current time. 2166 */ 2167 void KAEvent::defer(const DateTime& dt, bool reminder, bool adjustRecurrence) 2168 { 2169 d->defer(dt, reminder, adjustRecurrence); 2170 } 2171 2172 void KAEventPrivate::defer(const DateTime& dateTime, bool reminder, bool adjustRecurrence) 2173 { 2174 startChanges(); // prevent multiple trigger time evaluation here 2175 bool setNextRepetition = false; 2176 bool checkRepetition = false; 2177 bool checkReminderAfter = false; 2178 if (checkRecur() == KARecurrence::NO_RECUR) 2179 { 2180 // Deferring a non-recurring alarm 2181 if (mReminderMinutes) 2182 { 2183 bool deferReminder = false; 2184 if (mReminderMinutes > 0) 2185 { 2186 // There's a reminder BEFORE the main alarm 2187 if (dateTime < mNextMainDateTime.effectiveKDateTime()) 2188 deferReminder = true; 2189 else if (mReminderActive == ReminderType::Active || mDeferral == DeferType::Reminder) 2190 { 2191 // Deferring past the main alarm time, so adjust any existing deferral 2192 set_deferral(DeferType::None); 2193 mTriggerChanged = true; 2194 } 2195 } 2196 else if (reminder) 2197 deferReminder = true; // deferring a reminder AFTER the main alarm 2198 if (deferReminder) 2199 { 2200 set_deferral(DeferType::Reminder); // defer reminder alarm 2201 mDeferralTime = dateTime; 2202 mTriggerChanged = true; 2203 } 2204 if (mReminderActive == ReminderType::Active) 2205 { 2206 activate_reminder(false); 2207 mTriggerChanged = true; 2208 } 2209 } 2210 if (mDeferral != DeferType::Reminder) 2211 { 2212 // We're deferring the main alarm. 2213 // Main alarm has now expired. 2214 mNextMainDateTime = mDeferralTime = dateTime; 2215 set_deferral(DeferType::Normal); 2216 mTriggerChanged = true; 2217 checkReminderAfter = true; 2218 if (!mMainExpired) 2219 { 2220 // Mark the alarm as expired now 2221 mMainExpired = true; 2222 --mAlarmCount; 2223 if (mRepeatAtLogin) 2224 { 2225 // Remove the repeat-at-login alarm, but keep a note of it for archiving purposes 2226 mArchiveRepeatAtLogin = true; 2227 mRepeatAtLogin = false; 2228 --mAlarmCount; 2229 } 2230 } 2231 } 2232 } 2233 else if (reminder) 2234 { 2235 // Deferring a reminder for a recurring alarm 2236 if (dateTime >= mNextMainDateTime.effectiveKDateTime()) 2237 { 2238 // Trying to defer it past the next main alarm (regardless of whether 2239 // the reminder triggered before or after the main alarm). 2240 set_deferral(DeferType::None); // (error) 2241 } 2242 else 2243 { 2244 set_deferral(DeferType::Reminder); 2245 mDeferralTime = dateTime; 2246 checkRepetition = true; 2247 } 2248 mTriggerChanged = true; 2249 } 2250 else 2251 { 2252 // Deferring a recurring alarm 2253 mDeferralTime = dateTime; 2254 if (mDeferral == DeferType::None) 2255 set_deferral(DeferType::Normal); 2256 mTriggerChanged = true; 2257 checkReminderAfter = true; 2258 if (adjustRecurrence) 2259 { 2260 const KADateTime now = KADateTime::currentUtcDateTime(); 2261 if (mainEndRepeatTime() < now) 2262 { 2263 // The last repetition (if any) of the current recurrence has already passed. 2264 // Adjust to the next scheduled recurrence after now. 2265 if (!mMainExpired && setNextOccurrence(now) == KAEvent::OccurType::None) 2266 { 2267 mMainExpired = true; 2268 --mAlarmCount; 2269 } 2270 } 2271 else 2272 setNextRepetition = mRepetition; 2273 } 2274 else 2275 checkRepetition = true; 2276 } 2277 if (checkReminderAfter && mReminderMinutes < 0 && mReminderActive != ReminderType::None) 2278 { 2279 // Enable/disable the active reminder AFTER the main alarm, 2280 // depending on whether the deferral is before or after the reminder. 2281 mReminderActive = (mDeferralTime < mReminderAfterTime) ? ReminderType::Active : ReminderType::Hidden; 2282 } 2283 if (checkRepetition) 2284 setNextRepetition = (mRepetition && mDeferralTime < mainEndRepeatTime()); 2285 if (setNextRepetition) 2286 { 2287 // The alarm is repeated, and we're deferring to a time before the last repetition. 2288 // Set the next scheduled repetition to the one after the deferral. 2289 if (mNextMainDateTime >= mDeferralTime) 2290 mNextRepeat = 0; 2291 else 2292 mNextRepeat = mRepetition.nextRepeatCount(mNextMainDateTime.kDateTime(), mDeferralTime.kDateTime()); 2293 mTriggerChanged = true; 2294 } 2295 endChanges(); 2296 } 2297 2298 /****************************************************************************** 2299 * Cancel any deferral alarm. 2300 */ 2301 void KAEvent::cancelDefer() 2302 { 2303 d->cancelDefer(); 2304 } 2305 2306 void KAEventPrivate::cancelDefer() 2307 { 2308 if (mDeferral != DeferType::None) 2309 { 2310 mDeferralTime = DateTime(); 2311 set_deferral(DeferType::None); 2312 mTriggerChanged = true; 2313 } 2314 } 2315 2316 void KAEvent::setDeferDefaultMinutes(int minutes, bool dateOnly) 2317 { 2318 d->mDeferDefaultMinutes = minutes; 2319 d->mDeferDefaultDateOnly = dateOnly; 2320 } 2321 2322 bool KAEvent::deferred() const 2323 { 2324 return d->mDeferral != KAEventPrivate::DeferType::None; 2325 } 2326 2327 DateTime KAEvent::deferDateTime() const 2328 { 2329 return d->mDeferralTime; 2330 } 2331 2332 /****************************************************************************** 2333 * Find the latest time which the alarm can currently be deferred to. 2334 */ 2335 DateTime KAEvent::deferralLimit(DeferLimit* limitType) const 2336 { 2337 return d->deferralLimit(limitType); 2338 } 2339 2340 DateTime KAEventPrivate::deferralLimit(KAEvent::DeferLimit* limitType) const 2341 { 2342 KAEvent::DeferLimit ltype = KAEvent::DeferLimit::None; 2343 DateTime endTime; 2344 if (checkRecur() != KARecurrence::NO_RECUR) 2345 { 2346 // It's a recurring alarm. Find the latest time it can be deferred to: 2347 // it cannot be deferred past its next occurrence or sub-repetition, 2348 // or any advance reminder before that. 2349 DateTime reminderTime; 2350 const KADateTime now = KADateTime::currentUtcDateTime(); 2351 const KAEvent::OccurType type = nextOccurrence(now, endTime, KAEvent::Repeats::Return); 2352 if (type & KAEvent::OccurType::Repeat) 2353 ltype = KAEvent::DeferLimit::Repetition; 2354 else if (type == KAEvent::OccurType::None) 2355 ltype = KAEvent::DeferLimit::None; 2356 else if (mReminderActive == ReminderType::Active && mReminderMinutes > 0 2357 && (now < (reminderTime = endTime.addMins(-mReminderMinutes)))) 2358 { 2359 endTime = reminderTime; 2360 ltype = KAEvent::DeferLimit::Reminder; 2361 } 2362 else 2363 ltype = KAEvent::DeferLimit::Recurrence; 2364 } 2365 else if (mReminderMinutes < 0) 2366 { 2367 // There is a reminder alarm which occurs AFTER the main alarm. 2368 // Don't allow the reminder to be deferred past the next main alarm time. 2369 if (KADateTime::currentUtcDateTime() < mNextMainDateTime.effectiveKDateTime()) 2370 { 2371 endTime = mNextMainDateTime; 2372 ltype = KAEvent::DeferLimit::Main; 2373 } 2374 } 2375 else if (mReminderMinutes > 0 2376 && KADateTime::currentUtcDateTime() < mNextMainDateTime.effectiveKDateTime()) 2377 { 2378 // It's a reminder BEFORE the main alarm. 2379 // Don't allow it to be deferred past its main alarm time. 2380 endTime = mNextMainDateTime; 2381 ltype = KAEvent::DeferLimit::Main; 2382 } 2383 if (ltype != KAEvent::DeferLimit::None) 2384 endTime = endTime.addMins(-1); 2385 if (limitType) 2386 *limitType = ltype; 2387 return endTime; 2388 } 2389 2390 int KAEvent::deferDefaultMinutes() const 2391 { 2392 return d->mDeferDefaultMinutes; 2393 } 2394 2395 bool KAEvent::deferDefaultDateOnly() const 2396 { 2397 return d->mDeferDefaultDateOnly; 2398 } 2399 2400 DateTime KAEvent::startDateTime() const 2401 { 2402 return d->mStartDateTime; 2403 } 2404 2405 void KAEvent::setTime(const KADateTime& dt) 2406 { 2407 d->mNextMainDateTime = dt; 2408 d->mTriggerChanged = true; 2409 } 2410 2411 /****************************************************************************** 2412 */ 2413 DateTime KAEvent::nextDateTime(NextTypes type) const 2414 { 2415 return d->nextDateTime(type); 2416 } 2417 DateTime KAEventPrivate::nextDateTime(KAEvent::NextTypes type) const 2418 { 2419 // Remove flags from 'type' which are inapplicable to this alarm. 2420 if (!mRepetition) 2421 type &= ~KAEvent::NextRepeat; // the alarm doesn't repeat 2422 if (!mReminderMinutes) 2423 type &= ~KAEvent::NextReminder; // the alarm doesn't have a reminder 2424 if (checkRecur() == KARecurrence::NO_RECUR 2425 || (!mWorkTimeOnly && (!mExcludeHolidays || !mHolidays->isValid()))) 2426 type &= ~KAEvent::NextWorkHoliday; // this alarm isn't affected by work time/holidays 2427 2428 // Deferral time overrides anything else if NextDeferral is set. 2429 if (type & KAEvent::NextDeferral) 2430 { 2431 if (mDeferral == DeferType::Normal 2432 || (mDeferral == DeferType::Reminder && (type & KAEvent::NextReminder))) 2433 return mDeferralTime; 2434 } 2435 type &= ~KAEvent::NextDeferral; 2436 2437 // Find the next recurrence (not sub-repetition) after now. 2438 // If looking for reminders AFTER the alarm, start from now - reminder period. 2439 // If looking for repetitions, start from now - total repetition duration. 2440 const KADateTime now = KADateTime::currentUtcDateTime(); 2441 const int repDuration = mRepetition.duration().asSeconds(); 2442 int preAdjust = 0; 2443 if ((type & KAEvent::NextReminder) && mReminderMinutes < 0) // if reminder AFTER the alarm 2444 preAdjust = mReminderMinutes * 60; 2445 if (type & KAEvent::NextRepeat) 2446 { 2447 const int adjustment = -repDuration; 2448 if (adjustment < preAdjust) 2449 preAdjust = adjustment; 2450 } 2451 KADateTime pre = preAdjust ? now.addSecs(preAdjust) : now; 2452 DateTime nextRecur; 2453 if (nextOccurrence(pre, nextRecur, KAEvent::Repeats::Ignore) == KAEvent::OccurType::None) 2454 return DateTime(); 2455 const int offsetToRecur = now.secsTo(nextRecur.effectiveKDateTime()); 2456 2457 // If desired, check for the first sub-repetition after now. 2458 DateTime result; 2459 if (type & KAEvent::NextRepeat) 2460 { 2461 // If the recurrence is before now, find the first sub-repetition after now. 2462 if (offsetToRecur <= 0) 2463 { 2464 const int repInterval = mRepetition.intervalSeconds(); 2465 const int count = -offsetToRecur / repInterval + 1; // first sub-repetition AFTER now 2466 if (count <= mRepetition.count()) 2467 { 2468 const DateTime nextRep = nextRecur.addSecs(repInterval * count); 2469 if (!(type & KAEvent::NextWorkHoliday) 2470 || !excludedByWorkTimeOrHoliday(nextRep.effectiveKDateTime())) 2471 result = nextRep; 2472 } 2473 } 2474 } 2475 2476 bool checkedWorkHol = false; // whether nextRecur has been checked for working time/holiday 2477 bool excludeWorkHol = false; // whether nextRecur is excluded by working time/holiday 2478 2479 // If desired, check for the first reminder after now. 2480 DateTime resultReminder; 2481 if (type & KAEvent::NextReminder) 2482 { 2483 excludeWorkHol = excludedByWorkTimeOrHoliday(nextRecur.effectiveKDateTime()); 2484 checkedWorkHol = true; 2485 if (!excludeWorkHol) 2486 { 2487 // Reminders are never returned if the recurrence which they relate to 2488 // is excluded by working time/holiday restrictions, regardless of 2489 // whether or not NextWorkHoliday is specified. 2490 const int reminderSecs = mReminderMinutes * 60; 2491 if (mReminderMinutes > 0) 2492 { 2493 // Reminder is before the alarm. If the recurrence is after now, 2494 // check if its reminder occurs after now. 2495 if (offsetToRecur > 0 && offsetToRecur > reminderSecs) 2496 resultReminder = nextRecur.addSecs(-reminderSecs); 2497 } 2498 else 2499 { 2500 // Reminder is after the alarm. If the recurrence is before now, 2501 // check if its reminder occurs after now. 2502 if (offsetToRecur < 0 && -offsetToRecur < reminderSecs) 2503 resultReminder = nextRecur.addSecs(reminderSecs); 2504 } 2505 2506 if (resultReminder.isValid()) 2507 { 2508 // Find the earliest of the next sub-repetition or the reminder. 2509 if (!result.isValid() || resultReminder < result) 2510 result = resultReminder; 2511 } 2512 } 2513 } 2514 2515 if (result.isValid()) 2516 return result; // return the sub-repetition or reminder time 2517 if (type & KAEvent::NextWorkHoliday) 2518 { 2519 if (!checkedWorkHol) 2520 excludeWorkHol = excludedByWorkTimeOrHoliday(nextRecur.effectiveKDateTime()); 2521 if (excludeWorkHol) 2522 { 2523 // Find first recurrence/repetition which complies with working time/ 2524 // holiday restrictions. 2525 // The next recurrence found is excluded by work time or holidays. 2526 // Find a subsequent sub-repetition or recurrence which is not excluded. 2527 calcTriggerTimes(); 2528 return (type & KAEvent::NextReminder) ? mAllWorkTrigger : mMainWorkTrigger; 2529 } 2530 } 2531 return nextRecur; // return the recurrence, which must be after now 2532 } 2533 2534 DateTime KAEvent::mainDateTime(bool withRepeats) const 2535 { 2536 return d->mainDateTime(withRepeats); 2537 } 2538 2539 QTime KAEvent::mainTime() const 2540 { 2541 return d->mNextMainDateTime.effectiveTime(); 2542 } 2543 2544 DateTime KAEvent::mainEndRepeatTime() const 2545 { 2546 return d->mainEndRepeatTime(); 2547 } 2548 2549 /****************************************************************************** 2550 * Set the start-of-day time for date-only alarms. 2551 */ 2552 void KAEvent::setStartOfDay(const QTime& startOfDay) 2553 { 2554 DateTime::setStartOfDay(startOfDay); 2555 } 2556 2557 /****************************************************************************** 2558 * Called when the user changes the start-of-day time. 2559 * Adjust the start time of the recurrence to match, for each date-only event in 2560 * a list. 2561 */ 2562 void KAEvent::adjustStartOfDay(const KAEvent::List& events) 2563 { 2564 for (KAEvent* event : events) 2565 { 2566 KAEventPrivate* const p = event->d; 2567 if (p->mStartDateTime.isDateOnly() && p->checkRecur() != KARecurrence::NO_RECUR) 2568 p->mRecurrence->setStartDateTime(p->mStartDateTime.effectiveKDateTime(), true); 2569 } 2570 } 2571 2572 DateTime KAEvent::nextTrigger(Trigger type) const 2573 { 2574 if (d->mCategory == CalEvent::ARCHIVED || d->mCategory == CalEvent::TEMPLATE) 2575 return {}; // it's a template or archived 2576 if (d->mDeferral == KAEventPrivate::DeferType::Normal) 2577 return d->mDeferralTime; // for a deferred alarm, working time setting is ignored 2578 2579 d->calcTriggerTimes(); 2580 switch (type) 2581 { 2582 case Trigger::All: return d->mAllTrigger; 2583 case Trigger::Main: return d->mMainTrigger; 2584 case Trigger::AllWork: return d->mAllWorkTrigger; 2585 case Trigger::Work: return d->mMainWorkTrigger; 2586 case Trigger::Display: 2587 { 2588 const bool reminderAfter = d->mMainExpired && d->mReminderActive != KAEventPrivate::ReminderType::None && d->mReminderMinutes < 0; 2589 return d->checkRecur() != KARecurrence::NO_RECUR && (d->mWorkTimeOnly || d->mExcludeHolidays) 2590 ? (reminderAfter ? d->mAllWorkTrigger : d->mMainWorkTrigger) 2591 : (reminderAfter ? d->mAllTrigger : d->mMainTrigger); 2592 } 2593 default: return {}; 2594 } 2595 } 2596 2597 void KAEvent::setCreatedDateTime(const KADateTime& dt) 2598 { 2599 d->mCreatedDateTime = dt; 2600 } 2601 2602 KADateTime KAEvent::createdDateTime() const 2603 { 2604 return d->mCreatedDateTime; 2605 } 2606 2607 /****************************************************************************** 2608 * Set or clear repeat-at-login. 2609 */ 2610 void KAEvent::setRepeatAtLogin(bool rl) 2611 { 2612 d->setRepeatAtLogin(rl); 2613 } 2614 2615 void KAEventPrivate::setRepeatAtLogin(bool rl) 2616 { 2617 if (rl && !mRepeatAtLogin) 2618 { 2619 setRepeatAtLoginTrue(true); // clear incompatible statuses 2620 ++mAlarmCount; 2621 } 2622 else if (!rl && mRepeatAtLogin) 2623 --mAlarmCount; 2624 mRepeatAtLogin = rl; 2625 mTriggerChanged = true; 2626 } 2627 2628 /****************************************************************************** 2629 * Clear incompatible statuses when repeat-at-login is set. 2630 */ 2631 void KAEventPrivate::setRepeatAtLoginTrue(bool clearReminder) 2632 { 2633 clearRecur(); // clear recurrences 2634 if (mReminderMinutes >= 0 && clearReminder) 2635 setReminder(0, false); // clear pre-alarm reminder 2636 mLateCancel = 0; 2637 mAutoClose = false; 2638 mCopyToKOrganizer = false; 2639 } 2640 2641 bool KAEvent::repeatAtLogin(bool includeArchived) const 2642 { 2643 return d->mRepeatAtLogin || (includeArchived && d->mArchiveRepeatAtLogin); 2644 } 2645 2646 /****************************************************************************** 2647 * Enable or disable wake-from-suspend when the alarm is due. 2648 */ 2649 void KAEvent::setWakeFromSuspend(bool wake) 2650 { 2651 d->mWakeFromSuspend = wake; 2652 } 2653 2654 bool KAEvent::wakeFromSuspend() const 2655 { 2656 return d->mWakeFromSuspend; 2657 } 2658 2659 void KAEvent::setExcludeHolidays(bool ex) 2660 { 2661 d->mExcludeHolidays = ex; 2662 d->mExcludeHolidayRegion = KAEventPrivate::mHolidays->regionCode(); 2663 // Option only affects recurring alarms 2664 d->mTriggerChanged = (d->checkRecur() != KARecurrence::NO_RECUR); 2665 } 2666 2667 bool KAEvent::holidaysExcluded() const 2668 { 2669 return d->mExcludeHolidays; 2670 } 2671 2672 /****************************************************************************** 2673 * Set a new holiday region. 2674 * Alarms which exclude holidays record the pointer to the holiday definition 2675 * at the time their next trigger times were last calculated. The change in 2676 * holiday definition pointer will cause their next trigger times to be 2677 * recalculated. 2678 */ 2679 void KAEvent::setHolidays(const Holidays& h) 2680 { 2681 KAEventPrivate::mHolidays = &h; 2682 } 2683 2684 void KAEvent::setWorkTimeOnly(bool wto) 2685 { 2686 d->mWorkTimeOnly = wto; 2687 // Option only affects recurring alarms 2688 d->mTriggerChanged = (d->checkRecur() != KARecurrence::NO_RECUR); 2689 } 2690 2691 bool KAEvent::workTimeOnly() const 2692 { 2693 return d->mWorkTimeOnly; 2694 } 2695 2696 /****************************************************************************** 2697 * Check whether a date/time conflicts with working hours and/or holiday 2698 * restrictions for the alarm. 2699 */ 2700 bool KAEvent::excludedByWorkTimeOrHoliday(const KADateTime& dt) const 2701 { 2702 return d->excludedByWorkTimeOrHoliday(dt); 2703 } 2704 2705 bool KAEventPrivate::excludedByWorkTimeOrHoliday(const KADateTime& dt) const 2706 { 2707 if (mExcludeHolidays && mHolidays->isHoliday(dt.date())) 2708 return true; 2709 if (!mWorkTimeOnly) 2710 return false; 2711 const KADateTime wdt = dt.toTimeSpec(mWorkDayTimeSpec); 2712 if (!mWorkDays.testBit(wdt.date().dayOfWeek() - 1)) 2713 return true; 2714 return !wdt.isDateOnly() 2715 && (wdt.time() < mWorkDayStart || wdt.time() >= mWorkDayEnd); 2716 } 2717 2718 /****************************************************************************** 2719 * Set new working days and times. 2720 * Increment a counter so that working-time-only alarms can detect that they 2721 * need to update their next trigger time. 2722 */ 2723 void KAEvent::setWorkTime(const QBitArray& days, const QTime& start, const QTime& end, const KADateTime::Spec& timeSpec) 2724 { 2725 if (days != KAEventPrivate::mWorkDays || start != KAEventPrivate::mWorkDayStart || end != KAEventPrivate::mWorkDayEnd || timeSpec != KAEventPrivate::mWorkDayTimeSpec) 2726 { 2727 KAEventPrivate::mWorkDays = days; 2728 KAEventPrivate::mWorkDayStart = start; 2729 KAEventPrivate::mWorkDayEnd = end; 2730 KAEventPrivate::mWorkDayTimeSpec = timeSpec; 2731 if (!++KAEventPrivate::mWorkTimeIndex) 2732 ++KAEventPrivate::mWorkTimeIndex; 2733 } 2734 } 2735 2736 /****************************************************************************** 2737 * Clear the event's recurrence and alarm repetition data. 2738 */ 2739 void KAEvent::setNoRecur() 2740 { 2741 d->clearRecur(); 2742 } 2743 2744 void KAEventPrivate::clearRecur() 2745 { 2746 if (mRecurrence || mRepetition) 2747 { 2748 delete mRecurrence; 2749 mRecurrence = nullptr; 2750 mRepetition.set(0, 0); 2751 mTriggerChanged = true; 2752 } 2753 mNextRepeat = 0; 2754 } 2755 2756 /****************************************************************************** 2757 * Initialise the event's recurrence from a KCalendarCore::Recurrence. 2758 * The event's start date/time is not changed. 2759 */ 2760 void KAEvent::setRecurrence(const KARecurrence& recurrence) 2761 { 2762 d->setRecurrence(recurrence); 2763 } 2764 2765 void KAEventPrivate::setRecurrence(const KARecurrence& recurrence) 2766 { 2767 startChanges(); // prevent multiple trigger time evaluation here 2768 if (recurrence.recurs()) 2769 { 2770 delete mRecurrence; 2771 mRecurrence = new KARecurrence(recurrence); 2772 mRecurrence->setStartDateTime(mStartDateTime.effectiveKDateTime(), mStartDateTime.isDateOnly()); 2773 mTriggerChanged = true; 2774 2775 // Adjust sub-repetition values to fit the recurrence. 2776 setRepetition(mRepetition); 2777 } 2778 else 2779 clearRecur(); 2780 2781 endChanges(); 2782 } 2783 2784 /****************************************************************************** 2785 * Set the recurrence to recur at a minutes interval. 2786 * Parameters: 2787 * freq = how many minutes between recurrences. 2788 * count = number of occurrences, including first and last. 2789 * = -1 to recur indefinitely. 2790 * = 0 to use 'end' instead. 2791 * end = end date/time (invalid to use 'count' instead). 2792 * Reply = false if no recurrence was set up. 2793 */ 2794 bool KAEvent::setRecurMinutely(int freq, int count, const KADateTime& end) 2795 { 2796 const bool success = d->setRecur(RecurrenceRule::rMinutely, freq, count, end); 2797 d->mTriggerChanged = true; 2798 return success; 2799 } 2800 2801 /****************************************************************************** 2802 * Set the recurrence to recur daily. 2803 * Parameters: 2804 * freq = how many days between recurrences. 2805 * days = which days of the week alarms are allowed to occur on. 2806 * count = number of occurrences, including first and last. 2807 * = -1 to recur indefinitely. 2808 * = 0 to use 'end' instead. 2809 * end = end date (invalid to use 'count' instead). 2810 * Reply = false if no recurrence was set up. 2811 */ 2812 bool KAEvent::setRecurDaily(int freq, const QBitArray& days, int count, QDate end) 2813 { 2814 const bool success = d->setRecur(RecurrenceRule::rDaily, freq, count, end); 2815 if (success) 2816 { 2817 if (days.size() != 7) 2818 qCWarning(KALARMCAL_LOG) << "KAEvent::setRecurDaily: Error! 'days' parameter must have 7 elements: actual size" << days.size(); 2819 else 2820 { 2821 int n = days.count(true); // number of days when alarms occur 2822 if (n < 7) 2823 d->mRecurrence->addWeeklyDays(days); 2824 } 2825 } 2826 d->mTriggerChanged = true; 2827 return success; 2828 } 2829 2830 /****************************************************************************** 2831 * Set the recurrence to recur weekly, on the specified weekdays. 2832 * Parameters: 2833 * freq = how many weeks between recurrences. 2834 * days = which days of the week alarms should occur on. 2835 * count = number of occurrences, including first and last. 2836 * = -1 to recur indefinitely. 2837 * = 0 to use 'end' instead. 2838 * end = end date (invalid to use 'count' instead). 2839 * Reply = false if no recurrence was set up. 2840 */ 2841 bool KAEvent::setRecurWeekly(int freq, const QBitArray& days, int count, QDate end) 2842 { 2843 const bool success = d->setRecur(RecurrenceRule::rWeekly, freq, count, end); 2844 if (success) 2845 d->mRecurrence->addWeeklyDays(days); 2846 d->mTriggerChanged = true; 2847 return success; 2848 } 2849 2850 /****************************************************************************** 2851 * Set the recurrence to recur monthly, on the specified days within the month. 2852 * Parameters: 2853 * freq = how many months between recurrences. 2854 * days = which days of the month alarms should occur on. 2855 * count = number of occurrences, including first and last. 2856 * = -1 to recur indefinitely. 2857 * = 0 to use 'end' instead. 2858 * end = end date (invalid to use 'count' instead). 2859 * Reply = false if no recurrence was set up. 2860 */ 2861 bool KAEvent::setRecurMonthlyByDate(int freq, const QList<int>& days, int count, QDate end) 2862 { 2863 const bool success = d->setRecur(RecurrenceRule::rMonthly, freq, count, end); 2864 if (success) 2865 { 2866 for (int day : days) 2867 d->mRecurrence->addMonthlyDate(day); 2868 } 2869 d->mTriggerChanged = true; 2870 return success; 2871 } 2872 2873 /****************************************************************************** 2874 * Set the recurrence to recur monthly, on the specified weekdays in the 2875 * specified weeks of the month. 2876 * Parameters: 2877 * freq = how many months between recurrences. 2878 * posns = which days of the week/weeks of the month alarms should occur on. 2879 * count = number of occurrences, including first and last. 2880 * = -1 to recur indefinitely. 2881 * = 0 to use 'end' instead. 2882 * end = end date (invalid to use 'count' instead). 2883 * Reply = false if no recurrence was set up. 2884 */ 2885 bool KAEvent::setRecurMonthlyByPos(int freq, const QList<MonthPos>& posns, int count, QDate end) 2886 { 2887 const bool success = d->setRecur(RecurrenceRule::rMonthly, freq, count, end); 2888 if (success) 2889 { 2890 for (const MonthPos& posn : posns) 2891 d->mRecurrence->addMonthlyPos(posn.weeknum, posn.days); 2892 } 2893 d->mTriggerChanged = true; 2894 return success; 2895 } 2896 2897 /****************************************************************************** 2898 * Set the recurrence to recur annually, on the specified start date in each 2899 * of the specified months. 2900 * Parameters: 2901 * freq = how many years between recurrences. 2902 * months = which months of the year alarms should occur on. 2903 * day = day of month, or 0 to use start date 2904 * feb29 = when February 29th should recur in non-leap years. 2905 * count = number of occurrences, including first and last. 2906 * = -1 to recur indefinitely. 2907 * = 0 to use 'end' instead. 2908 * end = end date (invalid to use 'count' instead). 2909 * Reply = false if no recurrence was set up. 2910 */ 2911 bool KAEvent::setRecurAnnualByDate(int freq, const QList<int>& months, int day, 2912 KARecurrence::Feb29Type feb29, int count, QDate end) 2913 { 2914 const bool success = d->setRecur(RecurrenceRule::rYearly, freq, count, end, feb29); 2915 if (success) 2916 { 2917 for (int month : months) 2918 d->mRecurrence->addYearlyMonth(month); 2919 if (day) 2920 d->mRecurrence->addMonthlyDate(day); 2921 } 2922 d->mTriggerChanged = true; 2923 return success; 2924 } 2925 2926 /****************************************************************************** 2927 * Set the recurrence to recur annually, on the specified weekdays in the 2928 * specified weeks of the specified months. 2929 * Parameters: 2930 * freq = how many years between recurrences. 2931 * posns = which days of the week/weeks of the month alarms should occur on. 2932 * months = which months of the year alarms should occur on. 2933 * count = number of occurrences, including first and last. 2934 * = -1 to recur indefinitely. 2935 * = 0 to use 'end' instead. 2936 * end = end date (invalid to use 'count' instead). 2937 * Reply = false if no recurrence was set up. 2938 */ 2939 bool KAEvent::setRecurAnnualByPos(int freq, const QList<MonthPos>& posns, 2940 const QList<int>& months, int count, QDate end) 2941 { 2942 const bool success = d->setRecur(RecurrenceRule::rYearly, freq, count, end); 2943 if (success) 2944 { 2945 for (int month : months) 2946 d->mRecurrence->addYearlyMonth(month); 2947 for (const MonthPos& posn : posns) 2948 d->mRecurrence->addYearlyPos(posn.weeknum, posn.days); 2949 } 2950 d->mTriggerChanged = true; 2951 return success; 2952 } 2953 2954 /****************************************************************************** 2955 * Initialise the event's recurrence data. 2956 * Parameters: 2957 * freq = how many intervals between recurrences. 2958 * count = number of occurrences, including first and last. 2959 * = -1 to recur indefinitely. 2960 * = 0 to use 'end' instead. 2961 * end = end date/time (invalid to use 'count' instead). 2962 * Reply = false if no recurrence was set up. 2963 */ 2964 bool KAEventPrivate::setRecur(KCalendarCore::RecurrenceRule::PeriodType recurType, int freq, int count, QDate end, KARecurrence::Feb29Type feb29) 2965 { 2966 KADateTime edt = mNextMainDateTime.kDateTime(); 2967 edt.setDate(end); 2968 return setRecur(recurType, freq, count, edt, feb29); 2969 } 2970 bool KAEventPrivate::setRecur(KCalendarCore::RecurrenceRule::PeriodType recurType, int freq, int count, const KADateTime& end, KARecurrence::Feb29Type feb29) 2971 { 2972 if (count >= -1 && (count || end.date().isValid())) 2973 { 2974 if (!mRecurrence) 2975 mRecurrence = new KARecurrence; 2976 if (mRecurrence->init(recurType, freq, count, mNextMainDateTime.kDateTime(), end, feb29)) 2977 return true; 2978 } 2979 clearRecur(); 2980 return false; 2981 } 2982 2983 bool KAEvent::recurs() const 2984 { 2985 return d->checkRecur() != KARecurrence::NO_RECUR; 2986 } 2987 2988 KARecurrence::Type KAEvent::recurType() const 2989 { 2990 return d->checkRecur(); 2991 } 2992 2993 KARecurrence* KAEvent::recurrence() const 2994 { 2995 return d->mRecurrence; 2996 } 2997 2998 /****************************************************************************** 2999 * Return the recurrence interval in units of the recurrence period type. 3000 */ 3001 int KAEvent::recurInterval() const 3002 { 3003 if (d->mRecurrence) 3004 { 3005 switch (d->mRecurrence->type()) 3006 { 3007 case KARecurrence::MINUTELY: 3008 case KARecurrence::DAILY: 3009 case KARecurrence::WEEKLY: 3010 case KARecurrence::MONTHLY_DAY: 3011 case KARecurrence::MONTHLY_POS: 3012 case KARecurrence::ANNUAL_DATE: 3013 case KARecurrence::ANNUAL_POS: 3014 return d->mRecurrence->frequency(); 3015 default: 3016 break; 3017 } 3018 } 3019 return 0; 3020 } 3021 3022 Duration KAEvent::longestRecurrenceInterval() const 3023 { 3024 return d->mRecurrence ? d->mRecurrence->longestInterval() : Duration(0); 3025 } 3026 3027 /****************************************************************************** 3028 * Adjust the event date/time to the first recurrence of the event, on or after 3029 * start date/time. The event start date may not be a recurrence date, in which 3030 * case a later date will be set. 3031 */ 3032 void KAEvent::setFirstRecurrence() 3033 { 3034 d->setFirstRecurrence(); 3035 } 3036 3037 void KAEventPrivate::setFirstRecurrence() 3038 { 3039 switch (checkRecur()) 3040 { 3041 case KARecurrence::NO_RECUR: 3042 case KARecurrence::MINUTELY: 3043 return; 3044 case KARecurrence::ANNUAL_DATE: 3045 case KARecurrence::ANNUAL_POS: 3046 if (mRecurrence->yearMonths().isEmpty()) 3047 return; // (presumably it's a template) 3048 break; 3049 case KARecurrence::DAILY: 3050 case KARecurrence::WEEKLY: 3051 case KARecurrence::MONTHLY_POS: 3052 case KARecurrence::MONTHLY_DAY: 3053 break; 3054 } 3055 const KADateTime recurStart = mRecurrence->startDateTime(); 3056 if (mRecurrence->recursOn(recurStart.date(), recurStart.timeSpec())) 3057 return; // it already recurs on the start date 3058 3059 // Set the frequency to 1 to find the first possible occurrence 3060 const int frequency = mRecurrence->frequency(); 3061 mRecurrence->setFrequency(1); 3062 DateTime next; 3063 nextRecurrence(mNextMainDateTime.effectiveKDateTime(), next); 3064 if (!next.isValid()) 3065 mRecurrence->setStartDateTime(recurStart, mStartDateTime.isDateOnly()); // reinstate the old value 3066 else 3067 { 3068 mRecurrence->setStartDateTime(next.effectiveKDateTime(), next.isDateOnly()); 3069 mStartDateTime = mNextMainDateTime = next; 3070 mTriggerChanged = true; 3071 } 3072 mRecurrence->setFrequency(frequency); // restore the frequency 3073 } 3074 3075 /****************************************************************************** 3076 * Return the recurrence interval as text suitable for display. 3077 */ 3078 QString KAEvent::recurrenceText(bool brief) const 3079 { 3080 if (d->mRepeatAtLogin) 3081 return brief ? i18nc("@info Brief form of 'At Login'", "Login") : i18nc("@info", "At login"); 3082 if (d->mRecurrence) 3083 { 3084 QLocale locale; 3085 const int frequency = d->mRecurrence->frequency(); 3086 switch (d->mRecurrence->defaultRRuleConst()->recurrenceType()) 3087 { 3088 case RecurrenceRule::rMinutely: 3089 if (frequency < 60) 3090 return i18ncp("@info", "1 Minute", "%1 Minutes", frequency); 3091 else if (frequency % 60 == 0) 3092 return i18ncp("@info", "1 Hour", "%1 Hours", frequency / 60); 3093 else 3094 return i18nc("@info Hours and minutes", "%1h %2m", locale.toString(frequency / 60), 3095 locale.toString(frequency % 60)); 3096 case RecurrenceRule::rDaily: 3097 return i18ncp("@info", "1 Day", "%1 Days", frequency); 3098 case RecurrenceRule::rWeekly: 3099 return i18ncp("@info", "1 Week", "%1 Weeks", frequency); 3100 case RecurrenceRule::rMonthly: 3101 return i18ncp("@info", "1 Month", "%1 Months", frequency); 3102 case RecurrenceRule::rYearly: 3103 return i18ncp("@info", "1 Year", "%1 Years", frequency); 3104 case RecurrenceRule::rNone: 3105 default: 3106 break; 3107 } 3108 } 3109 return brief ? QString() : i18nc("@info No recurrence", "None"); 3110 } 3111 3112 /****************************************************************************** 3113 * Initialise the event's sub-repetition. 3114 * The repetition length is adjusted if necessary to fit the recurrence interval. 3115 * If the event doesn't recur, the sub-repetition is cleared. 3116 * Reply = false if a non-daily interval was specified for a date-only recurrence. 3117 */ 3118 bool KAEvent::setRepetition(const Repetition& r) 3119 { 3120 return d->setRepetition(r); 3121 } 3122 3123 bool KAEventPrivate::setRepetition(const Repetition& repetition) 3124 { 3125 // Don't set mRepetition to zero at the start of this function, in case the 3126 // 'repetition' parameter passed in is a reference to mRepetition. 3127 mNextRepeat = 0; 3128 if (repetition && !mRepeatAtLogin) 3129 { 3130 Q_ASSERT(checkRecur() != KARecurrence::NO_RECUR); 3131 if (!repetition.isDaily() && mStartDateTime.isDateOnly()) 3132 { 3133 mRepetition.set(0, 0); 3134 return false; // interval must be in units of days for date-only alarms 3135 } 3136 Duration longestInterval = mRecurrence->longestInterval(); 3137 if (repetition.duration() >= longestInterval) 3138 { 3139 const int count = mStartDateTime.isDateOnly() 3140 ? (longestInterval.asDays() - 1) / repetition.intervalDays() 3141 : (longestInterval.asSeconds() - 1) / repetition.intervalSeconds(); 3142 mRepetition.set(repetition.interval(), count); 3143 } 3144 else 3145 mRepetition = repetition; 3146 mTriggerChanged = true; 3147 } 3148 else if (mRepetition) 3149 { 3150 mRepetition.set(0, 0); 3151 mTriggerChanged = true; 3152 } 3153 return true; 3154 } 3155 3156 Repetition KAEvent::repetition() const 3157 { 3158 return d->mRepetition; 3159 } 3160 3161 int KAEvent::nextRepetition() const 3162 { 3163 return d->mNextRepeat; 3164 } 3165 3166 /****************************************************************************** 3167 * Return the repetition interval as text suitable for display. 3168 */ 3169 QString KAEvent::repetitionText(bool brief) const 3170 { 3171 if (d->mRepetition) 3172 { 3173 if (!d->mRepetition.isDaily()) 3174 { 3175 const int minutes = d->mRepetition.intervalMinutes(); 3176 if (minutes < 60) 3177 return i18ncp("@info", "1 Minute", "%1 Minutes", minutes); 3178 if (minutes % 60 == 0) 3179 return i18ncp("@info", "1 Hour", "%1 Hours", minutes / 60); 3180 return i18nc("@info Hours and minutes", "%1h %2m", minutes / 60, QString::asprintf("%02d", minutes % 60)); 3181 } 3182 const int days = d->mRepetition.intervalDays(); 3183 if (days % 7) 3184 return i18ncp("@info", "1 Day", "%1 Days", days); 3185 return i18ncp("@info", "1 Week", "%1 Weeks", days / 7); 3186 } 3187 return brief ? QString() : i18nc("@info No repetition", "None"); 3188 } 3189 3190 /****************************************************************************** 3191 * Determine whether the event will occur after the specified date/time. 3192 * If 'includeRepetitions' is true and the alarm has a sub-repetition, it 3193 * returns true if any repetitions occur after the specified date/time. 3194 */ 3195 bool KAEvent::occursAfter(const KADateTime& preDateTime, bool includeRepetitions) const 3196 { 3197 return d->occursAfter(preDateTime, includeRepetitions); 3198 } 3199 3200 bool KAEventPrivate::occursAfter(const KADateTime& preDateTime, bool includeRepetitions) const 3201 { 3202 KADateTime dt; 3203 if (checkRecur() != KARecurrence::NO_RECUR) 3204 { 3205 if (mRecurrence->duration() < 0) 3206 return true; // infinite recurrence 3207 dt = mRecurrence->endDateTime(); 3208 } 3209 else 3210 dt = mNextMainDateTime.effectiveKDateTime(); 3211 if (mStartDateTime.isDateOnly()) 3212 { 3213 QDate pre = preDateTime.date(); 3214 if (preDateTime.toTimeSpec(mStartDateTime.timeSpec()).time() < DateTime::startOfDay()) 3215 pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come 3216 if (pre < dt.date()) 3217 return true; 3218 } 3219 else if (preDateTime < dt) 3220 return true; 3221 3222 if (includeRepetitions && mRepetition) 3223 { 3224 if (preDateTime < KADateTime(mRepetition.duration().end(dt.qDateTime()))) 3225 return true; 3226 } 3227 return false; 3228 } 3229 3230 /****************************************************************************** 3231 * Set the date/time of the event to the next scheduled occurrence after the 3232 * specified date/time, provided that this is later than its current date/time. 3233 * Any reminder alarm is adjusted accordingly. 3234 * If the alarm has a sub-repetition, and a repetition of a previous recurrence 3235 * occurs after the specified date/time, that repetition is set as the next 3236 * occurrence. 3237 */ 3238 KAEvent::OccurType KAEvent::setNextOccurrence(const KADateTime& preDateTime) 3239 { 3240 return d->setNextOccurrence(preDateTime); 3241 } 3242 3243 KAEvent::OccurType KAEventPrivate::setNextOccurrence(const KADateTime& preDateTime) 3244 { 3245 if (preDateTime < mNextMainDateTime.effectiveKDateTime()) 3246 return KAEvent::OccurType::FirstOrOnly; // it might not be the first recurrence - tant pis 3247 KADateTime pre = preDateTime; 3248 // If there are repetitions, adjust the comparison date/time so that 3249 // we find the earliest recurrence which has a repetition falling after 3250 // the specified preDateTime. 3251 if (mRepetition) 3252 pre = KADateTime(mRepetition.duration(-mRepetition.count()).end(preDateTime.qDateTime())); 3253 3254 DateTime afterPre; // next recurrence after 'pre' 3255 KAEvent::OccurType type; 3256 if (pre < mNextMainDateTime.effectiveKDateTime()) 3257 { 3258 afterPre = mNextMainDateTime; 3259 type = KAEvent::OccurType::FirstOrOnly; // may not actually be the first occurrence 3260 } 3261 else if (checkRecur() != KARecurrence::NO_RECUR) 3262 { 3263 type = nextRecurrence(pre, afterPre); 3264 if (type == KAEvent::OccurType::None) 3265 return KAEvent::OccurType::None; 3266 if (type != KAEvent::OccurType::FirstOrOnly && afterPre != mNextMainDateTime) 3267 { 3268 // Need to reschedule the next trigger date/time 3269 mNextMainDateTime = afterPre; 3270 if (mReminderMinutes > 0 && (mDeferral == DeferType::Reminder || mReminderActive != ReminderType::Active)) 3271 { 3272 // Reinstate the advance reminder for the rescheduled recurrence. 3273 // Note that a reminder AFTER the main alarm will be left active. 3274 activate_reminder(!mReminderOnceOnly); 3275 } 3276 if (mDeferral == DeferType::Reminder) 3277 set_deferral(DeferType::None); 3278 mTriggerChanged = true; 3279 } 3280 } 3281 else 3282 return KAEvent::OccurType::None; 3283 3284 if (mRepetition) 3285 { 3286 if (afterPre <= preDateTime) 3287 { 3288 // The next occurrence is a sub-repetition. 3289 type = type | KAEvent::OccurType::Repeat; 3290 mNextRepeat = mRepetition.nextRepeatCount(afterPre.effectiveKDateTime(), preDateTime); 3291 // Repetitions can't have a reminder, so remove any. 3292 activate_reminder(false); 3293 if (mDeferral == DeferType::Reminder) 3294 set_deferral(DeferType::None); 3295 mTriggerChanged = true; 3296 } 3297 else if (mNextRepeat) 3298 { 3299 // The next occurrence is the main occurrence, not a repetition 3300 mNextRepeat = 0; 3301 mTriggerChanged = true; 3302 } 3303 } 3304 return type; 3305 } 3306 3307 /****************************************************************************** 3308 * Get the date/time of the next occurrence of the event, after the specified 3309 * date/time. 3310 * 'result' = date/time of next occurrence, or invalid date/time if none. 3311 */ 3312 KAEvent::OccurType KAEvent::nextOccurrence(const KADateTime& preDateTime, DateTime& result, Repeats o) const 3313 { 3314 return d->nextOccurrence(preDateTime, result, o); 3315 } 3316 3317 KAEvent::OccurType KAEventPrivate::nextOccurrence(const KADateTime& preDateTime, DateTime& result, 3318 KAEvent::Repeats includeRepetitions) const 3319 { 3320 KADateTime pre = preDateTime; 3321 if (includeRepetitions != KAEvent::Repeats::Ignore) 3322 { 3323 // Repeats::Return or Repeats::RecurBefore 3324 if (!mRepetition) 3325 includeRepetitions = KAEvent::Repeats::Ignore; 3326 else 3327 pre = KADateTime(mRepetition.duration(-mRepetition.count()).end(preDateTime.qDateTime())); 3328 } 3329 3330 KAEvent::OccurType type; 3331 const bool recurs = (checkRecur() != KARecurrence::NO_RECUR); 3332 if (recurs) 3333 type = nextRecurrence(pre, result); 3334 else if (pre < mNextMainDateTime.effectiveKDateTime()) 3335 { 3336 result = mNextMainDateTime; 3337 type = KAEvent::OccurType::FirstOrOnly; 3338 } 3339 else 3340 { 3341 result = DateTime(); 3342 type = KAEvent::OccurType::None; 3343 } 3344 3345 if (type != KAEvent::OccurType::None && result <= preDateTime && includeRepetitions != KAEvent::Repeats::Ignore) 3346 { 3347 // Repeats::Return or Repeats::RecurBefore 3348 // The next occurrence is a sub-repetition 3349 int repetition = mRepetition.nextRepeatCount(result.kDateTime(), preDateTime); 3350 const DateTime repeatDT(mRepetition.duration(repetition).end(result.qDateTime())); 3351 if (recurs) 3352 { 3353 // We've found a recurrence before the specified date/time, which has 3354 // a sub-repetition after the date/time. 3355 // However, if the intervals between recurrences vary, we could possibly 3356 // have missed a later recurrence which fits the criterion, so check again. 3357 DateTime dt; 3358 const KAEvent::OccurType newType = previousOccurrence(repeatDT.effectiveKDateTime(), dt, false); 3359 if (dt > result) 3360 { 3361 type = newType; 3362 result = dt; 3363 if (includeRepetitions == KAEvent::Repeats::Return && result <= preDateTime) 3364 { 3365 // The next occurrence is a sub-repetition 3366 repetition = mRepetition.nextRepeatCount(result.kDateTime(), preDateTime); 3367 result = DateTime(mRepetition.duration(repetition).end(result.qDateTime())); 3368 type = type | KAEvent::OccurType::Repeat; 3369 } 3370 return type; 3371 } 3372 } 3373 if (includeRepetitions == KAEvent::Repeats::Return) 3374 { 3375 // The next occurrence is a sub-repetition 3376 result = repeatDT; 3377 type = type | KAEvent::OccurType::Repeat; 3378 } 3379 } 3380 return type; 3381 } 3382 3383 /****************************************************************************** 3384 * Get the date/time of the last previous occurrence of the event, before the 3385 * specified date/time. 3386 * If 'includeRepetitions' is true and the alarm has a sub-repetition, the 3387 * last previous repetition is returned if appropriate. 3388 * 'result' = date/time of previous occurrence, or invalid date/time if none. 3389 */ 3390 KAEvent::OccurType KAEvent::previousOccurrence(const KADateTime& afterDateTime, DateTime& result, bool includeRepetitions) const 3391 { 3392 return d->previousOccurrence(afterDateTime, result, includeRepetitions); 3393 } 3394 3395 KAEvent::OccurType KAEventPrivate::previousOccurrence(const KADateTime& afterDateTime, DateTime& result, 3396 bool includeRepetitions) const 3397 { 3398 Q_ASSERT(!afterDateTime.isDateOnly()); 3399 if (mStartDateTime >= afterDateTime) 3400 { 3401 result = KADateTime(); 3402 return KAEvent::OccurType::None; // the event starts after the specified date/time 3403 } 3404 3405 // Find the latest recurrence of the event 3406 KAEvent::OccurType type; 3407 if (checkRecur() == KARecurrence::NO_RECUR) 3408 { 3409 result = mStartDateTime; 3410 type = KAEvent::OccurType::FirstOrOnly; 3411 } 3412 else 3413 { 3414 const KADateTime recurStart = mRecurrence->startDateTime(); 3415 KADateTime after = afterDateTime.toTimeSpec(mStartDateTime.timeSpec()); 3416 if (mStartDateTime.isDateOnly() && afterDateTime.time() > DateTime::startOfDay()) 3417 after = after.addDays(1); // today's recurrence (if today recurs) has passed 3418 const KADateTime dt = mRecurrence->getPreviousDateTime(after); 3419 result = dt; 3420 result.setDateOnly(mStartDateTime.isDateOnly()); 3421 if (!dt.isValid()) 3422 return KAEvent::OccurType::None; 3423 if (dt == recurStart) 3424 type = KAEvent::OccurType::FirstOrOnly; 3425 else if (mRecurrence->getNextDateTime(dt).isValid()) 3426 type = result.isDateOnly() ? KAEvent::OccurType::RecurDate : KAEvent::OccurType::RecurDateTime; 3427 else 3428 type = KAEvent::OccurType::LastRecur; 3429 } 3430 3431 if (includeRepetitions && mRepetition) 3432 { 3433 // Find the latest repetition which is before the specified time. 3434 const int repetition = mRepetition.previousRepeatCount(result.effectiveKDateTime(), afterDateTime); 3435 if (repetition > 0) 3436 { 3437 result = DateTime(mRepetition.duration(qMin(repetition, mRepetition.count())).end(result.qDateTime())); 3438 return type | KAEvent::OccurType::Repeat; 3439 } 3440 } 3441 return type; 3442 } 3443 3444 /****************************************************************************** 3445 * Set the event to be a copy of the specified event, making the specified 3446 * alarm the 'displaying' alarm. 3447 * The purpose of setting up a 'displaying' alarm is to be able to reinstate 3448 * the alarm message in case of a crash, or to reinstate it should the user 3449 * choose to defer the alarm. Note that even repeat-at-login alarms need to be 3450 * saved in case their end time expires before the next login. 3451 * Reply = true if successful, false if alarm was not copied. 3452 */ 3453 bool KAEvent::setDisplaying(const KAEvent& e, KAAlarm::Type t, ResourceId id, const KADateTime& dt, bool showEdit, bool showDefer) 3454 { 3455 return d->setDisplaying(*e.d, t, id, dt, showEdit, showDefer); 3456 } 3457 3458 bool KAEventPrivate::setDisplaying(const KAEventPrivate& event, KAAlarm::Type alarmType, ResourceId resourceId, 3459 const KADateTime& repeatAtLoginTime, bool showEdit, bool showDefer) 3460 { 3461 if (!mDisplaying 3462 && (alarmType == KAAlarm::Type::Main 3463 || alarmType == KAAlarm::Type::Reminder 3464 || alarmType == KAAlarm::Type::DeferredReminder 3465 || alarmType == KAAlarm::Type::Deferred 3466 || alarmType == KAAlarm::Type::AtLogin)) 3467 { 3468 //qCDebug(KALARMCAL_LOG)<<event.id()<<","<<(alarmType==KAAlarm::Type::Main?"MAIN":alarmType==KAAlarm::Type::Reminder?"REMINDER":alarmType==KAAlarm::Type::DeferredReminder?"DeferType::Reminder":alarmType==KAAlarm::Type::Deferred?"DEFERRAL":"LOGIN")<<"): time="<<repeatAtLoginTime.toString(); 3469 KAAlarm al = event.alarm(alarmType); 3470 if (al.isValid()) 3471 { 3472 *this = event; 3473 // Change the event ID to avoid duplicating the same unique ID as the original event 3474 setCategory(CalEvent::DISPLAYING); 3475 mResourceId = resourceId; // original resource ID which contained the event 3476 mDisplayingDefer = showDefer; 3477 mDisplayingEdit = showEdit; 3478 mDisplaying = true; 3479 mDisplayingTime = (alarmType == KAAlarm::Type::AtLogin) ? repeatAtLoginTime : al.dateTime().kDateTime(); 3480 switch (al.type()) 3481 { 3482 case KAAlarm::Type::AtLogin: mDisplayingFlags = KAEvent::REPEAT_AT_LOGIN; break; 3483 case KAAlarm::Type::Reminder: mDisplayingFlags = REMINDER; break; 3484 case KAAlarm::Type::DeferredReminder: mDisplayingFlags = al.timedDeferral() ? (REMINDER | TIME_DEFERRAL) : (REMINDER | DATE_DEFERRAL); break; 3485 case KAAlarm::Type::Deferred: mDisplayingFlags = al.timedDeferral() ? TIME_DEFERRAL : DATE_DEFERRAL; break; 3486 default: mDisplayingFlags = 0; break; 3487 } 3488 ++mAlarmCount; 3489 return true; 3490 } 3491 } 3492 return false; 3493 } 3494 3495 /****************************************************************************** 3496 * Reinstate the original event from the 'displaying' event. 3497 */ 3498 void KAEvent::reinstateFromDisplaying(const KCalendarCore::Event::Ptr& e, ResourceId& id, bool& showEdit, bool& showDefer) 3499 { 3500 d->reinstateFromDisplaying(e, id, showEdit, showDefer); 3501 } 3502 3503 void KAEventPrivate::reinstateFromDisplaying(const Event::Ptr& kcalEvent, ResourceId& resourceId, bool& showEdit, bool& showDefer) 3504 { 3505 *this = KAEventPrivate(kcalEvent); 3506 if (mDisplaying) 3507 { 3508 // Retrieve the original event's unique ID 3509 setCategory(CalEvent::ACTIVE); 3510 resourceId = mResourceId; 3511 mResourceId = -1; 3512 showDefer = mDisplayingDefer; 3513 showEdit = mDisplayingEdit; 3514 mDisplaying = false; 3515 --mAlarmCount; 3516 } 3517 } 3518 3519 /****************************************************************************** 3520 * Return the original alarm which the displaying alarm refers to. 3521 * Note that the caller is responsible for ensuring that the event was a 3522 * displaying event, since this is normally called after 3523 * reinstateFromDisplaying(), which clears mDisplaying. 3524 */ 3525 KAAlarm KAEvent::convertDisplayingAlarm() const 3526 { 3527 KAAlarm al = alarm(KAAlarm::Type::Displaying); 3528 KAAlarm::Private* const al_d = al.d; 3529 const int displayingFlags = d->mDisplayingFlags; 3530 if (displayingFlags & REPEAT_AT_LOGIN) 3531 { 3532 al_d->mRepeatAtLogin = true; 3533 al_d->mType = KAAlarm::Type::AtLogin; 3534 } 3535 else if (displayingFlags & KAEventPrivate::DEFERRAL) 3536 { 3537 al_d->mDeferred = true; 3538 al_d->mTimedDeferral = (displayingFlags & KAEventPrivate::TIMED_FLAG); 3539 al_d->mType = (displayingFlags & KAEventPrivate::REMINDER) ? KAAlarm::Type::DeferredReminder : KAAlarm::Type::Deferred; 3540 } 3541 else if (displayingFlags & KAEventPrivate::REMINDER) 3542 al_d->mType = KAAlarm::Type::Reminder; 3543 else 3544 al_d->mType = KAAlarm::Type::Main; 3545 return al; 3546 } 3547 3548 bool KAEvent::displaying() const 3549 { 3550 return d->mDisplaying; 3551 } 3552 3553 /****************************************************************************** 3554 * Return the alarm of the specified type. 3555 */ 3556 KAAlarm KAEvent::alarm(KAAlarm::Type t) const 3557 { 3558 return d->alarm(t); 3559 } 3560 3561 KAAlarm KAEventPrivate::alarm(KAAlarm::Type type) const 3562 { 3563 checkRecur(); // ensure recurrence/repetition data is consistent 3564 KAAlarm al; // this sets type to Type::Invalid 3565 KAAlarm::Private* const al_d = al.d; 3566 if (mAlarmCount) 3567 { 3568 al_d->mActionType = static_cast<KAAlarm::Action>(mActionSubType); 3569 al_d->mRepeatAtLogin = false; 3570 al_d->mDeferred = false; 3571 switch (type) 3572 { 3573 case KAAlarm::Type::Main: 3574 if (!mMainExpired) 3575 { 3576 al_d->mType = KAAlarm::Type::Main; 3577 al_d->mNextMainDateTime = mNextMainDateTime; 3578 al_d->mRepetition = mRepetition; 3579 al_d->mNextRepeat = mNextRepeat; 3580 } 3581 break; 3582 case KAAlarm::Type::Reminder: 3583 if (mReminderActive == ReminderType::Active) 3584 { 3585 al_d->mType = KAAlarm::Type::Reminder; 3586 if (mReminderMinutes < 0) 3587 al_d->mNextMainDateTime = mReminderAfterTime; 3588 else if (mReminderOnceOnly) 3589 al_d->mNextMainDateTime = mStartDateTime.addMins(-mReminderMinutes); 3590 else 3591 al_d->mNextMainDateTime = mNextMainDateTime.addMins(-mReminderMinutes); 3592 } 3593 break; 3594 case KAAlarm::Type::DeferredReminder: 3595 if (mDeferral != DeferType::Reminder) 3596 break; 3597 [[fallthrough]]; // fall through to Deferred 3598 case KAAlarm::Type::Deferred: 3599 if (mDeferral != DeferType::None) 3600 { 3601 al_d->mType = (mDeferral == DeferType::Reminder) ? KAAlarm::Type::DeferredReminder : KAAlarm::Type::Deferred; 3602 al_d->mNextMainDateTime = mDeferralTime; 3603 al_d->mDeferred = true; 3604 al_d->mTimedDeferral = !mDeferralTime.isDateOnly(); 3605 } 3606 break; 3607 case KAAlarm::Type::AtLogin: 3608 if (mRepeatAtLogin) 3609 { 3610 al_d->mType = KAAlarm::Type::AtLogin; 3611 al_d->mNextMainDateTime = mAtLoginDateTime; 3612 al_d->mRepeatAtLogin = true; 3613 } 3614 break; 3615 case KAAlarm::Type::Displaying: 3616 if (mDisplaying) 3617 { 3618 al_d->mType = KAAlarm::Type::Displaying; 3619 al_d->mNextMainDateTime = mDisplayingTime; 3620 } 3621 break; 3622 case KAAlarm::Type::Invalid: 3623 default: 3624 break; 3625 } 3626 } 3627 return al; 3628 } 3629 3630 /****************************************************************************** 3631 * Return the main alarm for the event. 3632 * If the main alarm does not exist, one of the subsidiary ones is returned if 3633 * possible. 3634 * N.B. a repeat-at-login alarm can only be returned if it has been read from/ 3635 * written to the calendar file. 3636 */ 3637 KAAlarm KAEvent::firstAlarm() const 3638 { 3639 return d->firstAlarm(); 3640 } 3641 3642 KAAlarm KAEventPrivate::firstAlarm() const 3643 { 3644 if (mAlarmCount) 3645 { 3646 if (!mMainExpired) 3647 return alarm(KAAlarm::Type::Main); 3648 return nextAlarm(KAAlarm::Type::Main); 3649 } 3650 return {}; 3651 } 3652 3653 /****************************************************************************** 3654 * Return the next alarm for the event, after the specified alarm. 3655 * N.B. a repeat-at-login alarm can only be returned if it has been read from/ 3656 * written to the calendar file. 3657 */ 3658 KAAlarm KAEvent::nextAlarm(const KAAlarm& previousAlarm) const 3659 { 3660 return d->nextAlarm(previousAlarm.type()); 3661 } 3662 3663 KAAlarm KAEvent::nextAlarm(KAAlarm::Type previousType) const 3664 { 3665 return d->nextAlarm(previousType); 3666 } 3667 3668 KAAlarm KAEventPrivate::nextAlarm(KAAlarm::Type previousType) const 3669 { 3670 switch (previousType) 3671 { 3672 case KAAlarm::Type::Main: 3673 if (mReminderActive == ReminderType::Active) 3674 return alarm(KAAlarm::Type::Reminder); 3675 [[fallthrough]]; // fall through to Reminder 3676 case KAAlarm::Type::Reminder: 3677 // There can only be one deferral alarm 3678 if (mDeferral == DeferType::Reminder) 3679 return alarm(KAAlarm::Type::DeferredReminder); 3680 if (mDeferral == DeferType::Normal) 3681 return alarm(KAAlarm::Type::Deferred); 3682 [[fallthrough]]; // fall through to Deferred 3683 case KAAlarm::Type::DeferredReminder: 3684 case KAAlarm::Type::Deferred: 3685 if (mRepeatAtLogin) 3686 return alarm(KAAlarm::Type::AtLogin); 3687 [[fallthrough]]; // fall through to AtLogin 3688 case KAAlarm::Type::AtLogin: 3689 if (mDisplaying) 3690 return alarm(KAAlarm::Type::Displaying); 3691 [[fallthrough]]; // fall through to Displaying 3692 case KAAlarm::Type::Displaying: 3693 case KAAlarm::Type::Invalid: 3694 // fall through to default 3695 default: 3696 break; 3697 } 3698 return {}; 3699 } 3700 3701 int KAEvent::alarmCount() const 3702 { 3703 return d->mAlarmCount; 3704 } 3705 3706 /****************************************************************************** 3707 * Remove the alarm of the specified type from the event. 3708 * This must only be called to remove an alarm which has expired, not to 3709 * reconfigure the event. 3710 */ 3711 void KAEvent::removeExpiredAlarm(KAAlarm::Type type) 3712 { 3713 d->removeExpiredAlarm(type); 3714 } 3715 3716 void KAEventPrivate::removeExpiredAlarm(KAAlarm::Type type) 3717 { 3718 const int count = mAlarmCount; 3719 switch (type) 3720 { 3721 case KAAlarm::Type::Main: 3722 if (mReminderActive == ReminderType::None || mReminderMinutes > 0) 3723 { 3724 mAlarmCount = 0; // removing main alarm - also remove subsidiary alarms 3725 break; 3726 } 3727 // There is a reminder after the main alarm - retain the 3728 // reminder and remove other subsidiary alarms. 3729 mMainExpired = true; // mark the alarm as expired now 3730 --mAlarmCount; 3731 set_deferral(DeferType::None); 3732 if (mDisplaying) 3733 { 3734 mDisplaying = false; 3735 --mAlarmCount; 3736 } 3737 [[fallthrough]]; // fall through to AtLogin 3738 case KAAlarm::Type::AtLogin: 3739 if (mRepeatAtLogin) 3740 { 3741 // Remove the at-login alarm, but keep a note of it for archiving purposes 3742 mArchiveRepeatAtLogin = true; 3743 mRepeatAtLogin = false; 3744 --mAlarmCount; 3745 } 3746 break; 3747 case KAAlarm::Type::Reminder: 3748 // Remove any reminder alarm, but keep a note of it for archiving purposes 3749 // and for restoration after the next recurrence. 3750 activate_reminder(false); 3751 break; 3752 case KAAlarm::Type::DeferredReminder: 3753 case KAAlarm::Type::Deferred: 3754 set_deferral(DeferType::None); 3755 break; 3756 case KAAlarm::Type::Displaying: 3757 if (mDisplaying) 3758 { 3759 mDisplaying = false; 3760 --mAlarmCount; 3761 } 3762 break; 3763 case KAAlarm::Type::Invalid: 3764 default: 3765 break; 3766 } 3767 if (mAlarmCount != count) 3768 mTriggerChanged = true; 3769 } 3770 3771 /****************************************************************************** 3772 * Compare this instance with another. 3773 */ 3774 bool KAEvent::compare(const KAEvent& other, Comparison comparison) const 3775 { 3776 return d->compare(*other.d, comparison); 3777 } 3778 bool KAEventPrivate::compare(const KAEventPrivate& other, KAEvent::Comparison comparison) const 3779 { 3780 if (comparison & KAEvent::Compare::Id) 3781 { 3782 if (mEventID != other.mEventID) 3783 return false; 3784 } 3785 if (mCategory != other.mCategory 3786 || mActionSubType != other.mActionSubType 3787 || mDisplaying != other.mDisplaying 3788 || mName != other.mName 3789 || mText != other.mText 3790 || mStartDateTime != other.mStartDateTime 3791 || mLateCancel != other.mLateCancel 3792 || mCopyToKOrganizer != other.mCopyToKOrganizer 3793 || mWakeFromSuspend != other.mWakeFromSuspend 3794 || mCompatibility != other.mCompatibility 3795 || mEnabled != other.mEnabled 3796 || mReadOnly != other.mReadOnly) 3797 return false; 3798 if (mRecurrence) 3799 { 3800 if (!other.mRecurrence 3801 || *mRecurrence != *other.mRecurrence 3802 || mExcludeHolidays != other.mExcludeHolidays 3803 || mWorkTimeOnly != other.mWorkTimeOnly 3804 || mRepetition != mRepetition) 3805 return false; 3806 } 3807 else 3808 { 3809 if (other.mRecurrence 3810 || mRepeatAtLogin != other.mRepeatAtLogin 3811 || mArchiveRepeatAtLogin != other.mArchiveRepeatAtLogin 3812 || (mRepeatAtLogin && mAtLoginDateTime != other.mAtLoginDateTime)) 3813 return false; 3814 } 3815 if (mDisplaying) 3816 { 3817 if (mDisplayingTime != other.mDisplayingTime 3818 || mDisplayingFlags != other.mDisplayingFlags 3819 || mDisplayingDefer != other.mDisplayingDefer 3820 || mDisplayingEdit != other.mDisplayingEdit) 3821 return false; 3822 } 3823 if (comparison & KAEvent::Compare::ICalendar) 3824 { 3825 if (mCreatedDateTime != other.mCreatedDateTime 3826 || mCustomProperties != other.mCustomProperties 3827 || mRevision != other.mRevision) 3828 return false; 3829 } 3830 if (comparison & KAEvent::Compare::UserSettable) 3831 { 3832 if (mResourceId != other.mResourceId) 3833 return false; 3834 } 3835 if (comparison & KAEvent::Compare::CurrentState) 3836 { 3837 if (mNextMainDateTime != other.mNextMainDateTime 3838 || mMainExpired != other.mMainExpired 3839 || (mRepetition && mNextRepeat != other.mNextRepeat)) 3840 return false; 3841 } 3842 switch (mCategory) 3843 { 3844 case CalEvent::ACTIVE: 3845 if (mArchive != other.mArchive) 3846 return false; 3847 break; 3848 case CalEvent::TEMPLATE: 3849 if (mTemplateAfterTime != other.mTemplateAfterTime) 3850 return false; 3851 break; 3852 default: 3853 break; 3854 } 3855 3856 switch (mActionSubType) 3857 { 3858 case KAEvent::SubAction::Command: 3859 if (mCommandScript != other.mCommandScript || mCommandXterm != other.mCommandXterm || mCommandDisplay != other.mCommandDisplay 3860 || mCommandError != other.mCommandError || mCommandHideError != other.mCommandHideError || mLogFile != other.mLogFile) 3861 return false; 3862 if (!mCommandDisplay) 3863 break; 3864 [[fallthrough]]; // fall through to Message 3865 case KAEvent::SubAction::File: 3866 case KAEvent::SubAction::Message: 3867 if (mReminderMinutes != other.mReminderMinutes 3868 || mBgColour != other.mBgColour 3869 || mFgColour != other.mFgColour 3870 || mUseDefaultFont != other.mUseDefaultFont 3871 || (!mUseDefaultFont && mFont != other.mFont) 3872 || mLateCancel != other.mLateCancel 3873 || (mLateCancel && mAutoClose != other.mAutoClose) 3874 || mDeferDefaultMinutes != other.mDeferDefaultMinutes 3875 || (mDeferDefaultMinutes && mDeferDefaultDateOnly != other.mDeferDefaultDateOnly) 3876 || mPreAction != other.mPreAction 3877 || mPostAction != other.mPostAction 3878 || mExtraActionOptions != other.mExtraActionOptions 3879 || mCommandError != other.mCommandError 3880 || mConfirmAck != other.mConfirmAck 3881 || mNotify != other.mNotify 3882 || mEmailId != other.mEmailId 3883 || mBeep != other.mBeep 3884 || mSpeak != other.mSpeak 3885 || mAudioFile != other.mAudioFile) 3886 return false; 3887 if (mReminderMinutes) 3888 { 3889 if (mReminderOnceOnly != other.mReminderOnceOnly) 3890 return false; 3891 if (comparison & KAEvent::Compare::CurrentState) 3892 { 3893 if (mReminderActive != other.mReminderActive 3894 || (mReminderActive != ReminderType::None && mReminderAfterTime != other.mReminderAfterTime)) 3895 return false; 3896 } 3897 } 3898 if (comparison & KAEvent::Compare::CurrentState) 3899 { 3900 if (mDeferral != other.mDeferral 3901 || (mDeferral != DeferType::None && mDeferralTime != other.mDeferralTime)) 3902 return false; 3903 } 3904 if (mAudioFile.isEmpty()) 3905 break; 3906 [[fallthrough]]; // fall through to Audio 3907 case KAEvent::SubAction::Audio: 3908 if (mRepeatSoundPause != other.mRepeatSoundPause) 3909 return false; 3910 if (mSoundVolume >= 0) 3911 { 3912 if (mSoundVolume != other.mSoundVolume) 3913 return false; 3914 if (mFadeVolume >= 0) 3915 { 3916 if (mFadeVolume != other.mFadeVolume 3917 || mFadeSeconds != other.mFadeSeconds) 3918 return false; 3919 } 3920 else if (other.mFadeVolume >= 0) 3921 return false; 3922 } 3923 else if (other.mSoundVolume >= 0) 3924 return false; 3925 break; 3926 case KAEvent::SubAction::Email: 3927 if (mEmailFromIdentity != other.mEmailFromIdentity 3928 || mEmailAddresses != other.mEmailAddresses 3929 || mEmailSubject != other.mEmailSubject 3930 || mEmailAttachments != other.mEmailAttachments 3931 || mEmailBcc != other.mEmailBcc) 3932 return false; 3933 break; 3934 } 3935 return true; 3936 } 3937 3938 void KAEvent::startChanges() 3939 { 3940 d->startChanges(); 3941 } 3942 3943 /****************************************************************************** 3944 * Indicate that changes to the instance are complete. 3945 * This allows trigger times to be recalculated if any changes have occurred. 3946 */ 3947 void KAEvent::endChanges() 3948 { 3949 d->endChanges(); 3950 } 3951 3952 void KAEventPrivate::endChanges() 3953 { 3954 if (mChangeCount > 0) 3955 --mChangeCount; 3956 } 3957 3958 /****************************************************************************** 3959 * Return a list of pointers to KAEvent objects. 3960 */ 3961 KAEvent::List KAEvent::ptrList(QList<KAEvent>& objList) 3962 { 3963 KAEvent::List ptrs; 3964 const int count = objList.count(); 3965 ptrs.reserve(count); 3966 for (int i = 0; i < count; ++i) 3967 ptrs += &objList[i]; 3968 return ptrs; 3969 } 3970 3971 void KAEvent::dumpDebug() const 3972 { 3973 #ifndef KDE_NO_DEBUG_OUTPUT 3974 d->dumpDebug(); 3975 #endif 3976 } 3977 3978 #ifndef KDE_NO_DEBUG_OUTPUT 3979 void KAEventPrivate::dumpDebug() const 3980 { 3981 qCDebug(KALARMCAL_LOG) << "KAEvent dump:"; 3982 qCDebug(KALARMCAL_LOG) << "-- mEventID:" << mEventID; 3983 qCDebug(KALARMCAL_LOG) << "-- mActionSubType:" << (mActionSubType == KAEvent::SubAction::Message ? "MESSAGE" : mActionSubType == KAEvent::SubAction::File ? "FILE" : mActionSubType == KAEvent::SubAction::Command ? "COMMAND" : mActionSubType == KAEvent::SubAction::Email ? "EMAIL" : mActionSubType == KAEvent::SubAction::Audio ? "AUDIO" : "??"); 3984 qCDebug(KALARMCAL_LOG) << "-- mNextMainDateTime:" << mNextMainDateTime.toString(); 3985 qCDebug(KALARMCAL_LOG) << "-- mCommandError:" << mCommandError; 3986 qCDebug(KALARMCAL_LOG) << "-- mAllTrigger:" << mAllTrigger.toString(); 3987 qCDebug(KALARMCAL_LOG) << "-- mMainTrigger:" << mMainTrigger.toString(); 3988 qCDebug(KALARMCAL_LOG) << "-- mAllWorkTrigger:" << mAllWorkTrigger.toString(); 3989 qCDebug(KALARMCAL_LOG) << "-- mMainWorkTrigger:" << mMainWorkTrigger.toString(); 3990 qCDebug(KALARMCAL_LOG) << "-- mCategory:" << mCategory; 3991 qCDebug(KALARMCAL_LOG) << "-- mName:" << mName; 3992 if (mCategory == CalEvent::TEMPLATE) 3993 qCDebug(KALARMCAL_LOG) << "-- mTemplateAfterTime:" << mTemplateAfterTime; 3994 qCDebug(KALARMCAL_LOG) << "-- mText:" << mText; 3995 if (mActionSubType == KAEvent::SubAction::Message 3996 || mActionSubType == KAEvent::SubAction::File 3997 || (mActionSubType == KAEvent::SubAction::Command && mCommandDisplay)) 3998 { 3999 if (mCommandDisplay) 4000 qCDebug(KALARMCAL_LOG) << "-- mCommandScript:" << mCommandScript; 4001 qCDebug(KALARMCAL_LOG) << "-- mBgColour:" << mBgColour.name(); 4002 qCDebug(KALARMCAL_LOG) << "-- mFgColour:" << mFgColour.name(); 4003 qCDebug(KALARMCAL_LOG) << "-- mUseDefaultFont:" << mUseDefaultFont; 4004 if (!mUseDefaultFont) 4005 qCDebug(KALARMCAL_LOG) << "-- mFont:" << mFont.toString(); 4006 qCDebug(KALARMCAL_LOG) << "-- mSpeak:" << mSpeak; 4007 qCDebug(KALARMCAL_LOG) << "-- mAudioFile:" << mAudioFile; 4008 qCDebug(KALARMCAL_LOG) << "-- mPreAction:" << mPreAction; 4009 qCDebug(KALARMCAL_LOG) << "-- mExecPreActOnDeferral:" << (mExtraActionOptions & KAEvent::ExecPreActOnDeferral); 4010 qCDebug(KALARMCAL_LOG) << "-- mCancelOnPreActErr:" << (mExtraActionOptions & KAEvent::CancelOnPreActError); 4011 qCDebug(KALARMCAL_LOG) << "-- mDontShowPreActErr:" << (mExtraActionOptions & KAEvent::DontShowPreActError); 4012 qCDebug(KALARMCAL_LOG) << "-- mPostAction:" << mPostAction; 4013 qCDebug(KALARMCAL_LOG) << "-- mLateCancel:" << mLateCancel; 4014 qCDebug(KALARMCAL_LOG) << "-- mAutoClose:" << mAutoClose; 4015 qCDebug(KALARMCAL_LOG) << "-- mNotify:" << mNotify; 4016 } 4017 else if (mActionSubType == KAEvent::SubAction::Command) 4018 { 4019 qCDebug(KALARMCAL_LOG) << "-- mCommandScript:" << mCommandScript; 4020 qCDebug(KALARMCAL_LOG) << "-- mCommandXterm:" << mCommandXterm; 4021 qCDebug(KALARMCAL_LOG) << "-- mCommandDisplay:" << mCommandDisplay; 4022 qCDebug(KALARMCAL_LOG) << "-- mCommandHideError:" << mCommandHideError; 4023 qCDebug(KALARMCAL_LOG) << "-- mLogFile:" << mLogFile; 4024 } 4025 else if (mActionSubType == KAEvent::SubAction::Email) 4026 { 4027 qCDebug(KALARMCAL_LOG) << "-- mEmail: FromKMail:" << mEmailFromIdentity; 4028 qCDebug(KALARMCAL_LOG) << "-- Addresses:" << mEmailAddresses.join(QStringLiteral(",")); 4029 qCDebug(KALARMCAL_LOG) << "-- Subject:" << mEmailSubject; 4030 qCDebug(KALARMCAL_LOG) << "-- Attachments:" << mEmailAttachments.join(QLatin1Char(',')); 4031 qCDebug(KALARMCAL_LOG) << "-- Bcc:" << mEmailBcc; 4032 } 4033 else if (mActionSubType == KAEvent::SubAction::Audio) 4034 qCDebug(KALARMCAL_LOG) << "-- mAudioFile:" << mAudioFile; 4035 qCDebug(KALARMCAL_LOG) << "-- mBeep:" << mBeep; 4036 if (mActionSubType == KAEvent::SubAction::Audio || !mAudioFile.isEmpty()) 4037 { 4038 if (mSoundVolume >= 0) 4039 { 4040 qCDebug(KALARMCAL_LOG) << "-- mSoundVolume:" << mSoundVolume; 4041 if (mFadeVolume >= 0) 4042 { 4043 qCDebug(KALARMCAL_LOG) << "-- mFadeVolume:" << mFadeVolume; 4044 qCDebug(KALARMCAL_LOG) << "-- mFadeSeconds:" << mFadeSeconds; 4045 } 4046 else 4047 qCDebug(KALARMCAL_LOG) << "-- mFadeVolume:-:"; 4048 } 4049 else 4050 qCDebug(KALARMCAL_LOG) << "-- mSoundVolume:-:"; 4051 qCDebug(KALARMCAL_LOG) << "-- mRepeatSoundPause:" << mRepeatSoundPause; 4052 } 4053 qCDebug(KALARMCAL_LOG) << "-- mEmailId:" << mEmailId; 4054 qCDebug(KALARMCAL_LOG) << "-- mCopyToKOrganizer:" << mCopyToKOrganizer; 4055 qCDebug(KALARMCAL_LOG) << "-- mWakeFromSuspend:" << mWakeFromSuspend; 4056 qCDebug(KALARMCAL_LOG) << "-- mExcludeHolidays:" << mExcludeHolidays; 4057 qCDebug(KALARMCAL_LOG) << "-- mWorkTimeOnly:" << mWorkTimeOnly; 4058 qCDebug(KALARMCAL_LOG) << "-- mStartDateTime:" << mStartDateTime.toString(); 4059 // qCDebug(KALARMCAL_LOG) << "-- mCreatedDateTime:" << mCreatedDateTime; 4060 qCDebug(KALARMCAL_LOG) << "-- mRepeatAtLogin:" << mRepeatAtLogin; 4061 // if (mRepeatAtLogin) 4062 // qCDebug(KALARMCAL_LOG) << "-- mAtLoginDateTime:" << mAtLoginDateTime; 4063 qCDebug(KALARMCAL_LOG) << "-- mArchiveRepeatAtLogin:" << mArchiveRepeatAtLogin; 4064 qCDebug(KALARMCAL_LOG) << "-- mConfirmAck:" << mConfirmAck; 4065 qCDebug(KALARMCAL_LOG) << "-- mEnabled:" << mEnabled; 4066 qCDebug(KALARMCAL_LOG) << "-- mResourceId:" << mResourceId; 4067 qCDebug(KALARMCAL_LOG) << "-- mCompatibility:" << mCompatibility; 4068 qCDebug(KALARMCAL_LOG) << "-- mReadOnly:" << mReadOnly; 4069 if (mReminderMinutes) 4070 { 4071 qCDebug(KALARMCAL_LOG) << "-- mReminderMinutes:" << mReminderMinutes; 4072 qCDebug(KALARMCAL_LOG) << "-- mReminderActive:" << (mReminderActive == ReminderType::Active ? "active" : mReminderActive == ReminderType::Hidden ? "hidden" : "no"); 4073 qCDebug(KALARMCAL_LOG) << "-- mReminderOnceOnly:" << mReminderOnceOnly; 4074 } 4075 if (mDeferral != DeferType::None) 4076 { 4077 qCDebug(KALARMCAL_LOG) << "-- mDeferral:" << (mDeferral == DeferType::Normal ? "normal" : "reminder"); 4078 qCDebug(KALARMCAL_LOG) << "-- mDeferralTime:" << mDeferralTime.toString(); 4079 } 4080 qCDebug(KALARMCAL_LOG) << "-- mDeferDefaultMinutes:" << mDeferDefaultMinutes; 4081 if (mDeferDefaultMinutes) 4082 qCDebug(KALARMCAL_LOG) << "-- mDeferDefaultDateOnly:" << mDeferDefaultDateOnly; 4083 if (mDisplaying) 4084 { 4085 qCDebug(KALARMCAL_LOG) << "-- mDisplayingTime:" << mDisplayingTime.toString(); 4086 qCDebug(KALARMCAL_LOG) << "-- mDisplayingFlags:" << mDisplayingFlags; 4087 qCDebug(KALARMCAL_LOG) << "-- mDisplayingDefer:" << mDisplayingDefer; 4088 qCDebug(KALARMCAL_LOG) << "-- mDisplayingEdit:" << mDisplayingEdit; 4089 } 4090 qCDebug(KALARMCAL_LOG) << "-- mRevision:" << mRevision; 4091 qCDebug(KALARMCAL_LOG) << "-- mRecurrence:" << mRecurrence; 4092 if (!mRepetition) 4093 qCDebug(KALARMCAL_LOG) << "-- mRepetition: 0"; 4094 else if (mRepetition.isDaily()) 4095 qCDebug(KALARMCAL_LOG) << "-- mRepetition: count:" << mRepetition.count() << ", interval:" << mRepetition.intervalDays() << "days"; 4096 else 4097 qCDebug(KALARMCAL_LOG) << "-- mRepetition: count:" << mRepetition.count() << ", interval:" << mRepetition.intervalMinutes() << "minutes"; 4098 qCDebug(KALARMCAL_LOG) << "-- mNextRepeat:" << mNextRepeat; 4099 qCDebug(KALARMCAL_LOG) << "-- mAlarmCount:" << mAlarmCount; 4100 qCDebug(KALARMCAL_LOG) << "-- mMainExpired:" << mMainExpired; 4101 qCDebug(KALARMCAL_LOG) << "-- mDisplaying:" << mDisplaying; 4102 qCDebug(KALARMCAL_LOG) << "KAEvent dump end"; 4103 } 4104 #endif 4105 4106 /****************************************************************************** 4107 * Fetch the start and next date/time for a KCalendarCore::Event. 4108 * Reply = next main date/time. 4109 */ 4110 DateTime KAEventPrivate::readDateTime(const Event::Ptr& event, bool localZone, bool dateOnly, DateTime& start) 4111 { 4112 start = DateTime(event->dtStart()); 4113 if (dateOnly) 4114 { 4115 // A date-only event is indicated by the X-KDE-KALARM-FLAGS:DATE property, not 4116 // by a date-only start date/time (for the reasons given in updateKCalEvent()). 4117 start.setDateOnly(true); 4118 } 4119 if (localZone) 4120 { 4121 // The local system time zone is indicated by the X-KDE-KALARM-FLAGS:LOCAL 4122 // property, because QDateTime values with time spec Qt::LocalTime are not 4123 // stored correctly in the calendar file. 4124 start.setTimeSpec(KADateTime::LocalZone); 4125 } 4126 DateTime next = start; 4127 const int SZ_YEAR = 4; // number of digits in year value 4128 const int SZ_MONTH = 2; // number of digits in month value 4129 const int SZ_DAY = 2; // number of digits in day value 4130 const int SZ_DATE = SZ_YEAR + SZ_MONTH + SZ_DAY; // total size of date value 4131 const int IX_TIME = SZ_DATE + 1; // offset to time value 4132 const int SZ_HOUR = 2; // number of digits in hour value 4133 const int SZ_MIN = 2; // number of digits in minute value 4134 const int SZ_SEC = 2; // number of digits in second value 4135 const int SZ_TIME = SZ_HOUR + SZ_MIN + SZ_SEC; // total size of time value 4136 const QString prop = event->customProperty(KACalendar::APPNAME, KAEventPrivate::NEXT_RECUR_PROPERTY); 4137 if (prop.length() >= SZ_DATE) 4138 { 4139 // The next due recurrence time is specified 4140 const QDate d(QStringView(prop).left(SZ_YEAR).toInt(), 4141 QStringView(prop).mid(SZ_YEAR, SZ_MONTH).toInt(), 4142 QStringView(prop).mid(SZ_YEAR + SZ_MONTH, SZ_DAY).toInt()); 4143 if (d.isValid()) 4144 { 4145 if (dateOnly && prop.length() == SZ_DATE) 4146 next.setDate(d); 4147 else if (!dateOnly && prop.length() == IX_TIME + SZ_TIME && prop[SZ_DATE] == QLatin1Char('T')) 4148 { 4149 const QTime t(QStringView(prop).mid(IX_TIME, SZ_HOUR).toInt(), 4150 QStringView(prop).mid(IX_TIME + SZ_HOUR, SZ_MIN).toInt(), 4151 QStringView(prop).mid(IX_TIME + SZ_HOUR + SZ_MIN, SZ_SEC).toInt()); 4152 if (t.isValid()) 4153 { 4154 next.setDate(d); 4155 next.setTime(t); 4156 } 4157 } 4158 if (next < start) 4159 next = start; // ensure next recurrence time is valid 4160 } 4161 } 4162 return next; 4163 } 4164 4165 /****************************************************************************** 4166 * Parse the alarms for a KCalendarCore::Event. 4167 * Reply = map of alarm data, indexed by KAAlarm::Type 4168 */ 4169 void KAEventPrivate::readAlarms(const Event::Ptr& event, AlarmMap* alarmMap, bool cmdDisplay) 4170 { 4171 const Alarm::List alarms = event->alarms(); 4172 4173 // Check if it's an audio event with no display alarm 4174 bool audioOnly = false; 4175 for (const Alarm::Ptr& alarm : alarms) 4176 { 4177 bool done = false; 4178 switch (alarm->type()) 4179 { 4180 case Alarm::Display: 4181 case Alarm::Procedure: 4182 audioOnly = false; 4183 done = true; // exit from the 'for' loop 4184 break; 4185 case Alarm::Audio: 4186 audioOnly = true; 4187 break; 4188 default: 4189 break; 4190 } 4191 if (done) 4192 break; 4193 } 4194 4195 for (const Alarm::Ptr& alarm : alarms) 4196 { 4197 // Parse the next alarm's text 4198 AlarmData data; 4199 readAlarm(alarm, data, audioOnly, cmdDisplay); 4200 if (data.type != INVALID_ALARM) 4201 alarmMap->insert(data.type, data); 4202 } 4203 } 4204 4205 /****************************************************************************** 4206 * Parse a KCalendarCore::Alarm. 4207 * If 'audioMain' is true, the event contains an audio alarm but no display alarm. 4208 * Reply = alarm ID (sequence number) 4209 */ 4210 void KAEventPrivate::readAlarm(const Alarm::Ptr& alarm, AlarmData& data, bool audioMain, bool cmdDisplay) 4211 { 4212 // Parse the next alarm's text 4213 data.alarm = alarm; 4214 data.displayingFlags = 0; 4215 data.isEmailText = false; 4216 data.speak = false; 4217 data.hiddenReminder = false; 4218 data.timedDeferral = false; 4219 data.nextRepeat = 0; 4220 data.repeatSoundPause = -1; 4221 if (alarm->repeatCount()) 4222 { 4223 bool ok; 4224 const QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::NEXT_REPEAT_PROPERTY); 4225 int n = static_cast<int>(property.toUInt(&ok)); 4226 if (ok) 4227 data.nextRepeat = n; 4228 } 4229 QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY); 4230 const QStringList flags = property.split(KAEventPrivate::SC, Qt::SkipEmptyParts); 4231 switch (alarm->type()) 4232 { 4233 case Alarm::Procedure: 4234 data.action = KAAlarm::Action::Command; 4235 data.cleanText = alarm->programFile(); 4236 data.commandScript = data.cleanText.isEmpty(); // blank command indicates a script 4237 if (!alarm->programArguments().isEmpty()) 4238 { 4239 if (!data.commandScript) 4240 data.cleanText += QLatin1Char(' '); 4241 data.cleanText += alarm->programArguments(); 4242 } 4243 data.extraActionOptions = {}; 4244 if (flags.contains(KAEventPrivate::EXEC_ON_DEFERRAL_FLAG)) 4245 data.extraActionOptions |= KAEvent::ExecPreActOnDeferral; 4246 if (flags.contains(KAEventPrivate::CANCEL_ON_ERROR_FLAG)) 4247 data.extraActionOptions |= KAEvent::CancelOnPreActError; 4248 if (flags.contains(KAEventPrivate::DONT_SHOW_ERROR_FLAG)) 4249 data.extraActionOptions |= KAEvent::DontShowPreActError; 4250 if (!cmdDisplay) 4251 break; 4252 [[fallthrough]]; // fall through to Display 4253 case Alarm::Display: 4254 { 4255 if (alarm->type() == Alarm::Display) 4256 { 4257 data.action = KAAlarm::Action::Message; 4258 data.cleanText = AlarmText::fromCalendarText(alarm->text(), data.isEmailText); 4259 } 4260 const QString prop = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::FONT_COLOUR_PROPERTY); 4261 const QStringList list = prop.split(QLatin1Char(';'), Qt::KeepEmptyParts); 4262 data.bgColour = QColor(255, 255, 255); // white 4263 data.fgColour = QColor(0, 0, 0); // black 4264 const int n = list.count(); 4265 if (n > 0) 4266 { 4267 if (!list[0].isEmpty()) 4268 { 4269 QColor c(list[0]); 4270 if (c.isValid()) 4271 data.bgColour = c; 4272 } 4273 if (n > 1 && !list[1].isEmpty()) 4274 { 4275 QColor c(list[1]); 4276 if (c.isValid()) 4277 data.fgColour = c; 4278 } 4279 } 4280 data.defaultFont = (n <= 2 || list[2].isEmpty()); 4281 if (!data.defaultFont) 4282 data.font.fromString(list[2]); 4283 break; 4284 } 4285 case Alarm::Email: 4286 { 4287 data.action = KAAlarm::Action::Email; 4288 data.cleanText = alarm->mailText(); 4289 const int i = flags.indexOf(KAEventPrivate::EMAIL_ID_FLAG); 4290 data.emailFromId = (i >= 0 && i + 1 < flags.count()) ? flags[i + 1].toUInt() : 0; 4291 break; 4292 } 4293 case Alarm::Audio: 4294 { 4295 data.action = KAAlarm::Action::Audio; 4296 data.cleanText = alarm->audioFile(); 4297 data.repeatSoundPause = (alarm->repeatCount() == -2) ? alarm->snoozeTime().asSeconds() 4298 : (alarm->repeatCount() == -1) ? 0 : -1; 4299 data.soundVolume = -1; 4300 data.fadeVolume = -1; 4301 data.fadeSeconds = 0; 4302 QString prop = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::VOLUME_PROPERTY); 4303 if (!prop.isEmpty()) 4304 { 4305 bool ok; 4306 const QStringList list = prop.split(QLatin1Char(';'), Qt::KeepEmptyParts); 4307 data.soundVolume = list[0].toFloat(&ok); 4308 if (!ok || data.soundVolume > 1.0f) 4309 data.soundVolume = -1; 4310 if (data.soundVolume >= 0 && list.count() >= 3) 4311 { 4312 float fadeVolume = list[1].toFloat(&ok); 4313 int fadeSecs = 0; 4314 if (ok) 4315 fadeSecs = static_cast<int>(list[2].toUInt(&ok)); 4316 if (ok && fadeVolume >= 0 && fadeVolume <= 1.0f && fadeSecs > 0) 4317 { 4318 data.fadeVolume = fadeVolume; 4319 data.fadeSeconds = fadeSecs; 4320 } 4321 } 4322 } 4323 if (!audioMain) 4324 { 4325 data.type = AUDIO_ALARM; 4326 data.speak = flags.contains(KAEventPrivate::SPEAK_FLAG); 4327 return; 4328 } 4329 break; 4330 } 4331 case Alarm::Invalid: 4332 data.type = INVALID_ALARM; 4333 return; 4334 } 4335 4336 bool atLogin = false; 4337 bool reminder = false; 4338 bool deferral = false; 4339 bool dateDeferral = false; 4340 bool repeatSound = false; 4341 data.type = MAIN_ALARM; 4342 property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY); 4343 const QStringList types = property.split(QLatin1Char(','), Qt::SkipEmptyParts); 4344 for (int i = 0, end = types.count(); i < end; ++i) 4345 { 4346 const QString type = types[i]; 4347 if (type == KAEventPrivate::AT_LOGIN_TYPE) 4348 atLogin = true; 4349 else if (type == KAEventPrivate::FILE_TYPE && data.action == KAAlarm::Action::Message) 4350 data.action = KAAlarm::Action::File; 4351 else if (type == KAEventPrivate::REMINDER_TYPE) 4352 reminder = true; 4353 else if (type == KAEventPrivate::TIME_DEFERRAL_TYPE) 4354 deferral = true; 4355 else if (type == KAEventPrivate::DATE_DEFERRAL_TYPE) 4356 dateDeferral = deferral = true; 4357 else if (type == KAEventPrivate::DISPLAYING_TYPE) 4358 data.type = DISPLAYING_ALARM; 4359 else if (type == KAEventPrivate::PRE_ACTION_TYPE && data.action == KAAlarm::Action::Command) 4360 data.type = PRE_ACTION_ALARM; 4361 else if (type == KAEventPrivate::POST_ACTION_TYPE && data.action == KAAlarm::Action::Command) 4362 data.type = POST_ACTION_ALARM; 4363 else if (type == KAEventPrivate::SOUND_REPEAT_TYPE && data.action == KAAlarm::Action::Audio) 4364 { 4365 repeatSound = true; 4366 if (i + 1 < end) 4367 { 4368 bool ok; 4369 uint n = types[i + 1].toUInt(&ok); 4370 if (ok) 4371 { 4372 data.repeatSoundPause = n; 4373 ++i; 4374 } 4375 } 4376 } 4377 } 4378 if (repeatSound && data.repeatSoundPause < 0) 4379 data.repeatSoundPause = 0; 4380 else if (!repeatSound) 4381 data.repeatSoundPause = -1; 4382 4383 if (reminder) 4384 { 4385 if (data.type == MAIN_ALARM) 4386 { 4387 data.type = deferral ? DEFERRED_REMINDER_ALARM : REMINDER_ALARM; 4388 data.timedDeferral = (deferral && !dateDeferral); 4389 if (data.type == REMINDER_ALARM 4390 && flags.contains(KAEventPrivate::HIDDEN_REMINDER_FLAG)) 4391 data.hiddenReminder = true; 4392 } 4393 else if (data.type == DISPLAYING_ALARM) 4394 data.displayingFlags = dateDeferral ? REMINDER | DATE_DEFERRAL 4395 : deferral ? REMINDER | TIME_DEFERRAL : REMINDER; 4396 } 4397 else if (deferral) 4398 { 4399 if (data.type == MAIN_ALARM) 4400 { 4401 data.type = DEFERRED_ALARM; 4402 data.timedDeferral = !dateDeferral; 4403 } 4404 else if (data.type == DISPLAYING_ALARM) 4405 data.displayingFlags = dateDeferral ? DATE_DEFERRAL : TIME_DEFERRAL; 4406 } 4407 if (atLogin) 4408 { 4409 if (data.type == MAIN_ALARM) 4410 data.type = AT_LOGIN_ALARM; 4411 else if (data.type == DISPLAYING_ALARM) 4412 data.displayingFlags = KAEvent::REPEAT_AT_LOGIN; 4413 } 4414 //qCDebug(KALARMCAL_LOG)<<"text="<<alarm->text()<<", time="<<alarm->time().toString()<<", valid time="<<alarm->time().isValid(); 4415 } 4416 4417 inline void KAEventPrivate::set_deferral(DeferType type) 4418 { 4419 if (type != DeferType::None) 4420 { 4421 if (mDeferral == DeferType::None) 4422 ++mAlarmCount; 4423 } 4424 else 4425 { 4426 if (mDeferral != DeferType::None) 4427 --mAlarmCount; 4428 } 4429 mDeferral = type; 4430 } 4431 4432 /****************************************************************************** 4433 * Calculate the next trigger times of the alarm. 4434 * This should only be called when changes have actually occurred which might 4435 * affect the event's trigger times. 4436 * mMainTrigger is set to the next scheduled recurrence/sub-repetition 4437 * mAllTrigger is the same as mMainTrigger, but takes account of reminders and 4438 * deferred reminders. 4439 * mMainWorkTrigger is set to the next scheduled recurrence/sub-repetition 4440 * which occurs in working hours, if working-time-only is set. 4441 * mAllWorkTrigger is the same as mMainWorkTrigger, but takes account of reminders. 4442 */ 4443 void KAEventPrivate::calcTriggerTimes() const 4444 { 4445 if (mChangeCount) 4446 return; 4447 #pragma message("May need to set date-only alarms to after start-of-day time in working-time checks") 4448 bool recurs = (checkRecur() != KARecurrence::NO_RECUR); 4449 if ((recurs && mWorkTimeOnly && mWorkTimeOnly != mWorkTimeIndex) 4450 || (recurs && mExcludeHolidays && mExcludeHolidayRegion != mHolidays->regionCode()) 4451 || (mStartDateTime.isDateOnly() && mTriggerStartOfDay != DateTime::startOfDay())) 4452 { 4453 // It's a work time alarm, and work days/times have changed, or 4454 // it excludes holidays, and the holidays definition has changed. 4455 mTriggerChanged = true; 4456 } 4457 else if (!mTriggerChanged) 4458 return; 4459 4460 mTriggerChanged = false; 4461 mTriggerStartOfDay = DateTime::startOfDay(); // note start of day time used in calculation 4462 if (recurs && mWorkTimeOnly) 4463 mWorkTimeOnly = mWorkTimeIndex; // note which work time definition was used in calculation 4464 if (recurs && mExcludeHolidays) 4465 mExcludeHolidayRegion = mHolidays->regionCode(); // note which holiday definition was used in calculation 4466 bool excludeHolidays = mExcludeHolidays && !mExcludeHolidayRegion.isEmpty(); 4467 4468 mMainTrigger = mainDateTime(true); // next recurrence or sub-repetition 4469 mAllTrigger = (mDeferral == DeferType::Reminder) ? mDeferralTime 4470 : (mReminderActive != ReminderType::Active) ? mMainTrigger 4471 : (mReminderMinutes < 0) ? mReminderAfterTime 4472 : mMainTrigger.addMins(-mReminderMinutes); 4473 // If only-during-working-time is set and it recurs, it won't actually trigger 4474 // unless it falls during working hours. 4475 if ((!mWorkTimeOnly && !excludeHolidays) 4476 || !recurs 4477 || !excludedByWorkTimeOrHoliday(mMainTrigger.kDateTime())) 4478 { 4479 // It only occurs once, or it complies with any working hours/holiday 4480 // restrictions. 4481 mMainWorkTrigger = mMainTrigger; 4482 mAllWorkTrigger = mAllTrigger; 4483 } 4484 else if (mWorkTimeOnly) 4485 { 4486 // The alarm is restricted to working hours. 4487 // Finding the next occurrence during working hours can sometimes take a long time, 4488 // so mark the next actual trigger as invalid until the calculation completes. 4489 // Note that reminders are only triggered if the main alarm is during working time. 4490 if (!excludeHolidays) 4491 { 4492 // There are no holiday restrictions. 4493 calcNextWorkingTime(mMainTrigger); 4494 } 4495 else if (mHolidays->isValid()) 4496 { 4497 // Holidays are excluded. 4498 DateTime nextTrigger = mMainTrigger; 4499 KADateTime kdt; 4500 for (int i = 0; i < 20; ++i) 4501 { 4502 calcNextWorkingTime(nextTrigger); 4503 if (!mHolidays->isHoliday(mMainWorkTrigger.date())) 4504 return; // found a non-holiday occurrence 4505 kdt = mMainWorkTrigger.effectiveKDateTime(); 4506 kdt.setTime(QTime(23, 59, 59)); 4507 const KAEvent::OccurType type = nextOccurrence(kdt, nextTrigger, KAEvent::Repeats::Return); 4508 if (!nextTrigger.isValid()) 4509 break; 4510 if (!excludedByWorkTimeOrHoliday(nextTrigger.kDateTime())) 4511 { 4512 const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm 4513 mMainWorkTrigger = nextTrigger; 4514 mAllWorkTrigger = (type & KAEvent::OccurType::Repeat) ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder); 4515 return; // found a non-holiday occurrence 4516 } 4517 } 4518 mMainWorkTrigger = mAllWorkTrigger = DateTime(); 4519 } 4520 } 4521 else if (excludeHolidays && mHolidays->isValid()) 4522 { 4523 // Holidays are excluded. 4524 DateTime nextTrigger = mMainTrigger; 4525 KADateTime kdt; 4526 for (int i = 0; i < 20; ++i) 4527 { 4528 kdt = nextTrigger.effectiveKDateTime(); 4529 kdt.setTime(QTime(23, 59, 59)); 4530 const KAEvent::OccurType type = nextOccurrence(kdt, nextTrigger, KAEvent::Repeats::Return); 4531 if (!nextTrigger.isValid()) 4532 break; 4533 if (!mHolidays->isHoliday(nextTrigger.date())) 4534 { 4535 const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm 4536 mMainWorkTrigger = nextTrigger; 4537 mAllWorkTrigger = (type & KAEvent::OccurType::Repeat) ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder); 4538 return; // found a non-holiday occurrence 4539 } 4540 } 4541 mMainWorkTrigger = mAllWorkTrigger = DateTime(); 4542 } 4543 } 4544 4545 /****************************************************************************** 4546 * Return the time of the next scheduled occurrence of the event during working 4547 * hours, for an alarm which is restricted to working hours. 4548 * On entry, 'nextTrigger' = the next recurrence or repetition (as returned by 4549 * mainDateTime(true) ). 4550 */ 4551 void KAEventPrivate::calcNextWorkingTime(const DateTime& nextTrigger) const 4552 { 4553 qCDebug(KALARMCAL_LOG) << "next=" << nextTrigger.kDateTime().toString(QStringLiteral("%Y-%m-%d %H:%M")); 4554 4555 // Convert next trigger time to working day time spec to enable working time evaluation. 4556 DateTime nextTriggerWT = nextTrigger.toTimeSpec(mWorkDayTimeSpec); 4557 4558 mMainWorkTrigger = mAllWorkTrigger = DateTime(); 4559 4560 if (!mWorkDays.count(true)) 4561 return; // no working days are defined 4562 const KARecurrence::Type recurType = checkRecur(); 4563 KADateTime kdt = nextTriggerWT.effectiveKDateTime(); 4564 const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm 4565 // Check if it always falls on the same day(s) of the week. 4566 const RecurrenceRule* rrule = mRecurrence->defaultRRuleConst(); 4567 if (!rrule) 4568 return; // no recurrence rule! 4569 unsigned allDaysMask = 0x7F; // mask bits for all days of week 4570 const QList<RecurrenceRule::WDayPos> pos = rrule->byDays(); 4571 const int nDayPos = pos.count(); // number of day positions 4572 if (nDayPos) 4573 { 4574 bool noWorkPos = true; // true if no recurrence day position is working day 4575 allDaysMask = 0; 4576 for (const RecurrenceRule::WDayPos& p : pos) 4577 { 4578 const int day = p.day() - 1; // Monday = 0 4579 if (mWorkDays.testBit(day)) 4580 noWorkPos = false; // found a working day occurrence 4581 allDaysMask |= 1 << day; 4582 } 4583 // Check whether it ever occurs on a working day. 4584 // Note that this check is only guaranteed to work if the alarm's time 4585 // spec is the same as the working day time spec, since otherwise there 4586 // is a possibility that trigger dates could change when trigger times 4587 // are converted to the working day time spec. 4588 if (noWorkPos && !mRepetition && nextTrigger.timeSpec() == mWorkDayTimeSpec) 4589 return; // never occurs on a working day 4590 } 4591 DateTime newdt; 4592 4593 if (mStartDateTime.isDateOnly()) 4594 { 4595 // It's a date-only alarm. 4596 // Sub-repetitions also have to be date-only. 4597 const int repeatFreq = mRepetition.intervalDays(); 4598 const bool weeklyRepeat = mRepetition && !(repeatFreq % 7); 4599 const Duration interval = mRecurrence->regularInterval(); 4600 if ((!interval.isNull() && !(interval.asDays() % 7)) 4601 || nDayPos == 1) 4602 { 4603 // It recurs on the same day each week 4604 if (!mRepetition || weeklyRepeat) 4605 return; // any repetitions are also weekly 4606 4607 // It's a weekly recurrence with a non-weekly sub-repetition. 4608 // Check one cycle of repetitions for the next one that lands 4609 // on a working day. 4610 KADateTime dt(nextTriggerWT.kDateTime().addDays(1)); 4611 dt.setTime(QTime(0, 0, 0)); 4612 previousOccurrence(dt, newdt, false); 4613 if (!newdt.isValid()) 4614 return; // this should never happen 4615 kdt = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 4616 const int day = kdt.date().dayOfWeek() - 1; // Monday = 0 4617 for (int repeatNum = mNextRepeat + 1; ; ++repeatNum) 4618 { 4619 if (repeatNum > mRepetition.count()) 4620 repeatNum = 0; 4621 if (repeatNum == mNextRepeat) 4622 break; 4623 if (!repeatNum) 4624 { 4625 nextOccurrence(newdt.kDateTime(), newdt, KAEvent::Repeats::Ignore); 4626 if (mWorkDays.testBit(day)) 4627 { 4628 newdt = newdt.toTimeSpec(nextTrigger.timeSpec()); 4629 mMainWorkTrigger = newdt; 4630 mAllWorkTrigger = mMainWorkTrigger.addMins(-reminder); 4631 return; 4632 } 4633 kdt = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 4634 } 4635 else 4636 { 4637 const int inc = repeatFreq * repeatNum; 4638 if (mWorkDays.testBit((day + inc) % 7)) 4639 { 4640 kdt = kdt.addDays(inc); 4641 kdt.setDateOnly(true); 4642 kdt = kdt.toTimeSpec(nextTrigger.timeSpec()); 4643 mMainWorkTrigger = mAllWorkTrigger = kdt; 4644 return; 4645 } 4646 } 4647 } 4648 return; 4649 } 4650 if (!mRepetition || weeklyRepeat) 4651 { 4652 // It's a date-only alarm with either no sub-repetition or a 4653 // sub-repetition which always falls on the same day of the week 4654 // as the recurrence (if any). 4655 unsigned days = 0; 4656 for (; ;) 4657 { 4658 kdt.setTime(QTime(23, 59, 59)); 4659 nextOccurrence(kdt, newdt, KAEvent::Repeats::Ignore); 4660 if (!newdt.isValid()) 4661 return; 4662 kdt = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 4663 const int day = kdt.date().dayOfWeek() - 1; 4664 if (mWorkDays.testBit(day)) 4665 break; // found a working day occurrence 4666 // Prevent indefinite looping (which should never happen anyway) 4667 if ((days & allDaysMask) == allDaysMask) 4668 return; // found a recurrence on every possible day of the week!?! 4669 days |= 1 << day; 4670 } 4671 kdt.setDateOnly(true); 4672 kdt = kdt.toTimeSpec(nextTrigger.timeSpec()); 4673 mMainWorkTrigger = kdt; 4674 mAllWorkTrigger = kdt.addSecs(-60 * reminder); 4675 return; 4676 } 4677 4678 // It's a date-only alarm which recurs on different days of the week, 4679 // as does the sub-repetition. 4680 // Find the previous recurrence (as opposed to sub-repetition) 4681 unsigned days = 1 << (kdt.date().dayOfWeek() - 1); 4682 KADateTime dt(nextTriggerWT.kDateTime().addDays(1)); 4683 dt.setTime(QTime(0, 0, 0)); 4684 previousOccurrence(dt, newdt, false); 4685 if (!newdt.isValid()) 4686 return; // this should never happen 4687 kdt = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 4688 int day = kdt.date().dayOfWeek() - 1; // Monday = 0 4689 for (int repeatNum = mNextRepeat; ; repeatNum = 0) 4690 { 4691 while (++repeatNum <= mRepetition.count()) 4692 { 4693 const int inc = repeatFreq * repeatNum; 4694 if (mWorkDays.testBit((day + inc) % 7)) 4695 { 4696 kdt = kdt.addDays(inc); 4697 kdt.setDateOnly(true); 4698 kdt = kdt.toTimeSpec(nextTrigger.timeSpec()); 4699 mMainWorkTrigger = mAllWorkTrigger = kdt; 4700 return; 4701 } 4702 if ((days & allDaysMask) == allDaysMask) 4703 return; // found an occurrence on every possible day of the week!?! 4704 days |= 1 << day; 4705 } 4706 nextOccurrence(kdt, newdt, KAEvent::Repeats::Ignore); 4707 if (!newdt.isValid()) 4708 return; 4709 kdt = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 4710 day = kdt.date().dayOfWeek() - 1; 4711 if (mWorkDays.testBit(day)) 4712 { 4713 kdt.setDateOnly(true); 4714 kdt = kdt.toTimeSpec(nextTrigger.timeSpec()); 4715 mMainWorkTrigger = kdt; 4716 mAllWorkTrigger = kdt.addSecs(-60 * reminder); 4717 return; 4718 } 4719 if ((days & allDaysMask) == allDaysMask) 4720 return; // found an occurrence on every possible day of the week!?! 4721 days |= 1 << day; 4722 } 4723 return; 4724 } 4725 4726 // It's a date-time alarm. 4727 4728 /* Check whether the recurrence or sub-repetition occurs at the same time 4729 * every day. Note that because of seasonal time changes, a recurrence 4730 * defined in terms of minutes will vary its time of day even if its value 4731 * is a multiple of a day (24*60 minutes). Sub-repetitions are considered 4732 * to repeat at the same time of day regardless of time changes if they 4733 * are multiples of a day, which doesn't strictly conform to the iCalendar 4734 * format because this only allows their interval to be recorded in seconds. 4735 */ 4736 const bool recurTimeVaries = (recurType == KARecurrence::MINUTELY); 4737 const bool repeatTimeVaries = (mRepetition && !mRepetition.isDaily()); 4738 4739 if (!recurTimeVaries && !repeatTimeVaries) 4740 { 4741 // The alarm always occurs at the same time of day. 4742 // Check whether it can ever occur during working hours. 4743 if (!mayOccurDailyDuringWork(kdt)) 4744 return; // never occurs during working hours 4745 4746 // Find the next working day it occurs on 4747 bool repetition = false; 4748 unsigned days = 0; 4749 for (; ;) 4750 { 4751 const KAEvent::OccurType type = nextOccurrence(kdt, newdt, KAEvent::Repeats::Return); 4752 if (!newdt.isValid()) 4753 return; 4754 repetition = (type & KAEvent::OccurType::Repeat); 4755 kdt = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 4756 const int day = kdt.date().dayOfWeek() - 1; 4757 if (mWorkDays.testBit(day)) 4758 break; // found a working day occurrence 4759 // Prevent indefinite looping (which should never happen anyway) 4760 if (!repetition) 4761 { 4762 if ((days & allDaysMask) == allDaysMask) 4763 return; // found a recurrence on every possible day of the week!?! 4764 days |= 1 << day; 4765 } 4766 } 4767 mMainWorkTrigger = nextTriggerWT; 4768 mMainWorkTrigger.setDate(kdt.date()); 4769 mMainWorkTrigger = mMainWorkTrigger.toTimeSpec(nextTrigger.timeSpec()); 4770 mAllWorkTrigger = repetition ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder); 4771 return; 4772 } 4773 4774 // The alarm occurs at different times of day. 4775 // We may need to check for a full annual cycle of seasonal time changes, in 4776 // case it only occurs during working hours after a time change. 4777 const QTimeZone tz = kdt.timeZone(); 4778 // Get time zone transitions for the next 10 years. 4779 const QDateTime endTransitionsTime = QDateTime::currentDateTimeUtc().addYears(10); 4780 const QTimeZone::OffsetDataList tzTransitions = tz.transitions(mStartDateTime.qDateTime(), endTransitionsTime); 4781 4782 if (recurTimeVaries) 4783 { 4784 /* The alarm recurs at regular clock intervals, at different times of day. 4785 * Note that for this type of recurrence, it's necessary to avoid the 4786 * performance overhead of Recurrence class calls since these can in the 4787 * worst case cause the program to hang for a significant length of time. 4788 * In this case, we can calculate the next recurrence by simply adding the 4789 * recurrence interval, since KAlarm offers no facility to regularly miss 4790 * recurrences. (But exception dates/times need to be taken into account.) 4791 */ 4792 KADateTime kdtRecur; 4793 int repeatFreq = 0; 4794 int repeatNum = 0; 4795 if (mRepetition) 4796 { 4797 // It's a repetition inside a recurrence, each of which occurs 4798 // at different times of day (bearing in mind that the repetition 4799 // may occur at daily intervals after each recurrence). 4800 // Find the previous recurrence (as opposed to sub-repetition) 4801 repeatFreq = mRepetition.intervalSeconds(); 4802 previousOccurrence(kdt.addSecs(1), newdt, false); 4803 if (!newdt.isValid()) 4804 return; // this should never happen 4805 kdtRecur = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 4806 repeatNum = kdtRecur.secsTo(kdt) / repeatFreq; 4807 kdt = kdtRecur.addSecs(repeatNum * repeatFreq); 4808 } 4809 else 4810 { 4811 // There is no sub-repetition. 4812 // (N.B. Sub-repetitions can't exist without a recurrence.) 4813 // Check until the original time wraps round, but ensure that 4814 // if there are seasonal time changes, that all other subsequent 4815 // time offsets within the next year are checked. 4816 // This does not guarantee to find the next working time, 4817 // particularly if there are exceptions, but it's a 4818 // reasonable try. 4819 kdtRecur = kdt; 4820 } 4821 QTime firstTime = kdtRecur.time(); 4822 int firstOffset = kdtRecur.utcOffset(); 4823 int currentOffset = firstOffset; 4824 int dayRecur = kdtRecur.date().dayOfWeek() - 1; // Monday = 0 4825 int firstDay = dayRecur; 4826 QDate finalDate; 4827 const bool subdaily = (repeatFreq < 24 * 3600); 4828 // int period = mRecurrence->frequency() % (24*60); // it is by definition a MINUTELY recurrence 4829 // int limit = (24*60 + period - 1) / period; // number of times until recurrence wraps round 4830 int transitionIndex = -1; 4831 for (int n = 0; n < 7 * 24 * 60; ++n) 4832 { 4833 if (mRepetition) 4834 { 4835 // Check the sub-repetitions for this recurrence 4836 for (; ;) 4837 { 4838 // Find the repeat count to the next start of the working day 4839 const int inc = subdaily ? nextWorkRepetition(kdt) : 1; 4840 repeatNum += inc; 4841 if (repeatNum > mRepetition.count()) 4842 break; 4843 kdt = kdt.addSecs(inc * repeatFreq); 4844 const QTime t = kdt.time(); 4845 if (t >= mWorkDayStart && t < mWorkDayEnd) 4846 { 4847 if (mWorkDays.testBit(kdt.date().dayOfWeek() - 1)) 4848 { 4849 kdt = kdt.toTimeSpec(nextTrigger.timeSpec()); 4850 mMainWorkTrigger = mAllWorkTrigger = kdt; 4851 return; 4852 } 4853 } 4854 } 4855 repeatNum = 0; 4856 } 4857 nextOccurrence(kdtRecur, newdt, KAEvent::Repeats::Ignore); 4858 if (!newdt.isValid()) 4859 return; 4860 kdtRecur = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 4861 dayRecur = kdtRecur.date().dayOfWeek() - 1; // Monday = 0 4862 const QTime t = kdtRecur.time(); 4863 if (t >= mWorkDayStart && t < mWorkDayEnd) 4864 { 4865 if (mWorkDays.testBit(dayRecur)) 4866 { 4867 kdtRecur = kdtRecur.toTimeSpec(nextTrigger.timeSpec()); 4868 mMainWorkTrigger = kdtRecur; 4869 mAllWorkTrigger = kdtRecur.addSecs(-60 * reminder); 4870 return; 4871 } 4872 } 4873 if (kdtRecur.utcOffset() != currentOffset) 4874 currentOffset = kdtRecur.utcOffset(); 4875 if (t == firstTime && dayRecur == firstDay && currentOffset == firstOffset) 4876 { 4877 // We've wrapped round to the starting day and time. 4878 // If there are seasonal time changes, check for up 4879 // to the next year in other time offsets in case the 4880 // alarm occurs inside working hours then. 4881 if (!finalDate.isValid()) 4882 finalDate = kdtRecur.date(); 4883 const int i = KAEventPrivate::transitionIndex(kdtRecur.toUtc().qDateTime(), tzTransitions); 4884 if (i < 0) 4885 return; 4886 if (i > transitionIndex) 4887 transitionIndex = i; 4888 if (++transitionIndex >= tzTransitions.count()) 4889 return; 4890 previousOccurrence(KADateTime(tzTransitions[transitionIndex].atUtc), newdt, false); 4891 kdtRecur = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 4892 if (finalDate.daysTo(kdtRecur.date()) > 365) 4893 return; 4894 firstTime = kdtRecur.time(); 4895 firstOffset = kdtRecur.utcOffset(); 4896 currentOffset = firstOffset; 4897 firstDay = kdtRecur.date().dayOfWeek() - 1; 4898 } 4899 kdt = kdtRecur; 4900 } 4901 //qCDebug(KALARMCAL_LOG)<<"-----exit loop: count="<<limit<<endl; 4902 return; // too many iterations 4903 } 4904 4905 if (repeatTimeVaries) 4906 { 4907 /* There's a sub-repetition which occurs at different times of 4908 * day, inside a recurrence which occurs at the same time of day. 4909 * We potentially need to check recurrences starting on each day. 4910 * Then, it is still possible that a working time sub-repetition 4911 * could occur immediately after a seasonal time change. 4912 */ 4913 // Find the previous recurrence (as opposed to sub-repetition) 4914 const int repeatFreq = mRepetition.intervalSeconds(); 4915 previousOccurrence(kdt.addSecs(1), newdt, false); 4916 if (!newdt.isValid()) 4917 return; // this should never happen 4918 KADateTime kdtRecur = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 4919 const bool recurDuringWork = (kdtRecur.time() >= mWorkDayStart && kdtRecur.time() < mWorkDayEnd); 4920 4921 // Use the previous recurrence as a base for checking whether 4922 // our tests have wrapped round to the same time/day of week. 4923 const bool subdaily = (repeatFreq < 24 * 3600); 4924 unsigned days = 0; 4925 bool checkTimeChangeOnly = false; 4926 int transitionIndex = -1; 4927 for (int limit = 10; --limit >= 0;) 4928 { 4929 // Check the next seasonal time change (for an arbitrary 10 times, 4930 // even though that might not guarantee the correct result) 4931 QDate dateRecur = kdtRecur.date(); 4932 int repeatNum = kdtRecur.secsTo(kdt) / repeatFreq; 4933 kdt = kdtRecur.addSecs(repeatNum * repeatFreq); 4934 4935 // Find the next recurrence, which sets the limit on possible sub-repetitions. 4936 // Note that for a monthly recurrence, for example, a sub-repetition could 4937 // be defined which is longer than the recurrence interval in short months. 4938 // In these cases, the sub-repetition is truncated by the following 4939 // recurrence. 4940 nextOccurrence(kdtRecur, newdt, KAEvent::Repeats::Ignore); 4941 KADateTime kdtNextRecur = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 4942 4943 int repeatsToCheck = mRepetition.count(); 4944 int repeatsDuringWork = 0; // 0=unknown, 1=does, -1=never 4945 for (; ;) 4946 { 4947 // Check the sub-repetitions for this recurrence 4948 if (repeatsDuringWork >= 0) 4949 { 4950 for (; ;) 4951 { 4952 // Find the repeat count to the next start of the working day 4953 int inc = subdaily ? nextWorkRepetition(kdt) : 1; 4954 repeatNum += inc; 4955 const bool pastEnd = (repeatNum > mRepetition.count()); 4956 if (pastEnd) 4957 inc -= repeatNum - mRepetition.count(); 4958 repeatsToCheck -= inc; 4959 kdt = kdt.addSecs(inc * repeatFreq); 4960 if (kdtNextRecur.isValid() && kdt >= kdtNextRecur) 4961 { 4962 // This sub-repetition is past the next recurrence, 4963 // so start the check again from the next recurrence. 4964 repeatsToCheck = mRepetition.count(); 4965 break; 4966 } 4967 if (pastEnd) 4968 break; 4969 const QTime t = kdt.time(); 4970 if (t >= mWorkDayStart && t < mWorkDayEnd) 4971 { 4972 if (mWorkDays.testBit(kdt.date().dayOfWeek() - 1)) 4973 { 4974 kdt = kdt.toTimeSpec(nextTrigger.timeSpec()); 4975 mMainWorkTrigger = mAllWorkTrigger = kdt; 4976 return; 4977 } 4978 repeatsDuringWork = 1; 4979 } 4980 else if (!repeatsDuringWork && repeatsToCheck <= 0) 4981 { 4982 // Sub-repetitions never occur during working hours 4983 repeatsDuringWork = -1; 4984 break; 4985 } 4986 } 4987 } 4988 repeatNum = 0; 4989 if (repeatsDuringWork < 0 && !recurDuringWork) 4990 break; // it never occurs during working hours 4991 4992 // Check the next recurrence 4993 if (!kdtNextRecur.isValid()) 4994 return; 4995 if (checkTimeChangeOnly || (days & allDaysMask) == allDaysMask) 4996 break; // found a recurrence on every possible day of the week!?! 4997 kdtRecur = kdtNextRecur; 4998 nextOccurrence(kdtRecur, newdt, KAEvent::Repeats::Ignore); 4999 kdtNextRecur = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 5000 dateRecur = kdtRecur.date(); 5001 const int dayRecur = dateRecur.dayOfWeek() - 1; // Monday = 0 5002 if (recurDuringWork && mWorkDays.testBit(dayRecur)) 5003 { 5004 kdtRecur = kdtRecur.toTimeSpec(nextTrigger.timeSpec()); 5005 mMainWorkTrigger = kdtRecur; 5006 mAllWorkTrigger = kdtRecur.addSecs(-60 * reminder); 5007 return; 5008 } 5009 days |= 1 << dayRecur; 5010 kdt = kdtRecur; 5011 } 5012 5013 // Find the next recurrence before a seasonal time change, 5014 // and ensure the time change is after the last one processed. 5015 checkTimeChangeOnly = true; 5016 const int i = KAEventPrivate::transitionIndex(kdtRecur.toUtc().qDateTime(), tzTransitions); 5017 if (i < 0) 5018 return; 5019 if (i > transitionIndex) 5020 transitionIndex = i; 5021 if (++transitionIndex >= tzTransitions.count()) 5022 return; 5023 kdt = KADateTime(tzTransitions[transitionIndex].atUtc); 5024 previousOccurrence(kdt, newdt, false); 5025 kdt = kdt.toTimeSpec(mWorkDayTimeSpec); 5026 kdtRecur = newdt.toTimeSpec(mWorkDayTimeSpec).effectiveKDateTime(); 5027 } 5028 return; // not found - give up 5029 } 5030 } 5031 5032 /****************************************************************************** 5033 * Find the repeat count to the next start of a working day. 5034 * This allows for possible daylight saving time changes during the repetition. 5035 * Use for repetitions which occur at different times of day. 5036 */ 5037 int KAEventPrivate::nextWorkRepetition(const KADateTime& pre) const 5038 { 5039 Q_ASSERT(pre.timeSpec() == mWorkDayTimeSpec); 5040 5041 KADateTime nextWork(pre); 5042 if (pre.time() < mWorkDayStart) 5043 nextWork.setTime(mWorkDayStart); 5044 else 5045 { 5046 const int preDay = pre.date().dayOfWeek() - 1; // Monday = 0 5047 for (int n = 1; ; ++n) 5048 { 5049 if (n >= 7) 5050 return mRepetition.count() + 1; // should never happen 5051 if (mWorkDays.testBit((preDay + n) % 7)) 5052 { 5053 nextWork = nextWork.addDays(n); 5054 nextWork.setTime(mWorkDayStart); 5055 break; 5056 } 5057 } 5058 } 5059 return (pre.secsTo(nextWork) - 1) / mRepetition.intervalSeconds() + 1; 5060 } 5061 5062 /****************************************************************************** 5063 * Check whether an alarm which recurs at the same time of day can possibly 5064 * occur during working hours. 5065 * This does not determine whether it actually does, but rather whether it could 5066 * potentially given enough repetitions. 5067 * Reply = false if it can never occur during working hours, true if it might. 5068 */ 5069 bool KAEventPrivate::mayOccurDailyDuringWork(const KADateTime& kdt) const 5070 { 5071 Q_ASSERT(kdt.timeSpec() == mWorkDayTimeSpec); 5072 5073 if (!kdt.isDateOnly() 5074 && (kdt.time() < mWorkDayStart || kdt.time() >= mWorkDayEnd)) 5075 return false; // its time is outside working hours 5076 // Check if it always occurs on the same day of the week 5077 const Duration interval = mRecurrence->regularInterval(); 5078 if (!interval.isNull() && interval.isDaily() && !(interval.asDays() % 7)) 5079 { 5080 // It recurs weekly 5081 if (!mRepetition || (mRepetition.isDaily() && !(mRepetition.intervalDays() % 7))) 5082 return false; // any repetitions are also weekly 5083 // Repetitions are daily. Check if any occur on working days 5084 // by checking the first recurrence and up to 6 repetitions. 5085 int day = mRecurrence->startDateTime().toTimeSpec(mWorkDayTimeSpec).date().dayOfWeek() - 1; // Monday = 0 5086 const int repeatDays = mRepetition.intervalDays(); 5087 const int maxRepeat = (mRepetition.count() < 6) ? mRepetition.count() : 6; 5088 for (int i = 0; !mWorkDays.testBit(day); ++i, day = (day + repeatDays) % 7) 5089 { 5090 if (i >= maxRepeat) 5091 return false; // no working day occurrences 5092 } 5093 } 5094 return true; 5095 } 5096 5097 /****************************************************************************** 5098 * Set the specified alarm to be an audio alarm with the given file name. 5099 */ 5100 void KAEventPrivate::setAudioAlarm(const Alarm::Ptr& alarm) const 5101 { 5102 alarm->setAudioAlarm(mAudioFile); // empty for a beep or for speaking 5103 if (mSoundVolume >= 0) 5104 alarm->setCustomProperty(KACalendar::APPNAME, VOLUME_PROPERTY, 5105 QStringLiteral("%1;%2;%3").arg(QString::number(mSoundVolume, 'f', 2), QString::number(mFadeVolume, 'f', 2), QString::number(mFadeSeconds))); 5106 } 5107 5108 /****************************************************************************** 5109 * Get the date/time of the next recurrence of the event, after the specified 5110 * date/time. 5111 * 'result' = date/time of next occurrence, or invalid date/time if none. 5112 */ 5113 KAEvent::OccurType KAEventPrivate::nextRecurrence(const KADateTime& preDateTime, DateTime& result) const 5114 { 5115 const KADateTime recurStart = mRecurrence->startDateTime(); 5116 KADateTime pre = preDateTime.toTimeSpec(mStartDateTime.timeSpec()); 5117 if (mStartDateTime.isDateOnly() && !pre.isDateOnly() && pre.time() < DateTime::startOfDay()) 5118 { 5119 pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come 5120 pre.setTime(DateTime::startOfDay()); 5121 } 5122 const KADateTime dt = mRecurrence->getNextDateTime(pre); 5123 result = dt; 5124 result.setDateOnly(mStartDateTime.isDateOnly()); 5125 if (!dt.isValid()) 5126 return KAEvent::OccurType::None; 5127 if (dt == recurStart) 5128 return KAEvent::OccurType::FirstOrOnly; 5129 if (mRecurrence->duration() >= 0 && dt == mRecurrence->endDateTime()) 5130 return KAEvent::OccurType::LastRecur; 5131 return result.isDateOnly() ? KAEvent::OccurType::RecurDate : KAEvent::OccurType::RecurDateTime; 5132 } 5133 5134 /****************************************************************************** 5135 * Validate the event's recurrence data, correcting any inconsistencies (which 5136 * should never occur!). 5137 * Reply = recurrence period type. 5138 */ 5139 KARecurrence::Type KAEventPrivate::checkRecur() const 5140 { 5141 if (mRecurrence) 5142 { 5143 KARecurrence::Type type = mRecurrence->type(); 5144 switch (type) 5145 { 5146 case KARecurrence::MINUTELY: // hourly 5147 case KARecurrence::DAILY: // daily 5148 case KARecurrence::WEEKLY: // weekly on multiple days of week 5149 case KARecurrence::MONTHLY_DAY: // monthly on multiple dates in month 5150 case KARecurrence::MONTHLY_POS: // monthly on multiple nth day of week 5151 case KARecurrence::ANNUAL_DATE: // annually on multiple months (day of month = start date) 5152 case KARecurrence::ANNUAL_POS: // annually on multiple nth day of week in multiple months 5153 return type; 5154 default: 5155 if (mRecurrence) 5156 const_cast<KAEventPrivate*>(this)->clearRecur(); // this shouldn't ever be necessary!! 5157 break; 5158 } 5159 } 5160 if (mRepetition) 5161 const_cast<KAEventPrivate*>(this)->clearRecur(); // this shouldn't ever be necessary!! 5162 return KARecurrence::NO_RECUR; 5163 } 5164 5165 /****************************************************************************** 5166 * If the calendar was written by a previous version of KAlarm, do any 5167 * necessary format conversions on the events to ensure that when the calendar 5168 * is saved, no information is lost or corrupted. 5169 * Reply = true if any conversions were done. 5170 */ 5171 bool KAEvent::convertKCalEvents(const Calendar::Ptr& calendar, int calendarVersion) 5172 { 5173 // KAlarm pre-0.9 codes held in the alarm's DESCRIPTION property 5174 static const QChar SEPARATOR = QLatin1Char(';'); 5175 static const QChar LATE_CANCEL_CODE = QLatin1Char('C'); 5176 static const QChar AT_LOGIN_CODE = QLatin1Char('L'); // subsidiary alarm at every login 5177 static const QChar DEFERRAL_CODE = QLatin1Char('D'); // extra deferred alarm 5178 static const QString TEXT_PREFIX = QStringLiteral("TEXT:"); 5179 static const QString FILE_PREFIX = QStringLiteral("FILE:"); 5180 static const QString COMMAND_PREFIX = QStringLiteral("CMD:"); 5181 5182 // KAlarm pre-0.9.2 codes held in the event's CATEGORY property 5183 static const QString BEEP_CATEGORY = QStringLiteral("BEEP"); 5184 5185 // KAlarm pre-1.1.1 LATECANCEL category with no parameter 5186 static const QString LATE_CANCEL_CAT = QStringLiteral("LATECANCEL"); 5187 5188 // KAlarm pre-1.3.0 TMPLDEFTIME category with no parameter 5189 static const QString TEMPL_DEF_TIME_CAT = QStringLiteral("TMPLDEFTIME"); 5190 5191 // KAlarm pre-1.3.1 XTERM category 5192 static const QString EXEC_IN_XTERM_CAT = QStringLiteral("XTERM"); 5193 5194 // KAlarm pre-1.9.0 categories 5195 static const QString DATE_ONLY_CATEGORY = QStringLiteral("DATE"); 5196 static const QString EMAIL_BCC_CATEGORY = QStringLiteral("BCC"); 5197 static const QString CONFIRM_ACK_CATEGORY = QStringLiteral("ACKCONF"); 5198 static const QString KORGANIZER_CATEGORY = QStringLiteral("KORG"); 5199 static const QString DEFER_CATEGORY = QStringLiteral("DEFER;"); 5200 static const QString ARCHIVE_CATEGORY = QStringLiteral("SAVE"); 5201 static const QString ARCHIVE_CATEGORIES = QStringLiteral("SAVE:"); 5202 static const QString LATE_CANCEL_CATEGORY = QStringLiteral("LATECANCEL;"); 5203 static const QString AUTO_CLOSE_CATEGORY = QStringLiteral("LATECLOSE;"); 5204 static const QString TEMPL_AFTER_TIME_CATEGORY = QStringLiteral("TMPLAFTTIME;"); 5205 static const QString KMAIL_SERNUM_CATEGORY = QStringLiteral("KMAIL:"); 5206 static const QString LOG_CATEGORY = QStringLiteral("LOG:"); 5207 5208 // KAlarm pre-1.5.0/1.9.9 properties 5209 static const QByteArray KMAIL_ID_PROPERTY("KMAILID"); // X-KDE-KALARM-KMAILID property 5210 5211 // KAlarm pre-2.6.0 properties 5212 static const QByteArray ARCHIVE_PROPERTY("ARCHIVE"); // X-KDE-KALARM-ARCHIVE property 5213 static const QString ARCHIVE_REMINDER_ONCE_TYPE = QStringLiteral("ONCE"); 5214 static const QString REMINDER_ONCE_TYPE = QStringLiteral("REMINDER_ONCE"); 5215 static const QByteArray EMAIL_ID_PROPERTY("EMAILID"); // X-KDE-KALARM-EMAILID property 5216 static const QByteArray SPEAK_PROPERTY("SPEAK"); // X-KDE-KALARM-SPEAK property 5217 static const QByteArray CANCEL_ON_ERROR_PROPERTY("ERRCANCEL");// X-KDE-KALARM-ERRCANCEL property 5218 static const QByteArray DONT_SHOW_ERROR_PROPERTY("ERRNOSHOW");// X-KDE-KALARM-ERRNOSHOW property 5219 5220 bool adjustSummerTime = false; 5221 if (calendarVersion == -Version(0, 5, 7)) 5222 { 5223 // The calendar file was written by the KDE 3.0.0 version of KAlarm 0.5.7. 5224 // Summer time was ignored when converting to UTC. 5225 calendarVersion = -calendarVersion; 5226 adjustSummerTime = true; 5227 } 5228 5229 if (calendarVersion >= currentCalendarVersion()) 5230 return false; 5231 5232 qCDebug(KALARMCAL_LOG) << "Adjusting version" << calendarVersion; 5233 const bool pre_0_7 = (calendarVersion < Version(0, 7, 0)); 5234 const bool pre_0_9 = (calendarVersion < Version(0, 9, 0)); 5235 const bool pre_0_9_2 = (calendarVersion < Version(0, 9, 2)); 5236 const bool pre_1_1_1 = (calendarVersion < Version(1, 1, 1)); 5237 const bool pre_1_2_1 = (calendarVersion < Version(1, 2, 1)); 5238 const bool pre_1_3_0 = (calendarVersion < Version(1, 3, 0)); 5239 const bool pre_1_3_1 = (calendarVersion < Version(1, 3, 1)); 5240 const bool pre_1_4_14 = (calendarVersion < Version(1, 4, 14)); 5241 const bool pre_1_5_0 = (calendarVersion < Version(1, 5, 0)); 5242 const bool pre_1_9_0 = (calendarVersion < Version(1, 9, 0)); 5243 const bool pre_1_9_2 = (calendarVersion < Version(1, 9, 2)); 5244 const bool pre_1_9_7 = (calendarVersion < Version(1, 9, 7)); 5245 const bool pre_1_9_9 = (calendarVersion < Version(1, 9, 9)); 5246 const bool pre_1_9_10 = (calendarVersion < Version(1, 9, 10)); 5247 const bool pre_2_2_9 = (calendarVersion < Version(2, 2, 9)); 5248 const bool pre_2_3_0 = (calendarVersion < Version(2, 3, 0)); 5249 const bool pre_2_3_2 = (calendarVersion < Version(2, 3, 2)); 5250 const bool pre_2_7_0 = (calendarVersion < Version(2, 7, 0)); 5251 Q_ASSERT(currentCalendarVersion() == Version(2, 7, 0)); 5252 5253 const QTimeZone localZone = QTimeZone::systemTimeZone(); 5254 5255 bool converted = false; 5256 const Event::List events = calendar->rawEvents(); 5257 for (Event::Ptr event : events) 5258 { 5259 const Alarm::List alarms = event->alarms(); 5260 if (alarms.isEmpty()) 5261 continue; // KAlarm isn't interested in events without alarms 5262 event->startUpdates(); // prevent multiple update notifications 5263 const bool readOnly = event->isReadOnly(); 5264 if (readOnly) 5265 event->setReadOnly(false); 5266 QStringList cats = event->categories(); 5267 bool addLateCancel = false; 5268 QStringList flags; 5269 5270 if (pre_0_7 && event->allDay()) 5271 { 5272 // It's a KAlarm pre-0.7 calendar file. 5273 // Ensure that when the calendar is saved, the alarm time isn't lost. 5274 event->setAllDay(false); 5275 } 5276 5277 if (pre_0_9) 5278 { 5279 /* 5280 * It's a KAlarm pre-0.9 calendar file. 5281 * All alarms were of type DISPLAY. Instead of the X-KDE-KALARM-TYPE 5282 * alarm property, characteristics were stored as a prefix to the 5283 * alarm DESCRIPTION property, as follows: 5284 * SEQNO;[FLAGS];TYPE:TEXT 5285 * where 5286 * SEQNO = sequence number of alarm within the event 5287 * FLAGS = C for late-cancel, L for repeat-at-login, D for deferral 5288 * TYPE = TEXT or FILE or CMD 5289 * TEXT = message text, file name/URL or command 5290 */ 5291 for (Alarm::Ptr alarm : alarms) 5292 { 5293 bool atLogin = false; 5294 bool deferral = false; 5295 bool lateCancel = false; 5296 KAAlarm::Action action = KAAlarm::Action::Message; 5297 const QString txt = alarm->text(); 5298 const int length = txt.length(); 5299 int i = 0; 5300 if (txt[0].isDigit()) 5301 { 5302 while (++i < length && txt[i].isDigit()) {} 5303 if (i < length && txt[i++] == SEPARATOR) 5304 { 5305 while (i < length) 5306 { 5307 const QChar ch = txt[i++]; 5308 if (ch == SEPARATOR) 5309 break; 5310 if (ch == LATE_CANCEL_CODE) 5311 lateCancel = true; 5312 else if (ch == AT_LOGIN_CODE) 5313 atLogin = true; 5314 else if (ch == DEFERRAL_CODE) 5315 deferral = true; 5316 } 5317 } 5318 else 5319 i = 0; // invalid prefix 5320 } 5321 if (txt.indexOf(TEXT_PREFIX, i) == i) 5322 i += TEXT_PREFIX.length(); 5323 else if (txt.indexOf(FILE_PREFIX, i) == i) 5324 { 5325 action = KAAlarm::Action::File; 5326 i += FILE_PREFIX.length(); 5327 } 5328 else if (txt.indexOf(COMMAND_PREFIX, i) == i) 5329 { 5330 action = KAAlarm::Action::Command; 5331 i += COMMAND_PREFIX.length(); 5332 } 5333 else 5334 i = 0; 5335 const QString altxt = txt.mid(i); 5336 5337 QStringList types; 5338 switch (action) 5339 { 5340 case KAAlarm::Action::File: 5341 types += KAEventPrivate::FILE_TYPE; 5342 [[fallthrough]]; // fall through to Message 5343 case KAAlarm::Action::Message: 5344 alarm->setDisplayAlarm(altxt); 5345 break; 5346 case KAAlarm::Action::Command: 5347 setProcedureAlarm(alarm, altxt); 5348 break; 5349 case KAAlarm::Action::Email: // email alarms were introduced in KAlarm 0.9 5350 case KAAlarm::Action::Audio: // audio alarms (with no display) were introduced in KAlarm 2.3.2 5351 break; 5352 } 5353 if (atLogin) 5354 { 5355 types += KAEventPrivate::AT_LOGIN_TYPE; 5356 lateCancel = false; 5357 } 5358 else if (deferral) 5359 types += KAEventPrivate::TIME_DEFERRAL_TYPE; 5360 if (lateCancel) 5361 addLateCancel = true; 5362 if (types.count() > 0) 5363 alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY, types.join(QLatin1Char(','))); 5364 5365 if (pre_0_7 && alarm->repeatCount() > 0 && alarm->snoozeTime().value() > 0) 5366 { 5367 // It's a KAlarm pre-0.7 calendar file. 5368 // Minutely recurrences were stored differently. 5369 Recurrence* recur = event->recurrence(); 5370 if (recur && recur->recurs()) 5371 { 5372 recur->setMinutely(alarm->snoozeTime().asSeconds() / 60); 5373 recur->setDuration(alarm->repeatCount() + 1); 5374 alarm->setRepeatCount(0); 5375 alarm->setSnoozeTime(0); 5376 } 5377 } 5378 5379 if (adjustSummerTime) 5380 { 5381 // The calendar file was written by the KDE 3.0.0 version of KAlarm 0.5.7. 5382 // Summer time was ignored when converting to UTC. 5383 KADateTime dt(alarm->time()); 5384 const qint64 t64 = dt.toSecsSinceEpoch(); 5385 const time_t t = (static_cast<quint64>(t64) >= uint(-1)) ? uint(-1) : static_cast<uint>(t64); 5386 const struct tm* dtm = localtime(&t); 5387 if (dtm->tm_isdst) 5388 { 5389 dt = dt.addSecs(-3600); 5390 alarm->setTime(dt.qDateTime()); 5391 } 5392 } 5393 } 5394 } 5395 5396 if (pre_0_9_2) 5397 { 5398 /* 5399 * It's a KAlarm pre-0.9.2 calendar file. 5400 * For the archive calendar, set the CREATED time to the DTEND value. 5401 * Convert date-only DTSTART to date/time, and add category "DATE". 5402 * Set the DTEND time to the DTSTART time. 5403 * Convert all alarm times to DTSTART offsets. 5404 * For display alarms, convert the first unlabelled category to an 5405 * X-KDE-KALARM-FONTCOLOR property. 5406 * Convert BEEP category into an audio alarm with no audio file. 5407 */ 5408 if (CalEvent::status(event) == CalEvent::ARCHIVED) 5409 event->setCreated(event->dtEnd()); 5410 QDateTime start = event->dtStart(); 5411 if (event->allDay()) 5412 { 5413 start.setTime(QTime(0, 0)); 5414 flags += KAEventPrivate::DATE_ONLY_FLAG; 5415 } 5416 event->setDtEnd(QDateTime()); 5417 5418 for (Alarm::Ptr alarm : alarms) 5419 alarm->setStartOffset(start.secsTo(alarm->time())); 5420 5421 if (!cats.isEmpty()) 5422 { 5423 for (const Alarm::Ptr& alarm : alarms) 5424 { //clazy:exclude=range-loop Can't use reference because 'alarms' is const 5425 if (alarm->type() == Alarm::Display) 5426 { 5427 alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FONT_COLOUR_PROPERTY, 5428 QStringLiteral("%1;;").arg(cats.at(0))); 5429 } 5430 } 5431 cats.removeAt(0); 5432 } 5433 5434 { 5435 int i = cats.indexOf(BEEP_CATEGORY); 5436 if (i >= 0) 5437 { 5438 cats.removeAt(i); 5439 5440 Alarm::Ptr alarm = event->newAlarm(); 5441 alarm->setEnabled(true); 5442 alarm->setAudioAlarm(); 5443 QDateTime dt = event->dtStart(); // default 5444 5445 // Parse and order the alarms to know which one's date/time to use 5446 KAEventPrivate::AlarmMap alarmMap; 5447 KAEventPrivate::readAlarms(event, &alarmMap); 5448 KAEventPrivate::AlarmMap::ConstIterator it = alarmMap.constBegin(); 5449 if (it != alarmMap.constEnd()) 5450 { 5451 dt = it.value().alarm->time(); 5452 break; 5453 } 5454 alarm->setStartOffset(start.secsTo(dt)); 5455 break; 5456 } 5457 } 5458 } 5459 5460 if (pre_1_1_1) 5461 { 5462 /* 5463 * It's a KAlarm pre-1.1.1 calendar file. 5464 * Convert simple LATECANCEL category to LATECANCEL:n where n = minutes late. 5465 */ 5466 int i; 5467 while ((i = cats.indexOf(LATE_CANCEL_CAT)) >= 0) 5468 { 5469 cats.removeAt(i); 5470 addLateCancel = true; 5471 } 5472 } 5473 5474 if (pre_1_2_1) 5475 { 5476 /* 5477 * It's a KAlarm pre-1.2.1 calendar file. 5478 * Convert email display alarms from translated to untranslated header prefixes. 5479 */ 5480 for (Alarm::Ptr alarm : alarms) 5481 { 5482 if (alarm->type() == Alarm::Display) 5483 { 5484 const QString oldtext = alarm->text(); 5485 const QString newtext = AlarmText::toCalendarText(oldtext); 5486 if (oldtext != newtext) 5487 alarm->setDisplayAlarm(newtext); 5488 } 5489 } 5490 } 5491 5492 if (pre_1_3_0) 5493 { 5494 /* 5495 * It's a KAlarm pre-1.3.0 calendar file. 5496 * Convert simple TMPLDEFTIME category to TMPLAFTTIME:n where n = minutes after. 5497 */ 5498 int i; 5499 while ((i = cats.indexOf(TEMPL_DEF_TIME_CAT)) >= 0) 5500 { 5501 cats.removeAt(i); 5502 (flags += KAEventPrivate::TEMPL_AFTER_TIME_FLAG) += QStringLiteral("0"); 5503 } 5504 } 5505 5506 if (pre_1_3_1) 5507 { 5508 /* 5509 * It's a KAlarm pre-1.3.1 calendar file. 5510 * Convert simple XTERM category to LOG:xterm: 5511 */ 5512 int i; 5513 while ((i = cats.indexOf(EXEC_IN_XTERM_CAT)) >= 0) 5514 { 5515 cats.removeAt(i); 5516 event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::LOG_PROPERTY, KAEventPrivate::xtermURL); 5517 } 5518 } 5519 5520 if (pre_1_9_0) 5521 { 5522 /* 5523 * It's a KAlarm pre-1.9 calendar file. 5524 * Add the X-KDE-KALARM-STATUS custom property. 5525 * Convert KAlarm categories to custom fields. 5526 */ 5527 CalEvent::setStatus(event, CalEvent::status(event)); 5528 for (int i = 0; i < cats.count();) 5529 { 5530 const QString cat = cats.at(i); 5531 if (cat == DATE_ONLY_CATEGORY) 5532 flags += KAEventPrivate::DATE_ONLY_FLAG; 5533 else if (cat == CONFIRM_ACK_CATEGORY) 5534 flags += KAEventPrivate::CONFIRM_ACK_FLAG; 5535 else if (cat == EMAIL_BCC_CATEGORY) 5536 flags += KAEventPrivate::EMAIL_BCC_FLAG; 5537 else if (cat == KORGANIZER_CATEGORY) 5538 flags += KAEventPrivate::KORGANIZER_FLAG; 5539 else if (cat.startsWith(DEFER_CATEGORY)) 5540 (flags += KAEventPrivate::DEFER_FLAG) += cat.mid(DEFER_CATEGORY.length()); 5541 else if (cat.startsWith(TEMPL_AFTER_TIME_CATEGORY)) 5542 (flags += KAEventPrivate::TEMPL_AFTER_TIME_FLAG) += cat.mid(TEMPL_AFTER_TIME_CATEGORY.length()); 5543 else if (cat.startsWith(LATE_CANCEL_CATEGORY)) 5544 (flags += KAEventPrivate::LATE_CANCEL_FLAG) += cat.mid(LATE_CANCEL_CATEGORY.length()); 5545 else if (cat.startsWith(AUTO_CLOSE_CATEGORY)) 5546 (flags += KAEventPrivate::AUTO_CLOSE_FLAG) += cat.mid(AUTO_CLOSE_CATEGORY.length()); 5547 else if (cat.startsWith(KMAIL_SERNUM_CATEGORY)) 5548 (flags += KAEventPrivate::KMAIL_ITEM_FLAG) += cat.mid(KMAIL_SERNUM_CATEGORY.length()); 5549 else if (cat == ARCHIVE_CATEGORY) 5550 event->setCustomProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY, QStringLiteral("0")); 5551 else if (cat.startsWith(ARCHIVE_CATEGORIES)) 5552 event->setCustomProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY, cat.mid(ARCHIVE_CATEGORIES.length())); 5553 else if (cat.startsWith(LOG_CATEGORY)) 5554 event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::LOG_PROPERTY, cat.mid(LOG_CATEGORY.length())); 5555 else 5556 { 5557 ++i; // Not a KAlarm category, so leave it 5558 continue; 5559 } 5560 cats.removeAt(i); 5561 } 5562 } 5563 5564 if (pre_1_9_2) 5565 { 5566 /* 5567 * It's a KAlarm pre-1.9.2 calendar file. 5568 * Convert from clock time to the local system time zone. 5569 */ 5570 event->shiftTimes(localZone, localZone); 5571 converted = true; 5572 } 5573 5574 if (addLateCancel) 5575 (flags += KAEventPrivate::LATE_CANCEL_FLAG) += QStringLiteral("1"); 5576 if (!flags.isEmpty()) 5577 event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY, flags.join(KAEventPrivate::SC)); 5578 event->setCategories(cats); 5579 5580 if ((pre_1_4_14 || (pre_1_9_7 && !pre_1_9_0)) 5581 && event->recurrence() && event->recurrence()->recurs()) 5582 { 5583 /* 5584 * It's a KAlarm pre-1.4.14 or KAlarm 1.9 series pre-1.9.7 calendar file. 5585 * For recurring events, convert the main alarm offset to an absolute 5586 * time in the X-KDE-KALARM-NEXTRECUR property, and set main alarm 5587 * offsets to zero, and convert deferral alarm offsets to be relative to 5588 * the next recurrence. 5589 */ 5590 const QStringList flagsProp = event->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY).split(KAEventPrivate::SC, Qt::SkipEmptyParts); 5591 const bool dateOnly = flagsProp.contains(KAEventPrivate::DATE_ONLY_FLAG); 5592 KADateTime startDateTime(event->dtStart()); 5593 if (dateOnly) 5594 startDateTime.setDateOnly(true); 5595 // Convert the main alarm and get the next main trigger time from it 5596 KADateTime nextMainDateTime; 5597 bool mainExpired = true; 5598 for (Alarm::Ptr alarm : alarms) 5599 { 5600 if (!alarm->hasStartOffset()) 5601 continue; 5602 // Find whether the alarm triggers at the same time as the main 5603 // alarm, in which case its offset needs to be set to 0. The 5604 // following trigger with the main alarm: 5605 // - Additional audio alarm 5606 // - PRE_ACTION_TYPE 5607 // - POST_ACTION_TYPE 5608 // - DISPLAYING_TYPE 5609 bool mainAlarm = true; 5610 QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY); 5611 const QStringList types = property.split(QLatin1Char(','), Qt::SkipEmptyParts); 5612 for (const QString& type : types) 5613 { 5614 if (type == KAEventPrivate::AT_LOGIN_TYPE 5615 || type == KAEventPrivate::TIME_DEFERRAL_TYPE 5616 || type == KAEventPrivate::DATE_DEFERRAL_TYPE 5617 || type == KAEventPrivate::REMINDER_TYPE 5618 || type == REMINDER_ONCE_TYPE) 5619 { 5620 mainAlarm = false; 5621 break; 5622 } 5623 } 5624 if (mainAlarm) 5625 { 5626 if (mainExpired) 5627 { 5628 // All main alarms are supposed to be at the same time, so 5629 // don't readjust the event's time for subsequent main alarms. 5630 mainExpired = false; 5631 nextMainDateTime = KADateTime(alarm->time()); 5632 nextMainDateTime.setDateOnly(dateOnly); 5633 nextMainDateTime = nextMainDateTime.toTimeSpec(startDateTime); 5634 if (nextMainDateTime != startDateTime) 5635 { 5636 QDateTime dt = nextMainDateTime.qDateTime(); 5637 event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::NEXT_RECUR_PROPERTY, 5638 QLocale::c().toString(dt, dateOnly ? QStringLiteral("yyyyMMdd") : QStringLiteral("yyyyMMddThhmmss"))); 5639 } 5640 } 5641 alarm->setStartOffset(0); 5642 converted = true; 5643 } 5644 } 5645 int adjustment; 5646 if (mainExpired) 5647 { 5648 // It's an expired recurrence. 5649 // Set the alarm offset relative to the first actual occurrence 5650 // (taking account of possible exceptions). 5651 KADateTime dt(event->recurrence()->getNextDateTime(startDateTime.qDateTime().addDays(-1))); 5652 dt.setDateOnly(dateOnly); 5653 adjustment = startDateTime.secsTo(dt); 5654 } 5655 else 5656 adjustment = startDateTime.secsTo(nextMainDateTime); 5657 if (adjustment) 5658 { 5659 // Convert deferred alarms 5660 for (Alarm::Ptr alarm : alarms) 5661 { 5662 if (!alarm->hasStartOffset()) 5663 continue; 5664 const QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY); 5665 const QStringList types = property.split(QLatin1Char(','), Qt::SkipEmptyParts); 5666 for (const QString& type : types) 5667 { 5668 if (type == KAEventPrivate::TIME_DEFERRAL_TYPE 5669 || type == KAEventPrivate::DATE_DEFERRAL_TYPE) 5670 { 5671 alarm->setStartOffset(alarm->startOffset().asSeconds() - adjustment); 5672 converted = true; 5673 break; 5674 } 5675 } 5676 } 5677 } 5678 } 5679 5680 if (pre_1_5_0 || (pre_1_9_9 && !pre_1_9_0)) 5681 { 5682 /* 5683 * It's a KAlarm pre-1.5.0 or KAlarm 1.9 series pre-1.9.9 calendar file. 5684 * Convert email identity names to uoids. 5685 */ 5686 for (const Alarm::Ptr& alarm : alarms) 5687 { 5688 const QString name = alarm->customProperty(KACalendar::APPNAME, KMAIL_ID_PROPERTY); 5689 if (name.isEmpty()) 5690 continue; 5691 const uint id = Identities::identityUoid(name); 5692 if (id) 5693 alarm->setCustomProperty(KACalendar::APPNAME, EMAIL_ID_PROPERTY, QString::number(id)); 5694 alarm->removeCustomProperty(KACalendar::APPNAME, KMAIL_ID_PROPERTY); 5695 converted = true; 5696 } 5697 } 5698 5699 if (pre_1_9_10) 5700 { 5701 /* 5702 * It's a KAlarm pre-1.9.10 calendar file. 5703 * Convert simple repetitions without a recurrence, to a recurrence. 5704 */ 5705 if (KAEventPrivate::convertRepetition(event)) 5706 converted = true; 5707 } 5708 5709 if (pre_2_2_9 || (pre_2_3_2 && !pre_2_3_0)) 5710 { 5711 /* 5712 * It's a KAlarm pre-2.2.9 or KAlarm 2.3 series pre-2.3.2 calendar file. 5713 * Set the time in the calendar for all date-only alarms to 00:00. 5714 */ 5715 if (KAEventPrivate::convertStartOfDay(event)) 5716 converted = true; 5717 } 5718 5719 if (pre_2_7_0) 5720 { 5721 /* 5722 * It's a KAlarm pre-2.7.0 calendar file. 5723 * Archive and at-login flags were stored in event's ARCHIVE property when the main alarm had expired. 5724 * Reminder parameters were stored in event's ARCHIVE property when no reminder was pending. 5725 * Negative reminder periods (i.e. alarm offset > 0) were invalid, so convert to 0. 5726 * Now store reminder information in FLAGS property, whether reminder is pending or not. 5727 * Move EMAILID, SPEAK, ERRCANCEL and ERRNOSHOW alarm properties into new FLAGS property. 5728 */ 5729 bool flagsValid = false; 5730 QStringList preFlags; 5731 QString reminder; 5732 bool reminderOnce = false; 5733 const QString prop = event->customProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY); 5734 if (!prop.isEmpty()) 5735 { 5736 // Convert the event's ARCHIVE property to parameters in the FLAGS property 5737 preFlags = event->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY).split(KAEventPrivate::SC, Qt::SkipEmptyParts); 5738 preFlags << KAEventPrivate::ARCHIVE_FLAG; 5739 flagsValid = true; 5740 if (prop != QLatin1String("0")) 5741 { // "0" was a dummy parameter if no others were present 5742 // It's the archive property containing a reminder time and/or repeat-at-login flag. 5743 // This was present when no reminder/at-login alarm was pending. 5744 const QStringList list = prop.split(KAEventPrivate::SC, Qt::SkipEmptyParts); 5745 for (const QString& pr : list) 5746 { 5747 if (pr == KAEventPrivate::AT_LOGIN_TYPE) 5748 preFlags << KAEventPrivate::AT_LOGIN_TYPE; 5749 else if (pr == ARCHIVE_REMINDER_ONCE_TYPE) 5750 reminderOnce = true; 5751 else if (!pr.isEmpty() && !pr.startsWith(QChar::fromLatin1('-'))) 5752 reminder = pr; 5753 } 5754 } 5755 event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY, preFlags.join(KAEventPrivate::SC)); 5756 event->removeCustomProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY); 5757 } 5758 5759 for (Alarm::Ptr alarm : alarms) 5760 { 5761 // Convert EMAILID, SPEAK, ERRCANCEL, ERRNOSHOW properties 5762 QStringList alflags; 5763 QString property = alarm->customProperty(KACalendar::APPNAME, EMAIL_ID_PROPERTY); 5764 if (!property.isEmpty()) 5765 { 5766 alflags << KAEventPrivate::EMAIL_ID_FLAG << property; 5767 alarm->removeCustomProperty(KACalendar::APPNAME, EMAIL_ID_PROPERTY); 5768 } 5769 if (!alarm->customProperty(KACalendar::APPNAME, SPEAK_PROPERTY).isEmpty()) 5770 { 5771 alflags << KAEventPrivate::SPEAK_FLAG; 5772 alarm->removeCustomProperty(KACalendar::APPNAME, SPEAK_PROPERTY); 5773 } 5774 if (!alarm->customProperty(KACalendar::APPNAME, CANCEL_ON_ERROR_PROPERTY).isEmpty()) 5775 { 5776 alflags << KAEventPrivate::CANCEL_ON_ERROR_FLAG; 5777 alarm->removeCustomProperty(KACalendar::APPNAME, CANCEL_ON_ERROR_PROPERTY); 5778 } 5779 if (!alarm->customProperty(KACalendar::APPNAME, DONT_SHOW_ERROR_PROPERTY).isEmpty()) 5780 { 5781 alflags << KAEventPrivate::DONT_SHOW_ERROR_FLAG; 5782 alarm->removeCustomProperty(KACalendar::APPNAME, DONT_SHOW_ERROR_PROPERTY); 5783 } 5784 if (!alflags.isEmpty()) 5785 alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY, alflags.join(KAEventPrivate::SC)); 5786 5787 // Invalidate negative reminder periods in alarms 5788 if (!alarm->hasStartOffset()) 5789 continue; 5790 property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY); 5791 QStringList types = property.split(QChar::fromLatin1(','), Qt::SkipEmptyParts); 5792 const int r = types.indexOf(REMINDER_ONCE_TYPE); 5793 if (r >= 0) 5794 { 5795 // Move reminder-once indicator from the alarm to the event's FLAGS property 5796 types[r] = KAEventPrivate::REMINDER_TYPE; 5797 alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY, types.join(QChar::fromLatin1(','))); 5798 reminderOnce = true; 5799 } 5800 if (r >= 0 || types.contains(KAEventPrivate::REMINDER_TYPE)) 5801 { 5802 // The alarm is a reminder alarm 5803 const int offset = alarm->startOffset().asSeconds(); 5804 if (offset > 0) 5805 { 5806 alarm->setStartOffset(0); 5807 converted = true; 5808 } 5809 else if (offset < 0) 5810 reminder = reminderToString(offset / 60); 5811 } 5812 } 5813 if (!reminder.isEmpty()) 5814 { 5815 // Write reminder parameters into the event's FLAGS property 5816 if (!flagsValid) 5817 preFlags = event->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY).split(KAEventPrivate::SC, Qt::SkipEmptyParts); 5818 if (!preFlags.contains(KAEventPrivate::REMINDER_TYPE)) 5819 { 5820 preFlags += KAEventPrivate::REMINDER_TYPE; 5821 if (reminderOnce) 5822 preFlags += KAEventPrivate::REMINDER_ONCE_FLAG; 5823 preFlags += reminder; 5824 } 5825 } 5826 } 5827 5828 if (readOnly) 5829 event->setReadOnly(true); 5830 event->endUpdates(); // finally issue an update notification 5831 } 5832 return converted; 5833 } 5834 5835 /****************************************************************************** 5836 * Set the time for a date-only event to 00:00. 5837 * Reply = true if the event was updated. 5838 */ 5839 bool KAEventPrivate::convertStartOfDay(const Event::Ptr& event) 5840 { 5841 bool changed = false; 5842 const QTime midnight(0, 0); 5843 const QStringList flags = event->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY).split(KAEventPrivate::SC, Qt::SkipEmptyParts); 5844 if (flags.contains(KAEventPrivate::DATE_ONLY_FLAG)) 5845 { 5846 // It's an untimed event, so fix it 5847 const QDateTime oldDt = event->dtStart(); 5848 const int adjustment = oldDt.time().secsTo(midnight); 5849 if (adjustment) 5850 { 5851 event->setDtStart(QDateTime(oldDt.date(), midnight, oldDt.timeSpec())); 5852 int deferralOffset = 0; 5853 AlarmMap alarmMap; 5854 readAlarms(event, &alarmMap); 5855 for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it) 5856 { 5857 const AlarmData& data = it.value(); 5858 if (!data.alarm->hasStartOffset()) 5859 continue; 5860 if (data.timedDeferral) 5861 { 5862 // Found a timed deferral alarm, so adjust the offset 5863 deferralOffset = data.alarm->startOffset().asSeconds(); 5864 const_cast<Alarm*>(data.alarm.get())->setStartOffset(deferralOffset - adjustment); 5865 } 5866 else if (data.type == AUDIO_ALARM 5867 && data.alarm->startOffset().asSeconds() == deferralOffset) 5868 { 5869 // Audio alarm is set for the same time as the above deferral alarm 5870 const_cast<Alarm*>(data.alarm.get())->setStartOffset(deferralOffset - adjustment); 5871 } 5872 } 5873 changed = true; 5874 } 5875 } 5876 else 5877 { 5878 // It's a timed event. Fix any untimed alarms. 5879 bool foundDeferral = false; 5880 int deferralOffset = 0; 5881 int newDeferralOffset = 0; 5882 DateTime start; 5883 const KADateTime nextMainDateTime = readDateTime(event, false, false, start).kDateTime(); 5884 AlarmMap alarmMap; 5885 readAlarms(event, &alarmMap); 5886 for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it) 5887 { 5888 const AlarmData& data = it.value(); 5889 if (!data.alarm->hasStartOffset()) 5890 continue; 5891 if ((data.type & DEFERRED_ALARM) && !data.timedDeferral) 5892 { 5893 // Found a date-only deferral alarm, so adjust its time 5894 QDateTime altime = data.alarm->startOffset().end(nextMainDateTime.qDateTime()); 5895 altime.setTime(midnight); 5896 deferralOffset = data.alarm->startOffset().asSeconds(); 5897 newDeferralOffset = event->dtStart().secsTo(altime); 5898 const_cast<Alarm*>(data.alarm.get())->setStartOffset(newDeferralOffset); 5899 foundDeferral = true; 5900 changed = true; 5901 } 5902 else if (foundDeferral 5903 && data.type == AUDIO_ALARM 5904 && data.alarm->startOffset().asSeconds() == deferralOffset) 5905 { 5906 // Audio alarm is set for the same time as the above deferral alarm 5907 const_cast<Alarm*>(data.alarm.get())->setStartOffset(newDeferralOffset); 5908 changed = true; 5909 } 5910 } 5911 } 5912 return changed; 5913 } 5914 5915 /****************************************************************************** 5916 * Convert simple repetitions in an event without a recurrence, to a 5917 * recurrence. Repetitions which are an exact multiple of 24 hours are converted 5918 * to daily recurrences; else they are converted to minutely recurrences. Note 5919 * that daily and minutely recurrences produce different results when they span 5920 * a daylight saving time change. 5921 * Reply = true if any conversions were done. 5922 */ 5923 bool KAEventPrivate::convertRepetition(const Event::Ptr& event) 5924 { 5925 const Alarm::List alarms = event->alarms(); 5926 if (alarms.isEmpty()) 5927 return false; 5928 Recurrence* recur = event->recurrence(); // guaranteed to return non-null 5929 if (recur->recurs()) 5930 return false; 5931 bool converted = false; 5932 const bool readOnly = event->isReadOnly(); 5933 for (Alarm::Ptr alarm : alarms) 5934 { 5935 if (alarm->repeatCount() > 0 && alarm->snoozeTime().value() > 0) 5936 { 5937 if (!converted) 5938 { 5939 event->startUpdates(); // prevent multiple update notifications 5940 if (readOnly) 5941 event->setReadOnly(false); 5942 if ((alarm->snoozeTime().asSeconds() % (24 * 3600)) != 0) 5943 recur->setMinutely(alarm->snoozeTime().asSeconds() / 60); 5944 else 5945 recur->setDaily(alarm->snoozeTime().asDays()); 5946 recur->setDuration(alarm->repeatCount() + 1); 5947 converted = true; 5948 } 5949 alarm->setRepeatCount(0); 5950 alarm->setSnoozeTime(0); 5951 } 5952 } 5953 if (converted) 5954 { 5955 if (readOnly) 5956 event->setReadOnly(true); 5957 event->endUpdates(); // finally issue an update notification 5958 } 5959 return converted; 5960 } 5961 5962 /*============================================================================= 5963 = Class KAAlarm 5964 = Corresponds to a single KCalendarCore::Alarm instance. 5965 =============================================================================*/ 5966 5967 KAAlarm::KAAlarm() 5968 : d(new Private) 5969 { 5970 } 5971 5972 KAAlarm::Private::Private() = default; 5973 5974 KAAlarm::KAAlarm(const KAAlarm& other) 5975 : d(new Private(*other.d)) 5976 { 5977 } 5978 5979 KAAlarm::~KAAlarm() 5980 { 5981 delete d; 5982 } 5983 5984 KAAlarm& KAAlarm::operator=(const KAAlarm& other) 5985 { 5986 if (&other != this) 5987 *d = *other.d; 5988 return *this; 5989 } 5990 5991 KAAlarm::Action KAAlarm::action() const 5992 { 5993 return d->mActionType; 5994 } 5995 5996 bool KAAlarm::isValid() const 5997 { 5998 return d->mType != Type::Invalid; 5999 } 6000 6001 KAAlarm::Type KAAlarm::type() const 6002 { 6003 return d->mType; 6004 } 6005 6006 DateTime KAAlarm::dateTime(bool withRepeats) const 6007 { 6008 return (withRepeats && d->mNextRepeat && d->mRepetition) 6009 ? DateTime(d->mRepetition.duration(d->mNextRepeat).end(d->mNextMainDateTime.qDateTime())) 6010 : d->mNextMainDateTime; 6011 } 6012 6013 QDate KAAlarm::date() const 6014 { 6015 return d->mNextMainDateTime.date(); 6016 } 6017 6018 QTime KAAlarm::time() const 6019 { 6020 return d->mNextMainDateTime.effectiveTime(); 6021 } 6022 6023 bool KAAlarm::repeatAtLogin() const 6024 { 6025 return d->mRepeatAtLogin; 6026 } 6027 6028 bool KAAlarm::isReminder() const 6029 { 6030 return d->mType == Type::Reminder; 6031 } 6032 6033 bool KAAlarm::deferred() const 6034 { 6035 return d->mDeferred; 6036 } 6037 6038 bool KAAlarm::timedDeferral() const 6039 { 6040 return d->mDeferred && d->mTimedDeferral; 6041 } 6042 6043 void KAAlarm::setTime(const DateTime& dt) 6044 { 6045 d->mNextMainDateTime = dt; 6046 } 6047 6048 void KAAlarm::setTime(const KADateTime& dt) 6049 { 6050 d->mNextMainDateTime = dt; 6051 } 6052 6053 #ifdef KDE_NO_DEBUG_OUTPUT 6054 const char* KAAlarm::debugType(Type) 6055 { 6056 return ""; 6057 } 6058 #else 6059 const char* KAAlarm::debugType(Type type) 6060 { 6061 switch (type) 6062 { 6063 case Type::Main: return "MAIN"; 6064 case Type::Reminder: return "REMINDER"; 6065 case Type::Deferred: return "DEFERRED"; 6066 case Type::DeferredReminder: return "DEFERRED_REMINDER"; 6067 case Type::AtLogin: return "LOGIN"; 6068 case Type::Displaying: return "DISPLAYING"; 6069 default: return "INVALID"; 6070 } 6071 } 6072 #endif 6073 6074 /*============================================================================= 6075 = Class EmailAddressList 6076 =============================================================================*/ 6077 6078 /****************************************************************************** 6079 * Sets the list of email addresses, removing any empty addresses. 6080 * Reply = false if empty addresses were found. 6081 */ 6082 EmailAddressList& EmailAddressList::operator=(const Person::List& addresses) 6083 { 6084 clear(); 6085 for (const Person& addr : addresses) 6086 { 6087 if (!addr.email().isEmpty()) 6088 append(addr); 6089 } 6090 return *this; 6091 } 6092 6093 /****************************************************************************** 6094 * Return the email address list as a string list of email addresses. 6095 */ 6096 EmailAddressList::operator QStringList() const 6097 { 6098 int cnt = count(); 6099 QStringList list; 6100 list.reserve(cnt); 6101 for (int p = 0; p < cnt; ++p) 6102 list += address(p); 6103 return list; 6104 } 6105 6106 /****************************************************************************** 6107 * Return the email address list as a string, each address being delimited by 6108 * the specified separator string. 6109 */ 6110 QString EmailAddressList::join(const QString& separator) const 6111 { 6112 QString result; 6113 bool first = true; 6114 for (int p = 0, end = count(); p < end; ++p) 6115 { 6116 if (first) 6117 first = false; 6118 else 6119 result += separator; 6120 result += address(p); 6121 } 6122 return result; 6123 } 6124 6125 /****************************************************************************** 6126 * Convert one item into an email address, including name. 6127 */ 6128 QString EmailAddressList::address(int index) const 6129 { 6130 if (index < 0 || index > count()) 6131 return {}; 6132 QString result; 6133 bool quote = false; 6134 const Person& person = (*this)[index]; 6135 const QString name = person.name(); 6136 if (!name.isEmpty()) 6137 { 6138 // Need to enclose the name in quotes if it has any special characters 6139 for (int i = 0, len = name.length(); i < len; ++i) 6140 { 6141 const QChar ch = name[i]; 6142 if (!ch.isLetterOrNumber()) 6143 { 6144 quote = true; 6145 result += QLatin1Char('\"'); 6146 break; 6147 } 6148 } 6149 result += (*this)[index].name(); 6150 result += (quote ? QLatin1String("\" <") : QLatin1String(" <")); 6151 quote = true; // need angle brackets round email address 6152 } 6153 6154 result += person.email(); 6155 if (quote) 6156 result += QLatin1Char('>'); 6157 return result; 6158 } 6159 6160 /****************************************************************************** 6161 * Return a list of the pure email addresses, excluding names. 6162 */ 6163 QStringList EmailAddressList::pureAddresses() const 6164 { 6165 int cnt = count(); 6166 QStringList list; 6167 list.reserve(cnt); 6168 for (int p = 0; p < cnt; ++p) 6169 list += at(p).email(); 6170 return list; 6171 } 6172 6173 /****************************************************************************** 6174 * Return a list of the pure email addresses, excluding names, as a string. 6175 */ 6176 QString EmailAddressList::pureAddresses(const QString& separator) const 6177 { 6178 QString result; 6179 bool first = true; 6180 for (int p = 0, end = count(); p < end; ++p) 6181 { 6182 if (first) 6183 first = false; 6184 else 6185 result += separator; 6186 result += at(p).email(); 6187 } 6188 return result; 6189 } 6190 6191 /*============================================================================= 6192 = Static functions 6193 =============================================================================*/ 6194 6195 /****************************************************************************** 6196 * Set the specified alarm to be a procedure alarm with the given command line. 6197 * The command line is first split into its program file and arguments before 6198 * initialising the alarm. 6199 */ 6200 static void setProcedureAlarm(const Alarm::Ptr& alarm, const QString& commandLine) 6201 { 6202 //TODO: cater for environment variables prefixed to command 6203 QString command; 6204 QString arguments; 6205 QChar quoteChar; 6206 bool quoted = false; 6207 const uint posMax = commandLine.length(); 6208 uint pos; 6209 for (pos = 0; pos < posMax; ++pos) 6210 { 6211 const QChar ch = commandLine[pos]; 6212 if (quoted) 6213 { 6214 if (ch == quoteChar) 6215 { 6216 ++pos; // omit the quote character 6217 break; 6218 } 6219 command += ch; 6220 } 6221 else 6222 { 6223 bool done = false; 6224 switch (ch.toLatin1()) 6225 { 6226 case ' ': 6227 case ';': 6228 case '|': 6229 case '<': 6230 case '>': 6231 done = !command.isEmpty(); 6232 break; 6233 case '\'': 6234 case '"': 6235 if (command.isEmpty()) 6236 { 6237 // Start of a quoted string. Omit the quote character. 6238 quoted = true; 6239 quoteChar = ch; 6240 break; 6241 } 6242 [[fallthrough]]; // fall through to default 6243 default: 6244 command += ch; 6245 break; 6246 } 6247 if (done) 6248 break; 6249 } 6250 } 6251 6252 // Skip any spaces after the command 6253 for (; pos < posMax && commandLine[pos] == QLatin1Char(' '); ++pos) {} 6254 arguments = commandLine.mid(pos); 6255 6256 alarm->setProcedureAlarm(command, arguments); 6257 } 6258 6259 /****************************************************************************** 6260 * Converts a reminder interval into a parameter string for the 6261 * X-KDE-KALARM-FLAGS property. 6262 */ 6263 QString reminderToString(int minutes) 6264 { 6265 char unit = 'M'; 6266 int count = abs(minutes); 6267 if (count % 1440 == 0) 6268 { 6269 unit = 'D'; 6270 count /= 1440; 6271 } 6272 else if (count % 60 == 0) 6273 { 6274 unit = 'H'; 6275 count /= 60; 6276 } 6277 if (minutes < 0) 6278 count = -count; 6279 return QStringLiteral("%1%2").arg(count).arg(unit); 6280 } 6281 6282 } // namespace KAlarmCal 6283 6284 // vim: et sw=4: