• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.10.5 API Reference
  • KDE Home
  • Contact Us
 

KAlarm Library

  • kalarmcal
kaevent.cpp
1 /*
2  * kaevent.cpp - represents calendar events
3  * This file is part of kalarmcal library, which provides access to KAlarm
4  * calendar data.
5  * Copyright © 2001-2012 by David Jarvie <djarvie@kde.org>
6  *
7  * This library is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU Library General Public License as published
9  * by the Free Software Foundation; either version 2 of the License, or (at
10  * your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
15  * License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB. If not, write to the
19  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20  * MA 02110-1301, USA.
21  */
22 
23 #include "kaevent.h"
24 
25 #include "alarmtext.h"
26 #include "identities.h"
27 #include "version.h"
28 
29 #ifndef KALARMCAL_USE_KRESOURCES
30 #include <kcalcore/memorycalendar.h>
31 #else
32 #include <kcal/calendarlocal.h>
33 #endif
34 #include <kholidays/holidays.h>
35 using namespace KHolidays;
36 
37 #include <ksystemtimezone.h>
38 #include <klocale.h>
39 #ifdef KALARMCAL_USE_KRESOURCES
40 #include <kglobal.h>
41 #include <kconfiggroup.h>
42 #endif
43 #include <kdebug.h>
44 
45 #ifndef KALARMCAL_USE_KRESOURCES
46 using namespace KCalCore;
47 #else
48 using namespace KCal;
49 #endif
50 using namespace KHolidays;
51 
52 namespace KAlarmCal
53 {
54 
55 #ifndef KALARMCAL_USE_KRESOURCES
56 typedef KCalCore::Person EmailAddress;
57 class EmailAddressList : public KCalCore::Person::List
58 #else
59 typedef KCal::Person EmailAddress;
60 class EmailAddressList : public QList<KCal::Person>
61 #endif
62 {
63  public:
64 #ifndef KALARMCAL_USE_KRESOURCES
65  EmailAddressList() : KCalCore::Person::List() { }
66  EmailAddressList(const KCalCore::Person::List& list) { operator=(list); }
67  EmailAddressList& operator=(const KCalCore::Person::List&);
68 #else
69  EmailAddressList() : QList<KCal::Person>() { }
70  EmailAddressList(const QList<KCal::Person>& list) { operator=(list); }
71  EmailAddressList& operator=(const QList<KCal::Person>&);
72 #endif
73  operator QStringList() const;
74  QString join(const QString& separator) const;
75  QStringList pureAddresses() const;
76  QString pureAddresses(const QString& separator) const;
77  private:
78  QString address(int index) const;
79 };
80 
81 
82 class KAAlarm::Private
83 {
84  public:
85  Private();
86 
87  Action mActionType; // alarm action type
88  Type mType; // alarm type
89  DateTime mNextMainDateTime; // next time to display the alarm, excluding repetitions
90  Repetition mRepetition; // sub-repetition count and interval
91  int mNextRepeat; // repetition count of next due sub-repetition
92  bool mRepeatAtLogin; // whether to repeat the alarm at every login
93  bool mRecurs; // there is a recurrence rule for the alarm
94  bool mDeferred; // whether the alarm is an extra deferred/deferred-reminder alarm
95  bool mTimedDeferral; // if mDeferred = true: true if the deferral is timed, false if date-only
96 };
97 
98 
99 class KAEvent::Private : public QSharedData
100 {
101  public:
102  // Read-only internal flags additional to KAEvent::Flags enum values.
103  // NOTE: If any values are added to those in KAEvent::Flags, ensure
104  // that these values don't overlap them.
105  enum
106  {
107  REMINDER = 0x100000,
108  DEFERRAL = 0x200000,
109  TIMED_FLAG = 0x400000,
110  DATE_DEFERRAL = DEFERRAL,
111  TIME_DEFERRAL = DEFERRAL | TIMED_FLAG,
112  DISPLAYING_ = 0x800000,
113  READ_ONLY_FLAGS = 0xF00000
114  };
115  enum ReminderType // current active state of reminder
116  {
117  NO_REMINDER, // reminder is not due
118  ACTIVE_REMINDER, // reminder is due
119  HIDDEN_REMINDER // reminder-after is disabled due to main alarm being deferred past it
120  };
121  enum DeferType
122  {
123  NO_DEFERRAL = 0, // there is no deferred alarm
124  NORMAL_DEFERRAL, // the main alarm, a recurrence or a repeat is deferred
125  REMINDER_DEFERRAL // a reminder alarm is deferred
126  };
127  // Alarm types.
128  // This uses the same scheme as KAAlarm::Type, with some extra values.
129  // Note that the actual enum values need not be the same as in KAAlarm::Type.
130  enum AlarmType
131  {
132  INVALID_ALARM = 0, // Not an alarm
133  MAIN_ALARM = 1, // THE real alarm. Must be the first in the enumeration.
134  REMINDER_ALARM = 0x02, // Reminder in advance of/after the main alarm
135  DEFERRED_ALARM = 0x04, // Deferred alarm
136  DEFERRED_REMINDER_ALARM = REMINDER_ALARM | DEFERRED_ALARM, // Deferred reminder alarm
137  // The following values must be greater than the preceding ones, to
138  // ensure that in ordered processing they are processed afterwards.
139  AT_LOGIN_ALARM = 0x10, // Additional repeat-at-login trigger
140  DISPLAYING_ALARM = 0x20, // Copy of the alarm currently being displayed
141  // The following are extra internal KAEvent values
142  AUDIO_ALARM = 0x30, // sound to play when displaying the alarm
143  PRE_ACTION_ALARM = 0x40, // command to execute before displaying the alarm
144  POST_ACTION_ALARM = 0x50 // command to execute after the alarm window is closed
145  };
146 
147  struct AlarmData
148  {
149 #ifndef KALARMCAL_USE_KRESOURCES
150  Alarm::Ptr alarm;
151 #else
152  const Alarm* alarm;
153 #endif
154  QString cleanText; // text or audio file name
155  uint emailFromId;
156  QFont font;
157  QColor bgColour, fgColour;
158  float soundVolume;
159  float fadeVolume;
160  int fadeSeconds;
161  int repeatSoundPause;
162  int nextRepeat;
163  bool speak;
164  KAEvent::Private::AlarmType type;
165  KAAlarm::Action action;
166  int displayingFlags;
167  ExtraActionOptions extraActionOptions;
168  bool defaultFont;
169  bool isEmailText;
170  bool commandScript;
171  bool timedDeferral;
172  bool hiddenReminder;
173  };
174  typedef QMap<AlarmType, AlarmData> AlarmMap;
175 
176  Private();
177  Private(const KDateTime&, const QString& message, const QColor& bg, const QColor& fg,
178  const QFont& f, SubAction, int lateCancel, Flags flags, bool changesPending = false);
179 #ifndef KALARMCAL_USE_KRESOURCES
180  explicit Private(const KCalCore::Event::Ptr&);
181 #else
182  explicit Private(const KCal::Event*);
183 #endif
184  Private(const Private&);
185  ~Private() { delete mRecurrence; }
186  Private& operator=(const Private& e) { if (&e != this) copy(e); return *this; }
187 #ifndef KALARMCAL_USE_KRESOURCES
188  void set(const KCalCore::Event::Ptr&);
189 #else
190  void set(const KCal::Event*);
191 #endif
192  void set(const KDateTime&, const QString& message, const QColor& bg, const QColor& fg,
193  const QFont&, SubAction, int lateCancel, Flags flags, bool changesPending = false);
194  void setAudioFile(const QString& filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile);
195  OccurType setNextOccurrence(const KDateTime& preDateTime);
196  void setFirstRecurrence();
197  void setCategory(CalEvent::Type);
198  void setRepeatAtLogin(bool);
199  void setRepeatAtLoginTrue(bool clearReminder);
200  void setReminder(int minutes, bool onceOnly);
201  void activateReminderAfter(const DateTime& mainAlarmTime);
202  void defer(const DateTime&, bool reminder, bool adjustRecurrence = false);
203  void cancelDefer();
204 #ifndef KALARMCAL_USE_KRESOURCES
205  bool setDisplaying(const Private&, KAAlarm::Type, Akonadi::Collection::Id, const KDateTime& dt, bool showEdit, bool showDefer);
206  void reinstateFromDisplaying(const KCalCore::Event::Ptr&, Akonadi::Collection::Id&, bool& showEdit, bool& showDefer);
207 #else
208  bool setDisplaying(const Private&, KAAlarm::Type, const QString& resourceID, const KDateTime& dt, bool showEdit, bool showDefer);
209  void reinstateFromDisplaying(const KCal::Event*, QString& resourceID, bool& showEdit, bool& showDefer);
210  void setCommandError(const QString& configString);
211  void setCommandError(CmdErrType, bool writeConfig) const;
212 #endif
213  void startChanges() { ++mChangeCount; }
214  void endChanges();
215  void removeExpiredAlarm(KAAlarm::Type);
216  KAAlarm alarm(KAAlarm::Type) const;
217  KAAlarm firstAlarm() const;
218  KAAlarm nextAlarm(KAAlarm::Type) const;
219 #ifndef KALARMCAL_USE_KRESOURCES
220  bool updateKCalEvent(const KCalCore::Event::Ptr&, UidAction, bool setCustomProperties = true) const;
221 #else
222  bool updateKCalEvent(KCal::Event*, UidAction) const;
223 #endif
224  DateTime mainDateTime(bool withRepeats = false) const
225  { return (withRepeats && mNextRepeat && mRepetition)
226  ? mRepetition.duration(mNextRepeat).end(mNextMainDateTime.kDateTime()) : mNextMainDateTime; }
227  DateTime mainEndRepeatTime() const
228  { return mRepetition ? mRepetition.duration().end(mNextMainDateTime.kDateTime()) : mNextMainDateTime; }
229  DateTime deferralLimit(DeferLimitType* = 0) const;
230  Flags flags() const;
231  bool isWorkingTime(const KDateTime&) const;
232  bool setRepetition(const Repetition&);
233  bool occursAfter(const KDateTime& preDateTime, bool includeRepetitions) const;
234  OccurType nextOccurrence(const KDateTime& preDateTime, DateTime& result, OccurOption = IGNORE_REPETITION) const;
235  OccurType previousOccurrence(const KDateTime& afterDateTime, DateTime& result, bool includeRepetitions = false) const;
236  void setRecurrence(const KARecurrence&);
237 #ifndef KALARMCAL_USE_KRESOURCES
238  bool setRecur(KCalCore::RecurrenceRule::PeriodType, int freq, int count, const QDate& end, KARecurrence::Feb29Type = KARecurrence::Feb29_None);
239  bool setRecur(KCalCore::RecurrenceRule::PeriodType, int freq, int count, const KDateTime& end, KARecurrence::Feb29Type = KARecurrence::Feb29_None);
240 #else
241  bool setRecur(KCal::RecurrenceRule::PeriodType, int freq, int count, const QDate& end, KARecurrence::Feb29Type = KARecurrence::Feb29_None);
242  bool setRecur(KCal::RecurrenceRule::PeriodType, int freq, int count, const KDateTime& end, KARecurrence::Feb29Type = KARecurrence::Feb29_None);
243 #endif
244  KARecurrence::Type checkRecur() const;
245  void clearRecur();
246  void calcTriggerTimes() const;
247 #ifdef KDE_NO_DEBUG_OUTPUT
248  void dumpDebug() const { }
249 #else
250  void dumpDebug() const;
251 #endif
252 #ifndef KALARMCAL_USE_KRESOURCES
253  static bool convertRepetition(const KCalCore::Event::Ptr&);
254  static bool convertStartOfDay(const KCalCore::Event::Ptr&);
255  static DateTime readDateTime(const KCalCore::Event::Ptr&, bool dateOnly, DateTime& start);
256  static void readAlarms(const KCalCore::Event::Ptr&, void* alarmMap, bool cmdDisplay = false);
257  static void readAlarm(const KCalCore::Alarm::Ptr&, AlarmData&, bool audioMain, bool cmdDisplay = false);
258 #else
259  static bool convertRepetition(KCal::Event*);
260  static bool convertStartOfDay(KCal::Event*);
261  static DateTime readDateTime(const KCal::Event*, bool dateOnly, DateTime& start);
262  static void readAlarms(const KCal::Event*, void* alarmMap, bool cmdDisplay = false);
263  static void readAlarm(const KCal::Alarm*, AlarmData&, bool audioMain, bool cmdDisplay = false);
264 #endif
265 
266  private:
267  void copy(const Private&);
268  bool mayOccurDailyDuringWork(const KDateTime&) const;
269  int nextWorkRepetition(const KDateTime& pre) const;
270  void calcNextWorkingTime(const DateTime& nextTrigger) const;
271  DateTime nextWorkingTime() const;
272  OccurType nextRecurrence(const KDateTime& preDateTime, DateTime& result) const;
273 #ifndef KALARMCAL_USE_KRESOURCES
274  void setAudioAlarm(const KCalCore::Alarm::Ptr&) const;
275  KCalCore::Alarm::Ptr initKCalAlarm(const KCalCore::Event::Ptr&, const DateTime&, const QStringList& types, AlarmType = INVALID_ALARM) const;
276  KCalCore::Alarm::Ptr initKCalAlarm(const KCalCore::Event::Ptr&, int startOffsetSecs, const QStringList& types, AlarmType = INVALID_ALARM) const;
277 #else
278  void setAudioAlarm(KCal::Alarm*) const;
279  KCal::Alarm* initKCalAlarm(KCal::Event*, const DateTime&, const QStringList& types, AlarmType = INVALID_ALARM) const;
280  KCal::Alarm* initKCalAlarm(KCal::Event*, int startOffsetSecs, const QStringList& types, AlarmType = INVALID_ALARM) const;
281 #endif
282  inline void set_deferral(DeferType);
283  inline void activate_reminder(bool activate);
284 
285  public:
286 #ifdef KALARMCAL_USE_KRESOURCES
287  static QString mCmdErrConfigGroup; // config file group for command error recording
288 #endif
289  static QFont mDefaultFont; // default alarm message font
290  static const KHolidays::HolidayRegion* mHolidays; // holiday region to use
291  static QBitArray mWorkDays; // working days of the week
292  static QTime mWorkDayStart; // start time of the working day
293  static QTime mWorkDayEnd; // end time of the working day
294  static int mWorkTimeIndex; // incremented every time working days/times are changed
295 #ifdef KALARMCAL_USE_KRESOURCES
296  AlarmResource* mResource; // resource which owns the event (for convenience - not used by this class)
297 #endif
298  mutable DateTime mAllTrigger; // next trigger time, including reminders, ignoring working hours
299  mutable DateTime mMainTrigger; // next trigger time, ignoring reminders and working hours
300  mutable DateTime mAllWorkTrigger; // next trigger time, taking account of reminders and working hours
301  mutable DateTime mMainWorkTrigger; // next trigger time, ignoring reminders but taking account of working hours
302  mutable CmdErrType mCommandError; // command execution error last time the alarm triggered
303 
304  QString mEventID; // UID: KCal::Event unique ID
305  QString mTemplateName; // alarm template's name, or null if normal event
306 #ifndef KALARMCAL_USE_KRESOURCES
307  QMap<QByteArray, QString> mCustomProperties; // KCal::Event's non-KAlarm custom properties
308  Akonadi::Item::Id mItemId; // Akonadi::Item ID for this event
309  mutable Akonadi::Collection::Id mCollectionId; // ID of collection containing the event, or for a displaying event,
310  // saved collection ID (not the collection the event is in)
311 #else
312  QString mOriginalResourceId;// saved resource ID (not the resource the event is in)
313 #endif
314  QString mText; // message text, file URL, command, email body [or audio file for KAAlarm]
315  QString mAudioFile; // ATTACH: audio file to play
316  QString mPreAction; // command to execute before alarm is displayed
317  QString mPostAction; // command to execute after alarm window is closed
318  DateTime mStartDateTime; // DTSTART and DTEND: start and end time for event
319  KDateTime mCreatedDateTime; // CREATED: date event was created, or saved in archive calendar
320  DateTime mNextMainDateTime; // next time to display the alarm, excluding repetitions
321  KDateTime mAtLoginDateTime; // repeat-at-login end time
322  DateTime mDeferralTime; // extra time to trigger alarm (if alarm or reminder deferred)
323  DateTime mDisplayingTime; // date/time shown in the alarm currently being displayed
324  int mDisplayingFlags; // type of alarm which is currently being displayed
325  int mReminderMinutes; // how long in advance reminder is to be, or 0 if none (<0 for reminder AFTER the alarm)
326  DateTime mReminderAfterTime; // if mReminderActive true, time to trigger reminder AFTER the main alarm, or invalid if not pending
327  ReminderType mReminderActive; // whether a reminder is due (before next, or after last, main alarm/recurrence)
328  int mDeferDefaultMinutes; // default number of minutes for deferral dialog, or 0 to select time control
329  bool mDeferDefaultDateOnly;// select date-only by default in deferral dialog
330  int mRevision; // SEQUENCE: revision number of the original alarm, or 0
331  KARecurrence* mRecurrence; // RECUR: recurrence specification, or 0 if none
332  Repetition mRepetition; // sub-repetition count and interval
333  int mNextRepeat; // repetition count of next due sub-repetition
334  int mAlarmCount; // number of alarms: count of !mMainExpired, mRepeatAtLogin, mDeferral, mReminderActive, mDisplaying
335  DeferType mDeferral; // whether the alarm is an extra deferred/deferred-reminder alarm
336  unsigned long mKMailSerialNumber; // if email text, message's KMail serial number
337  int mTemplateAfterTime; // time not specified: use n minutes after default time, or -1 (applies to templates only)
338  QColor mBgColour; // background colour of alarm message
339  QColor mFgColour; // foreground colour of alarm message, or invalid for default
340  QFont mFont; // font of alarm message (ignored if mUseDefaultFont true)
341  uint mEmailFromIdentity; // standard email identity uoid for 'From' field, or empty
342  EmailAddressList mEmailAddresses; // ATTENDEE: addresses to send email to
343  QString mEmailSubject; // SUMMARY: subject line of email
344  QStringList mEmailAttachments; // ATTACH: email attachment file names
345  mutable int mChangeCount; // >0 = inhibit calling calcTriggerTimes()
346  mutable bool mTriggerChanged; // true if need to recalculate trigger times
347  QString mLogFile; // alarm output is to be logged to this URL
348  float mSoundVolume; // volume for sound file (range 0 - 1), or < 0 for unspecified
349  float mFadeVolume; // initial volume for sound file (range 0 - 1), or < 0 for no fade
350  int mFadeSeconds; // fade time (seconds) for sound file, or 0 if none
351  int mRepeatSoundPause; // seconds to pause between sound file repetitions, or -1 if no repetition
352  int mLateCancel; // how many minutes late will cancel the alarm, or 0 for no cancellation
353  mutable const KHolidays::HolidayRegion*
354  mExcludeHolidays; // non-null to not trigger alarms on holidays (= mHolidays when trigger calculated)
355  mutable int mWorkTimeOnly; // non-zero to trigger alarm only during working hours (= mWorkTimeIndex when trigger calculated)
356  SubAction mActionSubType; // sub-action type for the event's main alarm
357  CalEvent::Type mCategory; // event category (active, archived, template, ...)
358  ExtraActionOptions mExtraActionOptions;// options for pre- or post-alarm actions
359 #ifndef KALARMCAL_USE_KRESOURCES
360  KACalendar::Compat mCompatibility; // event's storage format compatibility
361  bool mReadOnly; // event is read-only in its original calendar file
362 #endif
363  bool mConfirmAck; // alarm acknowledgement requires confirmation by user
364  bool mUseDefaultFont; // use default message font, not mFont
365  bool mCommandScript; // the command text is a script, not a shell command line
366  bool mCommandXterm; // command alarm is to be executed in a terminal window
367  bool mCommandDisplay; // command output is to be displayed in an alarm window
368  bool mEmailBcc; // blind copy the email to the user
369  bool mBeep; // whether to beep when the alarm is displayed
370  bool mSpeak; // whether to speak the message when the alarm is displayed
371  bool mCopyToKOrganizer; // KOrganizer should hold a copy of the event
372  bool mReminderOnceOnly; // the reminder is output only for the first recurrence
373  bool mAutoClose; // whether to close the alarm window after the late-cancel period
374  bool mMainExpired; // main alarm has expired (in which case a deferral alarm will exist)
375  bool mRepeatAtLogin; // whether to repeat the alarm at every login
376  bool mArchiveRepeatAtLogin; // if now archived, original event was repeat-at-login
377  bool mArchive; // event has triggered in the past, so archive it when closed
378  bool mDisplaying; // whether the alarm is currently being displayed (i.e. in displaying calendar)
379  bool mDisplayingDefer; // show Defer button (applies to displaying calendar only)
380  bool mDisplayingEdit; // show Edit button (applies to displaying calendar only)
381  bool mEnabled; // false if event is disabled
382 
383  public:
384  static const QByteArray FLAGS_PROPERTY;
385  static const QString DATE_ONLY_FLAG;
386  static const QString EMAIL_BCC_FLAG;
387  static const QString CONFIRM_ACK_FLAG;
388  static const QString KORGANIZER_FLAG;
389  static const QString EXCLUDE_HOLIDAYS_FLAG;
390  static const QString WORK_TIME_ONLY_FLAG;
391  static const QString REMINDER_ONCE_FLAG;
392  static const QString DEFER_FLAG;
393  static const QString LATE_CANCEL_FLAG;
394  static const QString AUTO_CLOSE_FLAG;
395  static const QString TEMPL_AFTER_TIME_FLAG;
396  static const QString KMAIL_SERNUM_FLAG;
397  static const QString ARCHIVE_FLAG;
398  static const QByteArray NEXT_RECUR_PROPERTY;
399  static const QByteArray REPEAT_PROPERTY;
400  static const QByteArray LOG_PROPERTY;
401  static const QString xtermURL;
402  static const QString displayURL;
403  static const QByteArray TYPE_PROPERTY;
404  static const QString FILE_TYPE;
405  static const QString AT_LOGIN_TYPE;
406  static const QString REMINDER_TYPE;
407  static const QString REMINDER_ONCE_TYPE;
408  static const QString TIME_DEFERRAL_TYPE;
409  static const QString DATE_DEFERRAL_TYPE;
410  static const QString DISPLAYING_TYPE;
411  static const QString PRE_ACTION_TYPE;
412  static const QString POST_ACTION_TYPE;
413  static const QString SOUND_REPEAT_TYPE;
414  static const QByteArray NEXT_REPEAT_PROPERTY;
415  static const QString HIDDEN_REMINDER_FLAG;
416  static const QByteArray FONT_COLOUR_PROPERTY;
417  static const QByteArray VOLUME_PROPERTY;
418  static const QString EMAIL_ID_FLAG;
419  static const QString SPEAK_FLAG;
420  static const QString EXEC_ON_DEFERRAL_FLAG;
421  static const QString CANCEL_ON_ERROR_FLAG;
422  static const QString DONT_SHOW_ERROR_FLAG;
423  static const QString DISABLED_STATUS;
424  static const QString DISP_DEFER;
425  static const QString DISP_EDIT;
426  static const QString CMD_ERROR_VALUE;
427  static const QString CMD_ERROR_PRE_VALUE;
428  static const QString CMD_ERROR_POST_VALUE;
429  static const QString SC;
430 };
431 
432 
433 // KAlarm version which first used the current calendar/event format.
434 // If this changes, KAEvent::convertKCalEvents() must be changed correspondingly.
435 // The string version is the KAlarm version string used in the calendar file.
436 QByteArray KAEvent::currentCalendarVersionString() { return QByteArray("2.7.0"); }
437 int KAEvent::currentCalendarVersion() { return Version(2,7,0); }
438 
439 // Custom calendar properties.
440 // Note that all custom property names are prefixed with X-KDE-KALARM- in the calendar file.
441 
442 // Event properties
443 const QByteArray KAEvent::Private::FLAGS_PROPERTY("FLAGS"); // X-KDE-KALARM-FLAGS property
444 const QString KAEvent::Private::DATE_ONLY_FLAG = QLatin1String("DATE");
445 const QString KAEvent::Private::EMAIL_BCC_FLAG = QLatin1String("BCC");
446 const QString KAEvent::Private::CONFIRM_ACK_FLAG = QLatin1String("ACKCONF");
447 const QString KAEvent::Private::KORGANIZER_FLAG = QLatin1String("KORG");
448 const QString KAEvent::Private::EXCLUDE_HOLIDAYS_FLAG = QLatin1String("EXHOLIDAYS");
449 const QString KAEvent::Private::WORK_TIME_ONLY_FLAG = QLatin1String("WORKTIME");
450 const QString KAEvent::Private::REMINDER_ONCE_FLAG = QLatin1String("ONCE");
451 const QString KAEvent::Private::DEFER_FLAG = QLatin1String("DEFER"); // default defer interval for this alarm
452 const QString KAEvent::Private::LATE_CANCEL_FLAG = QLatin1String("LATECANCEL");
453 const QString KAEvent::Private::AUTO_CLOSE_FLAG = QLatin1String("LATECLOSE");
454 const QString KAEvent::Private::TEMPL_AFTER_TIME_FLAG = QLatin1String("TMPLAFTTIME");
455 const QString KAEvent::Private::KMAIL_SERNUM_FLAG = QLatin1String("KMAIL");
456 const QString KAEvent::Private::ARCHIVE_FLAG = QLatin1String("ARCHIVE");
457 
458 const QByteArray KAEvent::Private::NEXT_RECUR_PROPERTY("NEXTRECUR"); // X-KDE-KALARM-NEXTRECUR property
459 const QByteArray KAEvent::Private::REPEAT_PROPERTY("REPEAT"); // X-KDE-KALARM-REPEAT property
460 const QByteArray KAEvent::Private::LOG_PROPERTY("LOG"); // X-KDE-KALARM-LOG property
461 const QString KAEvent::Private::xtermURL = QLatin1String("xterm:");
462 const QString KAEvent::Private::displayURL = QLatin1String("display:");
463 
464 // - General alarm properties
465 const QByteArray KAEvent::Private::TYPE_PROPERTY("TYPE"); // X-KDE-KALARM-TYPE property
466 const QString KAEvent::Private::FILE_TYPE = QLatin1String("FILE");
467 const QString KAEvent::Private::AT_LOGIN_TYPE = QLatin1String("LOGIN");
468 const QString KAEvent::Private::REMINDER_TYPE = QLatin1String("REMINDER");
469 const QString KAEvent::Private::TIME_DEFERRAL_TYPE = QLatin1String("DEFERRAL");
470 const QString KAEvent::Private::DATE_DEFERRAL_TYPE = QLatin1String("DATE_DEFERRAL");
471 const QString KAEvent::Private::DISPLAYING_TYPE = QLatin1String("DISPLAYING"); // used only in displaying calendar
472 const QString KAEvent::Private::PRE_ACTION_TYPE = QLatin1String("PRE");
473 const QString KAEvent::Private::POST_ACTION_TYPE = QLatin1String("POST");
474 const QString KAEvent::Private::SOUND_REPEAT_TYPE = QLatin1String("SOUNDREPEAT");
475 const QByteArray KAEvent::Private::NEXT_REPEAT_PROPERTY("NEXTREPEAT"); // X-KDE-KALARM-NEXTREPEAT property
476 const QString KAEvent::Private::HIDDEN_REMINDER_FLAG = QLatin1String("HIDE");
477 // - Display alarm properties
478 const QByteArray KAEvent::Private::FONT_COLOUR_PROPERTY("FONTCOLOR"); // X-KDE-KALARM-FONTCOLOR property
479 // - Email alarm properties
480 const QString KAEvent::Private::EMAIL_ID_FLAG = QLatin1String("EMAILID");
481 // - Audio alarm properties
482 const QByteArray KAEvent::Private::VOLUME_PROPERTY("VOLUME"); // X-KDE-KALARM-VOLUME property
483 const QString KAEvent::Private::SPEAK_FLAG = QLatin1String("SPEAK");
484 // - Command alarm properties
485 const QString KAEvent::Private::EXEC_ON_DEFERRAL_FLAG = QLatin1String("EXECDEFER");
486 const QString KAEvent::Private::CANCEL_ON_ERROR_FLAG = QLatin1String("ERRCANCEL");
487 const QString KAEvent::Private::DONT_SHOW_ERROR_FLAG = QLatin1String("ERRNOSHOW");
488 
489 // Event status strings
490 const QString KAEvent::Private::DISABLED_STATUS = QLatin1String("DISABLED");
491 
492 // Displaying event ID identifier
493 const QString KAEvent::Private::DISP_DEFER = QLatin1String("DEFER");
494 const QString KAEvent::Private::DISP_EDIT = QLatin1String("EDIT");
495 
496 // Command error strings
497 #ifdef KALARMCAL_USE_KRESOURCES
498 QString KAEvent::Private::mCmdErrConfigGroup = QLatin1String("CommandErrors");
499 #endif
500 const QString KAEvent::Private::CMD_ERROR_VALUE = QLatin1String("MAIN");
501 const QString KAEvent::Private::CMD_ERROR_PRE_VALUE = QLatin1String("PRE");
502 const QString KAEvent::Private::CMD_ERROR_POST_VALUE = QLatin1String("POST");
503 
504 const QString KAEvent::Private::SC = QLatin1String(";");
505 
506 QFont KAEvent::Private::mDefaultFont;
507 const KHolidays::HolidayRegion* KAEvent::Private::mHolidays = 0;
508 QBitArray KAEvent::Private::mWorkDays(7);
509 QTime KAEvent::Private::mWorkDayStart(9, 0, 0);
510 QTime KAEvent::Private::mWorkDayEnd(17, 0, 0);
511 int KAEvent::Private::mWorkTimeIndex = 1;
512 
513 
514 #ifndef KALARMCAL_USE_KRESOURCES
515 static void setProcedureAlarm(const Alarm::Ptr&, const QString& commandLine);
516 #else
517 static void setProcedureAlarm(Alarm*, const QString& commandLine);
518 #endif
519 static QString reminderToString(int minutes);
520 
521 
522 /*=============================================================================
523 = Class KAEvent
524 = Corresponds to a KCal::Event instance.
525 =============================================================================*/
526 
527 inline void KAEvent::Private::set_deferral(DeferType type)
528 {
529  if (type)
530  {
531  if (mDeferral == NO_DEFERRAL)
532  ++mAlarmCount;
533  }
534  else
535  {
536  if (mDeferral != NO_DEFERRAL)
537  --mAlarmCount;
538  }
539  mDeferral = type;
540 }
541 
542 inline void KAEvent::Private::activate_reminder(bool activate)
543 {
544  if (activate && mReminderActive != ACTIVE_REMINDER && mReminderMinutes)
545  {
546  if (mReminderActive == NO_REMINDER)
547  ++mAlarmCount;
548  mReminderActive = ACTIVE_REMINDER;
549  }
550  else if (!activate && mReminderActive != NO_REMINDER)
551  {
552  mReminderActive = NO_REMINDER;
553  mReminderAfterTime = DateTime();
554  --mAlarmCount;
555  }
556 }
557 
558 KAEvent::KAEvent()
559  : d(new Private)
560 { }
561 
562 KAEvent::Private::Private()
563  :
564 #ifdef KALARMCAL_USE_KRESOURCES
565  mResource(0),
566 #endif
567  mCommandError(CMD_NO_ERROR),
568 #ifndef KALARMCAL_USE_KRESOURCES
569  mItemId(-1),
570  mCollectionId(-1),
571 #endif
572  mReminderMinutes(0),
573  mReminderActive(NO_REMINDER),
574  mRevision(0),
575  mRecurrence(0),
576  mNextRepeat(0),
577  mAlarmCount(0),
578  mDeferral(NO_DEFERRAL),
579  mChangeCount(0),
580  mTriggerChanged(false),
581  mLateCancel(0),
582  mExcludeHolidays(0),
583  mWorkTimeOnly(0),
584  mCategory(CalEvent::EMPTY),
585 #ifndef KALARMCAL_USE_KRESOURCES
586  mCompatibility(KACalendar::Current),
587  mReadOnly(false),
588 #endif
589  mConfirmAck(false),
590  mEmailBcc(false),
591  mBeep(false),
592  mAutoClose(false),
593  mRepeatAtLogin(false),
594  mDisplaying(false)
595 { }
596 
597 KAEvent::KAEvent(const KDateTime& dt, const QString& message, const QColor& bg, const QColor& fg, const QFont& f,
598  SubAction action, int lateCancel, Flags flags, bool changesPending)
599  : d(new Private(dt, message, bg, fg, f, action, lateCancel, flags, changesPending))
600 {
601 }
602 
603 KAEvent::Private::Private(const KDateTime& dt, const QString& message, const QColor& bg, const QColor& fg, const QFont& f,
604  SubAction action, int lateCancel, Flags flags, bool changesPending)
605  : mRecurrence(0)
606 {
607  set(dt, message, bg, fg, f, action, lateCancel, flags, changesPending);
608 }
609 
610 #ifndef KALARMCAL_USE_KRESOURCES
611 KAEvent::KAEvent(const Event::Ptr& e)
612 #else
613 KAEvent::KAEvent(const Event* e)
614 #endif
615  : d(new Private(e))
616 {
617 }
618 
619 #ifndef KALARMCAL_USE_KRESOURCES
620 KAEvent::Private::Private(const Event::Ptr& e)
621 #else
622 KAEvent::Private::Private(const Event* e)
623 #endif
624  : mRecurrence(0)
625 {
626  set(e);
627 }
628 
629 KAEvent::Private::Private(const KAEvent::Private& e)
630  : QSharedData(e),
631  mRecurrence(0)
632 {
633  copy(e);
634 }
635 
636 KAEvent::KAEvent(const KAEvent& other)
637  : d(other.d)
638 { }
639 
640 KAEvent::~KAEvent()
641 { }
642 
643 KAEvent& KAEvent::operator=(const KAEvent& other)
644 {
645  if (&other != this)
646  d = other.d;
647  return *this;
648 }
649 
650 /******************************************************************************
651 * Copies the data from another instance.
652 */
653 void KAEvent::Private::copy(const KAEvent::Private& event)
654 {
655 #ifdef KALARMCAL_USE_KRESOURCES
656  mResource = event.mResource;
657 #endif
658  mAllTrigger = event.mAllTrigger;
659  mMainTrigger = event.mMainTrigger;
660  mAllWorkTrigger = event.mAllWorkTrigger;
661  mMainWorkTrigger = event.mMainWorkTrigger;
662  mCommandError = event.mCommandError;
663  mEventID = event.mEventID;
664  mTemplateName = event.mTemplateName;
665 #ifndef KALARMCAL_USE_KRESOURCES
666  mCustomProperties = event.mCustomProperties;
667  mItemId = event.mItemId;
668  mCollectionId = event.mCollectionId;
669 #else
670  mOriginalResourceId = event.mOriginalResourceId;
671 #endif
672  mText = event.mText;
673  mAudioFile = event.mAudioFile;
674  mPreAction = event.mPreAction;
675  mPostAction = event.mPostAction;
676  mStartDateTime = event.mStartDateTime;
677  mCreatedDateTime = event.mCreatedDateTime;
678  mNextMainDateTime = event.mNextMainDateTime;
679  mAtLoginDateTime = event.mAtLoginDateTime;
680  mDeferralTime = event.mDeferralTime;
681  mDisplayingTime = event.mDisplayingTime;
682  mDisplayingFlags = event.mDisplayingFlags;
683  mReminderMinutes = event.mReminderMinutes;
684  mReminderAfterTime = event.mReminderAfterTime;
685  mReminderActive = event.mReminderActive;
686  mDeferDefaultMinutes = event.mDeferDefaultMinutes;
687  mDeferDefaultDateOnly = event.mDeferDefaultDateOnly;
688  mRevision = event.mRevision;
689  mRepetition = event.mRepetition;
690  mNextRepeat = event.mNextRepeat;
691  mAlarmCount = event.mAlarmCount;
692  mDeferral = event.mDeferral;
693  mKMailSerialNumber = event.mKMailSerialNumber;
694  mTemplateAfterTime = event.mTemplateAfterTime;
695  mBgColour = event.mBgColour;
696  mFgColour = event.mFgColour;
697  mFont = event.mFont;
698  mEmailFromIdentity = event.mEmailFromIdentity;
699  mEmailAddresses = event.mEmailAddresses;
700  mEmailSubject = event.mEmailSubject;
701  mEmailAttachments = event.mEmailAttachments;
702  mLogFile = event.mLogFile;
703  mSoundVolume = event.mSoundVolume;
704  mFadeVolume = event.mFadeVolume;
705  mFadeSeconds = event.mFadeSeconds;
706  mRepeatSoundPause = event.mRepeatSoundPause;
707  mLateCancel = event.mLateCancel;
708  mExcludeHolidays = event.mExcludeHolidays;
709  mWorkTimeOnly = event.mWorkTimeOnly;
710  mActionSubType = event.mActionSubType;
711  mCategory = event.mCategory;
712  mExtraActionOptions = event.mExtraActionOptions;
713 #ifndef KALARMCAL_USE_KRESOURCES
714  mCompatibility = event.mCompatibility;
715  mReadOnly = event.mReadOnly;
716 #endif
717  mConfirmAck = event.mConfirmAck;
718  mUseDefaultFont = event.mUseDefaultFont;
719  mCommandScript = event.mCommandScript;
720  mCommandXterm = event.mCommandXterm;
721  mCommandDisplay = event.mCommandDisplay;
722  mEmailBcc = event.mEmailBcc;
723  mBeep = event.mBeep;
724  mSpeak = event.mSpeak;
725  mCopyToKOrganizer = event.mCopyToKOrganizer;
726  mReminderOnceOnly = event.mReminderOnceOnly;
727  mAutoClose = event.mAutoClose;
728  mMainExpired = event.mMainExpired;
729  mRepeatAtLogin = event.mRepeatAtLogin;
730  mArchiveRepeatAtLogin = event.mArchiveRepeatAtLogin;
731  mArchive = event.mArchive;
732  mDisplaying = event.mDisplaying;
733  mDisplayingDefer = event.mDisplayingDefer;
734  mDisplayingEdit = event.mDisplayingEdit;
735  mEnabled = event.mEnabled;
736  mChangeCount = 0;
737  mTriggerChanged = event.mTriggerChanged;
738  delete mRecurrence;
739  if (event.mRecurrence)
740  mRecurrence = new KARecurrence(*event.mRecurrence);
741  else
742  mRecurrence = 0;
743 }
744 
745 #ifndef KALARMCAL_USE_KRESOURCES
746 void KAEvent::set(const Event::Ptr& e)
747 #else
748 void KAEvent::set(const Event* e)
749 #endif
750 {
751  d->set(e);
752 }
753 
754 /******************************************************************************
755 * Initialise the KAEvent::Private from a KCal::Event.
756 */
757 #ifndef KALARMCAL_USE_KRESOURCES
758 void KAEvent::Private::set(const Event::Ptr& event)
759 #else
760 void KAEvent::Private::set(const Event* event)
761 #endif
762 {
763  startChanges();
764  // Extract status from the event
765  mCommandError = CMD_NO_ERROR;
766 #ifdef KALARMCAL_USE_KRESOURCES
767  mResource = 0;
768 #endif
769  mEventID = event->uid();
770  mRevision = event->revision();
771  mTemplateName.clear();
772  mLogFile.clear();
773 #ifndef KALARMCAL_USE_KRESOURCES
774  mItemId = -1;
775  mCollectionId = -1;
776 #else
777  mOriginalResourceId.clear();
778 #endif
779  mTemplateAfterTime = -1;
780  mBeep = false;
781  mSpeak = false;
782  mEmailBcc = false;
783  mCommandXterm = false;
784  mCommandDisplay = false;
785  mCopyToKOrganizer = false;
786  mConfirmAck = false;
787  mArchive = false;
788  mReminderOnceOnly = false;
789  mAutoClose = false;
790  mArchiveRepeatAtLogin = false;
791  mDisplayingDefer = false;
792  mDisplayingEdit = false;
793  mDeferDefaultDateOnly = false;
794  mReminderActive = NO_REMINDER;
795  mReminderMinutes = 0;
796  mDeferDefaultMinutes = 0;
797  mLateCancel = 0;
798  mKMailSerialNumber = 0;
799  mExcludeHolidays = 0;
800  mWorkTimeOnly = 0;
801  mChangeCount = 0;
802  mBgColour = QColor(255, 255, 255); // missing/invalid colour - return white background
803  mFgColour = QColor(0, 0, 0); // and black foreground
804 #ifndef KALARMCAL_USE_KRESOURCES
805  mCompatibility = KACalendar::Current;
806  mReadOnly = event->isReadOnly();
807 #endif
808  mUseDefaultFont = true;
809  mEnabled = true;
810  clearRecur();
811  QString param;
812  bool ok;
813  mCategory = CalEvent::status(event, &param);
814  if (mCategory == CalEvent::DISPLAYING)
815  {
816  // It's a displaying calendar event - set values specific to displaying alarms
817  const QStringList params = param.split(SC, QString::KeepEmptyParts);
818  int n = params.count();
819  if (n)
820  {
821 #ifndef KALARMCAL_USE_KRESOURCES
822  const qlonglong id = params[0].toLongLong(&ok);
823  if (ok)
824  mCollectionId = id; // original collection ID which contained the event
825 #else
826  mOriginalResourceId = params[0];
827 #endif
828  for (int i = 1; i < n; ++i)
829  {
830  if (params[i] == DISP_DEFER)
831  mDisplayingDefer = true;
832  if (params[i] == DISP_EDIT)
833  mDisplayingEdit = true;
834  }
835  }
836  }
837 #ifndef KALARMCAL_USE_KRESOURCES
838  // Store the non-KAlarm custom properties of the event
839  const QByteArray kalarmKey = "X-KDE-" + KACalendar::APPNAME + '-';
840  mCustomProperties = event->customProperties();
841  for (QMap<QByteArray, QString>::Iterator it = mCustomProperties.begin(); it != mCustomProperties.end(); )
842  {
843  if (it.key().startsWith(kalarmKey))
844  it = mCustomProperties.erase(it);
845  else
846  ++it;
847  }
848 #endif
849 
850  bool dateOnly = false;
851  QStringList flags = event->customProperty(KACalendar::APPNAME, FLAGS_PROPERTY).split(SC, QString::SkipEmptyParts);
852  flags << QString() << QString(); // to avoid having to check for end of list
853  for (int i = 0, end = flags.count() - 1; i < end; ++i)
854  {
855  if (flags[i] == DATE_ONLY_FLAG)
856  dateOnly = true;
857  else if (flags[i] == CONFIRM_ACK_FLAG)
858  mConfirmAck = true;
859  else if (flags[i] == EMAIL_BCC_FLAG)
860  mEmailBcc = true;
861  else if (flags[i] == KORGANIZER_FLAG)
862  mCopyToKOrganizer = true;
863  else if (flags[i] == EXCLUDE_HOLIDAYS_FLAG)
864  mExcludeHolidays = mHolidays;
865  else if (flags[i] == WORK_TIME_ONLY_FLAG)
866  mWorkTimeOnly = 1;
867  else if (flags[i]== KMAIL_SERNUM_FLAG)
868  {
869  const unsigned long n = flags[i + 1].toULong(&ok);
870  if (!ok)
871  continue;
872  mKMailSerialNumber = n;
873  ++i;
874  }
875  else if (flags[i] == Private::ARCHIVE_FLAG)
876  mArchive = true;
877  else if (flags[i] == Private::AT_LOGIN_TYPE)
878  mArchiveRepeatAtLogin = true;
879  else if (flags[i] == Private::REMINDER_TYPE)
880  {
881  if (flags[++i] == Private::REMINDER_ONCE_FLAG)
882  {
883  mReminderOnceOnly = true;
884  ++i;
885  }
886  const int len = flags[i].length() - 1;
887  mReminderMinutes = -flags[i].left(len).toInt(); // -> 0 if conversion fails
888  switch (flags[i].at(len).toLatin1())
889  {
890  case 'M': break;
891  case 'H': mReminderMinutes *= 60; break;
892  case 'D': mReminderMinutes *= 1440; break;
893  default: mReminderMinutes = 0; break;
894  }
895  }
896  else if (flags[i] == DEFER_FLAG)
897  {
898  QString mins = flags[i + 1];
899  if (mins.endsWith('D'))
900  {
901  mDeferDefaultDateOnly = true;
902  mins.truncate(mins.length() - 1);
903  }
904  const int n = static_cast<int>(mins.toUInt(&ok));
905  if (!ok)
906  continue;
907  mDeferDefaultMinutes = n;
908  ++i;
909  }
910  else if (flags[i] == TEMPL_AFTER_TIME_FLAG)
911  {
912  const int n = static_cast<int>(flags[i + 1].toUInt(&ok));
913  if (!ok)
914  continue;
915  mTemplateAfterTime = n;
916  ++i;
917  }
918  else if (flags[i] == LATE_CANCEL_FLAG)
919  {
920  mLateCancel = static_cast<int>(flags[i + 1].toUInt(&ok));
921  if (ok)
922  ++i;
923  if (!ok || !mLateCancel)
924  mLateCancel = 1; // invalid parameter defaults to 1 minute
925  }
926  else if (flags[i] == AUTO_CLOSE_FLAG)
927  {
928  mLateCancel = static_cast<int>(flags[i + 1].toUInt(&ok));
929  if (ok)
930  ++i;
931  if (!ok || !mLateCancel)
932  mLateCancel = 1; // invalid parameter defaults to 1 minute
933  mAutoClose = true;
934  }
935  }
936 
937  QString prop = event->customProperty(KACalendar::APPNAME, LOG_PROPERTY);
938  if (!prop.isEmpty())
939  {
940  if (prop == xtermURL)
941  mCommandXterm = true;
942  else if (prop == displayURL)
943  mCommandDisplay = true;
944  else
945  mLogFile = prop;
946  }
947  prop = event->customProperty(KACalendar::APPNAME, REPEAT_PROPERTY);
948  if (!prop.isEmpty())
949  {
950  // This property is used when the main alarm has expired
951  const QStringList list = prop.split(QLatin1Char(':'));
952  if (list.count() >= 2)
953  {
954  const int interval = static_cast<int>(list[0].toUInt());
955  const int count = static_cast<int>(list[1].toUInt());
956  if (interval && count)
957  {
958  if (interval % (24*60))
959  mRepetition.set(Duration(interval * 60, Duration::Seconds), count);
960  else
961  mRepetition.set(Duration(interval / (24*60), Duration::Days), count);
962  }
963  }
964  }
965  mNextMainDateTime = readDateTime(event, dateOnly, mStartDateTime);
966  mCreatedDateTime = event->created();
967  if (dateOnly && !mRepetition.isDaily())
968  mRepetition.set(Duration(mRepetition.intervalDays(), Duration::Days));
969  if (mCategory == CalEvent::TEMPLATE)
970  mTemplateName = event->summary();
971 #ifndef KALARMCAL_USE_KRESOURCES
972  if (event->customStatus() == DISABLED_STATUS)
973 #else
974  if (event->statusStr() == DISABLED_STATUS)
975 #endif
976  mEnabled = false;
977 
978  // Extract status from the event's alarms.
979  // First set up defaults.
980  mActionSubType = MESSAGE;
981  mMainExpired = true;
982  mRepeatAtLogin = false;
983  mDisplaying = false;
984  mCommandScript = false;
985  mExtraActionOptions = 0;
986  mDeferral = NO_DEFERRAL;
987  mSoundVolume = -1;
988  mFadeVolume = -1;
989  mRepeatSoundPause = -1;
990  mFadeSeconds = 0;
991  mEmailFromIdentity = 0;
992  mReminderAfterTime = DateTime();
993  mText.clear();
994  mAudioFile.clear();
995  mPreAction.clear();
996  mPostAction.clear();
997  mEmailSubject.clear();
998  mEmailAddresses.clear();
999  mEmailAttachments.clear();
1000 
1001  // Extract data from all the event's alarms and index the alarms by sequence number
1002  AlarmMap alarmMap;
1003  readAlarms(event, &alarmMap, mCommandDisplay);
1004 
1005  // Incorporate the alarms' details into the overall event
1006  mAlarmCount = 0; // initialise as invalid
1007  DateTime alTime;
1008  bool set = false;
1009  bool isEmailText = false;
1010  bool setDeferralTime = false;
1011  Duration deferralOffset;
1012  for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it)
1013  {
1014  const AlarmData& data = it.value();
1015  const DateTime dateTime = data.alarm->hasStartOffset() ? data.alarm->startOffset().end(mNextMainDateTime.effectiveKDateTime()) : data.alarm->time();
1016  switch (data.type)
1017  {
1018  case MAIN_ALARM:
1019  mMainExpired = false;
1020  alTime = dateTime;
1021  alTime.setDateOnly(mStartDateTime.isDateOnly());
1022  if (data.alarm->repeatCount() && data.alarm->snoozeTime())
1023  {
1024  mRepetition.set(data.alarm->snoozeTime(), data.alarm->repeatCount()); // values may be adjusted in setRecurrence()
1025  mNextRepeat = data.nextRepeat;
1026  }
1027  if (data.action != KAAlarm::AUDIO)
1028  break;
1029  // Fall through to AUDIO_ALARM
1030  case AUDIO_ALARM:
1031  mAudioFile = data.cleanText;
1032  mSpeak = data.speak && mAudioFile.isEmpty();
1033  mBeep = !mSpeak && mAudioFile.isEmpty();
1034  mSoundVolume = (!mBeep && !mSpeak) ? data.soundVolume : -1;
1035  mFadeVolume = (mSoundVolume >= 0 && data.fadeSeconds > 0) ? data.fadeVolume : -1;
1036  mFadeSeconds = (mFadeVolume >= 0) ? data.fadeSeconds : 0;
1037  mRepeatSoundPause = (!mBeep && !mSpeak) ? data.repeatSoundPause : -1;
1038  break;
1039  case AT_LOGIN_ALARM:
1040  mRepeatAtLogin = true;
1041  mAtLoginDateTime = dateTime.kDateTime();
1042  alTime = mAtLoginDateTime;
1043  break;
1044  case REMINDER_ALARM:
1045  // N.B. there can be a start offset but no valid date/time (e.g. in template)
1046  if (data.alarm->startOffset().asSeconds() / 60)
1047  {
1048  mReminderActive = ACTIVE_REMINDER;
1049  if (mReminderMinutes < 0)
1050  {
1051  mReminderAfterTime = dateTime; // the reminder is AFTER the main alarm
1052  mReminderAfterTime.setDateOnly(dateOnly);
1053  if (data.hiddenReminder)
1054  mReminderActive = HIDDEN_REMINDER;
1055  }
1056  }
1057  break;
1058  case DEFERRED_REMINDER_ALARM:
1059  case DEFERRED_ALARM:
1060  mDeferral = (data.type == DEFERRED_REMINDER_ALARM) ? REMINDER_DEFERRAL : NORMAL_DEFERRAL;
1061  mDeferralTime = dateTime;
1062  if (!data.timedDeferral)
1063  mDeferralTime.setDateOnly(true);
1064  if (data.alarm->hasStartOffset())
1065  deferralOffset = data.alarm->startOffset();
1066  break;
1067  case DISPLAYING_ALARM:
1068  {
1069  mDisplaying = true;
1070  mDisplayingFlags = data.displayingFlags;
1071  const bool dateOnly = (mDisplayingFlags & DEFERRAL) ? !(mDisplayingFlags & TIMED_FLAG)
1072  : mStartDateTime.isDateOnly();
1073  mDisplayingTime = dateTime;
1074  mDisplayingTime.setDateOnly(dateOnly);
1075  alTime = mDisplayingTime;
1076  break;
1077  }
1078  case PRE_ACTION_ALARM:
1079  mPreAction = data.cleanText;
1080  mExtraActionOptions = data.extraActionOptions;
1081  break;
1082  case POST_ACTION_ALARM:
1083  mPostAction = data.cleanText;
1084  break;
1085  case INVALID_ALARM:
1086  default:
1087  break;
1088  }
1089 
1090  bool noSetNextTime = false;
1091  switch (data.type)
1092  {
1093  case DEFERRED_REMINDER_ALARM:
1094  case DEFERRED_ALARM:
1095  if (!set)
1096  {
1097  // The recurrence has to be evaluated before we can
1098  // calculate the time of a deferral alarm.
1099  setDeferralTime = true;
1100  noSetNextTime = true;
1101  }
1102  // fall through to REMINDER_ALARM
1103  case REMINDER_ALARM:
1104  case AT_LOGIN_ALARM:
1105  case DISPLAYING_ALARM:
1106  if (!set && !noSetNextTime)
1107  mNextMainDateTime = alTime;
1108  // fall through to MAIN_ALARM
1109  case MAIN_ALARM:
1110  // Ensure that the basic fields are set up even if there is no main
1111  // alarm in the event (if it has expired and then been deferred)
1112  if (!set)
1113  {
1114  mActionSubType = (KAEvent::SubAction)data.action;
1115  mText = (mActionSubType == COMMAND) ? data.cleanText.trimmed() : data.cleanText;
1116  switch (data.action)
1117  {
1118  case KAAlarm::COMMAND:
1119  mCommandScript = data.commandScript;
1120  if (!mCommandDisplay)
1121  break;
1122  // fall through to MESSAGE
1123  case KAAlarm::MESSAGE:
1124  mFont = data.font;
1125  mUseDefaultFont = data.defaultFont;
1126  if (data.isEmailText)
1127  isEmailText = true;
1128  // fall through to FILE
1129  case KAAlarm::FILE:
1130  mBgColour = data.bgColour;
1131  mFgColour = data.fgColour;
1132  break;
1133  case KAAlarm::EMAIL:
1134  mEmailFromIdentity = data.emailFromId;
1135  mEmailAddresses = data.alarm->mailAddresses();
1136  mEmailSubject = data.alarm->mailSubject();
1137  mEmailAttachments = data.alarm->mailAttachments();
1138  break;
1139  case KAAlarm::AUDIO:
1140  // Already mostly handled above
1141  mRepeatSoundPause = data.repeatSoundPause;
1142  break;
1143  default:
1144  break;
1145  }
1146  set = true;
1147  }
1148  if (data.action == KAAlarm::FILE && mActionSubType == MESSAGE)
1149  mActionSubType = FILE;
1150  ++mAlarmCount;
1151  break;
1152  case AUDIO_ALARM:
1153  case PRE_ACTION_ALARM:
1154  case POST_ACTION_ALARM:
1155  case INVALID_ALARM:
1156  default:
1157  break;
1158  }
1159  }
1160  if (!isEmailText)
1161  mKMailSerialNumber = 0;
1162 
1163  Recurrence* recur = event->recurrence();
1164  if (recur && recur->recurs())
1165  {
1166  const int nextRepeat = mNextRepeat; // setRecurrence() clears mNextRepeat
1167  setRecurrence(*recur);
1168  if (nextRepeat <= mRepetition.count())
1169  mNextRepeat = nextRepeat;
1170  }
1171  else if (mRepetition)
1172  {
1173  // Convert a repetition with no recurrence into a recurrence
1174  if (mRepetition.isDaily())
1175  recur->setDaily(mRepetition.intervalDays());
1176  else
1177  recur->setMinutely(mRepetition.intervalMinutes());
1178  recur->setDuration(mRepetition.count() + 1);
1179  mRepetition.set(0, 0);
1180  }
1181 
1182  if (mRepeatAtLogin)
1183  {
1184  mArchiveRepeatAtLogin = false;
1185  if (mReminderMinutes > 0)
1186  {
1187  mReminderMinutes = 0; // pre-alarm reminder not allowed for at-login alarm
1188  mReminderActive = NO_REMINDER;
1189  }
1190  setRepeatAtLoginTrue(false); // clear other incompatible statuses
1191  }
1192 
1193  if (mMainExpired && deferralOffset && checkRecur() != KARecurrence::NO_RECUR)
1194  {
1195  // Adjust the deferral time for an expired recurrence, since the
1196  // offset is relative to the first actual occurrence.
1197  DateTime dt = mRecurrence->getNextDateTime(mStartDateTime.addDays(-1).kDateTime());
1198  dt.setDateOnly(mStartDateTime.isDateOnly());
1199  if (mDeferralTime.isDateOnly())
1200  {
1201  mDeferralTime = deferralOffset.end(dt.kDateTime());
1202  mDeferralTime.setDateOnly(true);
1203  }
1204  else
1205  mDeferralTime = deferralOffset.end(dt.effectiveKDateTime());
1206  }
1207  if (mDeferral != NO_DEFERRAL)
1208  {
1209  if (setDeferralTime)
1210  mNextMainDateTime = mDeferralTime;
1211  }
1212  mTriggerChanged = true;
1213  endChanges();
1214 }
1215 
1216 void KAEvent::set(const KDateTime& dt, const QString& message, const QColor& bg, const QColor& fg,
1217  const QFont& f, SubAction act, int lateCancel, Flags flags, bool changesPending)
1218 {
1219  d->set(dt, message, bg, fg, f, act, lateCancel, flags, changesPending);
1220 }
1221 
1222 /******************************************************************************
1223 * Initialise the instance with the specified parameters.
1224 */
1225 void KAEvent::Private::set(const KDateTime& dateTime, const QString& text, const QColor& bg, const QColor& fg,
1226  const QFont& font, SubAction action, int lateCancel, Flags flags, bool changesPending)
1227 {
1228  clearRecur();
1229  mStartDateTime = dateTime;
1230  mStartDateTime.setDateOnly(flags & ANY_TIME);
1231  mNextMainDateTime = mStartDateTime;
1232  switch (action)
1233  {
1234  case MESSAGE:
1235  case FILE:
1236  case COMMAND:
1237  case EMAIL:
1238  case AUDIO:
1239  mActionSubType = (KAEvent::SubAction)action;
1240  break;
1241  default:
1242  mActionSubType = MESSAGE;
1243  break;
1244  }
1245  mEventID.clear();
1246  mTemplateName.clear();
1247 #ifndef KALARMCAL_USE_KRESOURCES
1248  mItemId = -1;
1249  mCollectionId = -1;
1250 #else
1251  mResource = 0;
1252  mOriginalResourceId.clear();
1253 #endif
1254  mPreAction.clear();
1255  mPostAction.clear();
1256  mText = (mActionSubType == COMMAND) ? text.trimmed()
1257  : (mActionSubType == AUDIO) ? QString() : text;
1258  mCategory = CalEvent::ACTIVE;
1259  mAudioFile = (mActionSubType == AUDIO) ? text : QString();
1260  mSoundVolume = -1;
1261  mFadeVolume = -1;
1262  mTemplateAfterTime = -1;
1263  mFadeSeconds = 0;
1264  mBgColour = bg;
1265  mFgColour = fg;
1266  mFont = font;
1267  mAlarmCount = 1;
1268  mLateCancel = lateCancel; // do this before setting flags
1269  mDeferral = NO_DEFERRAL; // do this before setting flags
1270 
1271  mStartDateTime.setDateOnly(flags & ANY_TIME);
1272  set_deferral((flags & DEFERRAL) ? NORMAL_DEFERRAL : NO_DEFERRAL);
1273  mRepeatAtLogin = flags & REPEAT_AT_LOGIN;
1274  mConfirmAck = flags & CONFIRM_ACK;
1275  mUseDefaultFont = flags & DEFAULT_FONT;
1276  mCommandScript = flags & SCRIPT;
1277  mCommandXterm = flags & EXEC_IN_XTERM;
1278  mCommandDisplay = flags & DISPLAY_COMMAND;
1279  mCopyToKOrganizer = flags & COPY_KORGANIZER;
1280  mExcludeHolidays = (flags & EXCL_HOLIDAYS) ? mHolidays : 0;
1281  mWorkTimeOnly = flags & WORK_TIME_ONLY;
1282  mEmailBcc = flags & EMAIL_BCC;
1283  mEnabled = !(flags & DISABLED);
1284  mDisplaying = flags & DISPLAYING_;
1285  mReminderOnceOnly = flags & REMINDER_ONCE;
1286  mAutoClose = (flags & AUTO_CLOSE) && mLateCancel;
1287  mRepeatSoundPause = (flags & REPEAT_SOUND) ? 0 : -1;
1288  mSpeak = (flags & SPEAK) && action != AUDIO;
1289  mBeep = (flags & BEEP) && action != AUDIO && !mSpeak;
1290  if (mRepeatAtLogin) // do this after setting other flags
1291  {
1292  ++mAlarmCount;
1293  setRepeatAtLoginTrue(false);
1294  }
1295 
1296  mKMailSerialNumber = 0;
1297  mReminderMinutes = 0;
1298  mDeferDefaultMinutes = 0;
1299  mDeferDefaultDateOnly = false;
1300  mArchiveRepeatAtLogin = false;
1301  mReminderActive = NO_REMINDER;
1302  mDisplaying = false;
1303  mMainExpired = false;
1304  mDisplayingDefer = false;
1305  mDisplayingEdit = false;
1306  mArchive = false;
1307  mReminderAfterTime = DateTime();
1308  mExtraActionOptions = 0;
1309 #ifndef KALARMCAL_USE_KRESOURCES
1310  mCompatibility = KACalendar::Current;
1311  mReadOnly = false;
1312 #endif
1313  mCommandError = CMD_NO_ERROR;
1314  mChangeCount = changesPending ? 1 : 0;
1315  mTriggerChanged = true;
1316 }
1317 
1318 /******************************************************************************
1319 * Update an existing KCal::Event with the KAEvent::Private data.
1320 * If 'setCustomProperties' is true, all the KCal::Event's existing custom
1321 * properties are cleared and replaced with the KAEvent's custom properties. If
1322 * false, the KCal::Event's non-KAlarm custom properties are left untouched.
1323 */
1324 #ifndef KALARMCAL_USE_KRESOURCES
1325 bool KAEvent::updateKCalEvent(const KCalCore::Event::Ptr& e, UidAction u, bool setCustomProperties) const
1326 {
1327  return d->updateKCalEvent(e, u, setCustomProperties);
1328 }
1329 
1330 #else
1331 bool KAEvent::updateKCalEvent(KCal::Event* e, UidAction u) const
1332 {
1333  return d->updateKCalEvent(e, u);
1334 }
1335 #endif
1336 
1337 #ifndef KALARMCAL_USE_KRESOURCES
1338 bool KAEvent::Private::updateKCalEvent(const Event::Ptr& ev, UidAction uidact, bool setCustomProperties) const
1339 #else
1340 bool KAEvent::Private::updateKCalEvent(Event* ev, UidAction uidact) const
1341 #endif
1342 {
1343  // If it's an archived event, the event start date/time will be adjusted to its original
1344  // value instead of its next occurrence, and the expired main alarm will be reinstated.
1345  const bool archived = (mCategory == CalEvent::ARCHIVED);
1346 
1347  if (!ev
1348  || (uidact == UID_CHECK && !mEventID.isEmpty() && mEventID != ev->uid())
1349  || (!mAlarmCount && (!archived || !mMainExpired)))
1350  return false;
1351 
1352  ev->startUpdates(); // prevent multiple update notifications
1353  checkRecur(); // ensure recurrence/repetition data is consistent
1354  const bool readOnly = ev->isReadOnly();
1355  if (uidact == KAEvent::UID_SET)
1356  ev->setUid(mEventID);
1357 #ifndef KALARMCAL_USE_KRESOURCES
1358  ev->setReadOnly(mReadOnly);
1359 #else
1360  ev->setReadOnly(false);
1361 #endif
1362  ev->setTransparency(Event::Transparent);
1363 
1364  // Set up event-specific data
1365 
1366  // Set up custom properties.
1367 #ifndef KALARMCAL_USE_KRESOURCES
1368  if (setCustomProperties)
1369  ev->setCustomProperties(mCustomProperties);
1370 #endif
1371  ev->removeCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY);
1372  ev->removeCustomProperty(KACalendar::APPNAME, NEXT_RECUR_PROPERTY);
1373  ev->removeCustomProperty(KACalendar::APPNAME, REPEAT_PROPERTY);
1374  ev->removeCustomProperty(KACalendar::APPNAME, LOG_PROPERTY);
1375 
1376  QString param;
1377  if (mCategory == CalEvent::DISPLAYING)
1378  {
1379 #ifndef KALARMCAL_USE_KRESOURCES
1380  param = QString::number(mCollectionId); // original collection ID which contained the event
1381 #else
1382  param = mOriginalResourceId;
1383 #endif
1384  if (mDisplayingDefer)
1385  param += SC + DISP_DEFER;
1386  if (mDisplayingEdit)
1387  param += SC + DISP_EDIT;
1388  }
1389 #ifndef KALARMCAL_USE_KRESOURCES
1390  CalEvent::setStatus(ev, mCategory, param);
1391 #else
1392  CalEvent::setStatus(ev, mCategory, param);
1393 #endif
1394  QStringList flags;
1395  if (mStartDateTime.isDateOnly())
1396  flags += DATE_ONLY_FLAG;
1397  if (mConfirmAck)
1398  flags += CONFIRM_ACK_FLAG;
1399  if (mEmailBcc)
1400  flags += EMAIL_BCC_FLAG;
1401  if (mCopyToKOrganizer)
1402  flags += KORGANIZER_FLAG;
1403  if (mExcludeHolidays)
1404  flags += EXCLUDE_HOLIDAYS_FLAG;
1405  if (mWorkTimeOnly)
1406  flags += WORK_TIME_ONLY_FLAG;
1407  if (mLateCancel)
1408  (flags += (mAutoClose ? AUTO_CLOSE_FLAG : LATE_CANCEL_FLAG)) += QString::number(mLateCancel);
1409  if (mReminderMinutes)
1410  {
1411  flags += REMINDER_TYPE;
1412  if (mReminderOnceOnly)
1413  flags += REMINDER_ONCE_FLAG;
1414  flags += reminderToString(-mReminderMinutes);
1415  }
1416  if (mDeferDefaultMinutes)
1417  {
1418  QString param = QString::number(mDeferDefaultMinutes);
1419  if (mDeferDefaultDateOnly)
1420  param += 'D';
1421  (flags += DEFER_FLAG) += param;
1422  }
1423  if (!mTemplateName.isEmpty() && mTemplateAfterTime >= 0)
1424  (flags += TEMPL_AFTER_TIME_FLAG) += QString::number(mTemplateAfterTime);
1425  if (mKMailSerialNumber)
1426  (flags += KMAIL_SERNUM_FLAG) += QString::number(mKMailSerialNumber);
1427  if (mArchive && !archived)
1428  {
1429  flags += ARCHIVE_FLAG;
1430  if (mArchiveRepeatAtLogin)
1431  flags += AT_LOGIN_TYPE;
1432  }
1433  if (!flags.isEmpty())
1434  ev->setCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY, flags.join(SC));
1435 
1436  if (mCommandXterm)
1437  ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, xtermURL);
1438  else if (mCommandDisplay)
1439  ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, displayURL);
1440  else if (!mLogFile.isEmpty())
1441  ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, mLogFile);
1442 
1443  ev->setCustomStatus(mEnabled ? QString() : DISABLED_STATUS);
1444  ev->setRevision(mRevision);
1445  ev->clearAlarms();
1446 
1447  /* Always set DTSTART as date/time, and use the category "DATE" to indicate
1448  * a date-only event, instead of calling setAllDay(). This is necessary to
1449  * allow a time zone to be specified for a date-only event. Also, KAlarm
1450  * allows the alarm to float within the 24-hour period defined by the
1451  * start-of-day time (which is user-dependent and therefore can't be
1452  * written into the calendar) rather than midnight to midnight, and there
1453  * is no RFC2445 conformant way to specify this.
1454  * RFC2445 states that alarm trigger times specified in absolute terms
1455  * (rather than relative to DTSTART or DTEND) can only be specified as a
1456  * UTC DATE-TIME value. So always use a time relative to DTSTART instead of
1457  * an absolute time.
1458  */
1459  ev->setDtStart(mStartDateTime.calendarKDateTime());
1460  ev->setAllDay(false);
1461  ev->setHasEndDate(false);
1462 
1463  const DateTime dtMain = archived ? mStartDateTime : mNextMainDateTime;
1464  int ancillaryType = 0; // 0 = invalid, 1 = time, 2 = offset
1465  DateTime ancillaryTime; // time for ancillary alarms (pre-action, extra audio, etc)
1466  int ancillaryOffset = 0; // start offset for ancillary alarms
1467  if (!mMainExpired || archived)
1468  {
1469  /* The alarm offset must always be zero for the main alarm. To determine
1470  * which recurrence is due, the property X-KDE-KALARM_NEXTRECUR is used.
1471  * If the alarm offset was non-zero, exception dates and rules would not
1472  * work since they apply to the event time, not the alarm time.
1473  */
1474  if (!archived && checkRecur() != KARecurrence::NO_RECUR)
1475  {
1476  QDateTime dt = mNextMainDateTime.kDateTime().toTimeSpec(mStartDateTime.timeSpec()).dateTime();
1477  ev->setCustomProperty(KACalendar::APPNAME, NEXT_RECUR_PROPERTY,
1478  dt.toString(mNextMainDateTime.isDateOnly() ? "yyyyMMdd" : "yyyyMMddThhmmss"));
1479  }
1480  // Add the main alarm
1481  initKCalAlarm(ev, 0, QStringList(), MAIN_ALARM);
1482  ancillaryOffset = 0;
1483  ancillaryType = dtMain.isValid() ? 2 : 0;
1484  }
1485  else if (mRepetition)
1486  {
1487  // Alarm repetition is normally held in the main alarm, but since
1488  // the main alarm has expired, store in a custom property.
1489  const QString param = QString("%1:%2").arg(mRepetition.intervalMinutes()).arg(mRepetition.count());
1490  ev->setCustomProperty(KACalendar::APPNAME, REPEAT_PROPERTY, param);
1491  }
1492 
1493  // Add subsidiary alarms
1494  if (mRepeatAtLogin || (mArchiveRepeatAtLogin && archived))
1495  {
1496  DateTime dtl;
1497  if (mArchiveRepeatAtLogin)
1498  dtl = mStartDateTime.calendarKDateTime().addDays(-1);
1499  else if (mAtLoginDateTime.isValid())
1500  dtl = mAtLoginDateTime;
1501  else if (mStartDateTime.isDateOnly())
1502  dtl = DateTime(KDateTime::currentLocalDate().addDays(-1), mStartDateTime.timeSpec());
1503  else
1504  dtl = KDateTime::currentUtcDateTime();
1505  initKCalAlarm(ev, dtl, QStringList(AT_LOGIN_TYPE));
1506  if (!ancillaryType && dtl.isValid())
1507  {
1508  ancillaryTime = dtl;
1509  ancillaryType = 1;
1510  }
1511  }
1512 
1513  // Find the base date/time for calculating alarm offsets
1514  DateTime nextDateTime = mNextMainDateTime;
1515  if (mMainExpired)
1516  {
1517  if (checkRecur() == KARecurrence::NO_RECUR)
1518  nextDateTime = mStartDateTime;
1519  else if (!archived)
1520  {
1521  // It's a deferral of an expired recurrence.
1522  // Need to ensure that the alarm offset is to an occurrence
1523  // which isn't excluded by an exception - otherwise, it will
1524  // never be triggered. So choose the first recurrence which
1525  // isn't an exception.
1526  KDateTime dt = mRecurrence->getNextDateTime(mStartDateTime.addDays(-1).kDateTime());
1527  dt.setDateOnly(mStartDateTime.isDateOnly());
1528  nextDateTime = dt;
1529  }
1530  }
1531 
1532  if (mReminderMinutes && (mReminderActive != NO_REMINDER || archived))
1533  {
1534  int startOffset;
1535  if (mReminderMinutes < 0 && mReminderActive != NO_REMINDER)
1536  {
1537  // A reminder AFTER the main alarm is active or disabled
1538  startOffset = nextDateTime.calendarKDateTime().secsTo(mReminderAfterTime.calendarKDateTime());
1539  }
1540  else
1541  {
1542  // A reminder BEFORE the main alarm is active
1543  startOffset = -mReminderMinutes * 60;
1544  }
1545  initKCalAlarm(ev, startOffset, QStringList(REMINDER_TYPE));
1546  // Don't set ancillary time if the reminder AFTER is hidden by a deferral
1547  if (!ancillaryType && (mReminderActive == ACTIVE_REMINDER || archived))
1548  {
1549  ancillaryOffset = startOffset;
1550  ancillaryType = 2;
1551  }
1552  }
1553  if (mDeferral != NO_DEFERRAL)
1554  {
1555  int startOffset;
1556  QStringList list;
1557  if (mDeferralTime.isDateOnly())
1558  {
1559  startOffset = nextDateTime.secsTo(mDeferralTime.calendarKDateTime());
1560  list += DATE_DEFERRAL_TYPE;
1561  }
1562  else
1563  {
1564  startOffset = nextDateTime.calendarKDateTime().secsTo(mDeferralTime.calendarKDateTime());
1565  list += TIME_DEFERRAL_TYPE;
1566  }
1567  if (mDeferral == REMINDER_DEFERRAL)
1568  list += REMINDER_TYPE;
1569  initKCalAlarm(ev, startOffset, list);
1570  if (!ancillaryType && mDeferralTime.isValid())
1571  {
1572  ancillaryOffset = startOffset;
1573  ancillaryType = 2;
1574  }
1575  }
1576  if (!mTemplateName.isEmpty())
1577  ev->setSummary(mTemplateName);
1578  else if (mDisplaying)
1579  {
1580  QStringList list(DISPLAYING_TYPE);
1581  if (mDisplayingFlags & REPEAT_AT_LOGIN)
1582  list += AT_LOGIN_TYPE;
1583  else if (mDisplayingFlags & DEFERRAL)
1584  {
1585  if (mDisplayingFlags & TIMED_FLAG)
1586  list += TIME_DEFERRAL_TYPE;
1587  else
1588  list += DATE_DEFERRAL_TYPE;
1589  }
1590  if (mDisplayingFlags & REMINDER)
1591  list += REMINDER_TYPE;
1592  initKCalAlarm(ev, mDisplayingTime, list);
1593  if (!ancillaryType && mDisplayingTime.isValid())
1594  {
1595  ancillaryTime = mDisplayingTime;
1596  ancillaryType = 1;
1597  }
1598  }
1599  if ((mBeep || mSpeak || !mAudioFile.isEmpty()) && mActionSubType != AUDIO)
1600  {
1601  // A sound is specified
1602  if (ancillaryType == 2)
1603  initKCalAlarm(ev, ancillaryOffset, QStringList(), AUDIO_ALARM);
1604  else
1605  initKCalAlarm(ev, ancillaryTime, QStringList(), AUDIO_ALARM);
1606  }
1607  if (!mPreAction.isEmpty())
1608  {
1609  // A pre-display action is specified
1610  if (ancillaryType == 2)
1611  initKCalAlarm(ev, ancillaryOffset, QStringList(PRE_ACTION_TYPE), PRE_ACTION_ALARM);
1612  else
1613  initKCalAlarm(ev, ancillaryTime, QStringList(PRE_ACTION_TYPE), PRE_ACTION_ALARM);
1614  }
1615  if (!mPostAction.isEmpty())
1616  {
1617  // A post-display action is specified
1618  if (ancillaryType == 2)
1619  initKCalAlarm(ev, ancillaryOffset, QStringList(POST_ACTION_TYPE), POST_ACTION_ALARM);
1620  else
1621  initKCalAlarm(ev, ancillaryTime, QStringList(POST_ACTION_TYPE), POST_ACTION_ALARM);
1622  }
1623 
1624  if (mRecurrence)
1625  mRecurrence->writeRecurrence(*ev->recurrence());
1626  else
1627  ev->clearRecurrence();
1628  if (mCreatedDateTime.isValid())
1629  ev->setCreated(mCreatedDateTime);
1630  ev->setReadOnly(readOnly);
1631  ev->endUpdates(); // finally issue an update notification
1632  return true;
1633 }
1634 
1635 /******************************************************************************
1636 * Create a new alarm for a libkcal event, and initialise it according to the
1637 * alarm action. If 'types' is non-null, it is appended to the X-KDE-KALARM-TYPE
1638 * property value list.
1639 * NOTE: The variant taking a DateTime calculates the offset from mStartDateTime,
1640 * which is not suitable for an alarm in a recurring event.
1641 */
1642 #ifndef KALARMCAL_USE_KRESOURCES
1643 Alarm::Ptr KAEvent::Private::initKCalAlarm(const Event::Ptr& event, const DateTime& dt, const QStringList& types, AlarmType type) const
1644 #else
1645 Alarm* KAEvent::Private::initKCalAlarm(Event* event, const DateTime& dt, const QStringList& types, AlarmType type) const
1646 #endif
1647 {
1648  const int startOffset = dt.isDateOnly() ? mStartDateTime.secsTo(dt)
1649  : mStartDateTime.calendarKDateTime().secsTo(dt.calendarKDateTime());
1650  return initKCalAlarm(event, startOffset, types, type);
1651 }
1652 
1653 #ifndef KALARMCAL_USE_KRESOURCES
1654 Alarm::Ptr KAEvent::Private::initKCalAlarm(const Event::Ptr& event, int startOffsetSecs, const QStringList& types, AlarmType type) const
1655 #else
1656 Alarm* KAEvent::Private::initKCalAlarm(Event* event, int startOffsetSecs, const QStringList& types, AlarmType type) const
1657 #endif
1658 {
1659  QStringList alltypes;
1660  QStringList flags;
1661 #ifndef KALARMCAL_USE_KRESOURCES
1662  Alarm::Ptr alarm = event->newAlarm();
1663 #else
1664  Alarm* alarm = event->newAlarm();
1665 #endif
1666  alarm->setEnabled(true);
1667  if (type != MAIN_ALARM)
1668  {
1669  // RFC2445 specifies that absolute alarm times must be stored as a UTC DATE-TIME value.
1670  // Set the alarm time as an offset to DTSTART for the reasons described in updateKCalEvent().
1671  alarm->setStartOffset(startOffsetSecs);
1672  }
1673 
1674  switch (type)
1675  {
1676  case AUDIO_ALARM:
1677  setAudioAlarm(alarm);
1678  if (mSpeak)
1679  flags << Private::SPEAK_FLAG;
1680  if (mRepeatSoundPause >= 0)
1681  {
1682  // Alarm::setSnoozeTime() sets 5 seconds if duration parameter is zero,
1683  // so repeat count = -1 represents 0 pause, -2 represents non-zero pause.
1684  alarm->setRepeatCount(mRepeatSoundPause ? -2 : -1);
1685  alarm->setSnoozeTime(Duration(mRepeatSoundPause, Duration::Seconds));
1686  }
1687  break;
1688  case PRE_ACTION_ALARM:
1689  setProcedureAlarm(alarm, mPreAction);
1690  if (mExtraActionOptions & ExecPreActOnDeferral)
1691  flags << Private::EXEC_ON_DEFERRAL_FLAG;
1692  if (mExtraActionOptions & CancelOnPreActError)
1693  flags << Private::CANCEL_ON_ERROR_FLAG;
1694  if (mExtraActionOptions & DontShowPreActError)
1695  flags << Private::DONT_SHOW_ERROR_FLAG;
1696  break;
1697  case POST_ACTION_ALARM:
1698  setProcedureAlarm(alarm, mPostAction);
1699  break;
1700  case MAIN_ALARM:
1701  alarm->setSnoozeTime(mRepetition.interval());
1702  alarm->setRepeatCount(mRepetition.count());
1703  if (mRepetition)
1704  alarm->setCustomProperty(KACalendar::APPNAME, NEXT_REPEAT_PROPERTY,
1705  QString::number(mNextRepeat));
1706  // fall through to INVALID_ALARM
1707  case REMINDER_ALARM:
1708  case INVALID_ALARM:
1709  {
1710  if (types == QStringList(REMINDER_TYPE)
1711  && mReminderMinutes < 0 && mReminderActive == HIDDEN_REMINDER)
1712  {
1713  // It's a reminder AFTER the alarm which is currently disabled
1714  // due to the main alarm being deferred past it.
1715  flags << HIDDEN_REMINDER_FLAG;
1716  }
1717  bool display = false;
1718  switch (mActionSubType)
1719  {
1720  case FILE:
1721  alltypes += FILE_TYPE;
1722  // fall through to MESSAGE
1723  case MESSAGE:
1724  alarm->setDisplayAlarm(AlarmText::toCalendarText(mText));
1725  display = true;
1726  break;
1727  case COMMAND:
1728  if (mCommandScript)
1729  alarm->setProcedureAlarm("", mText);
1730  else
1731  setProcedureAlarm(alarm, mText);
1732  display = mCommandDisplay;
1733  break;
1734  case EMAIL:
1735  alarm->setEmailAlarm(mEmailSubject, mText, mEmailAddresses, mEmailAttachments);
1736  if (mEmailFromIdentity)
1737  flags << Private::EMAIL_ID_FLAG << QString::number(mEmailFromIdentity);
1738  break;
1739  case AUDIO:
1740  setAudioAlarm(alarm);
1741  if (mRepeatSoundPause >= 0)
1742  {
1743  alltypes += SOUND_REPEAT_TYPE;
1744  if (type == MAIN_ALARM)
1745  alltypes += QString::number(mRepeatSoundPause);
1746  }
1747  break;
1748  }
1749  if (display)
1750  alarm->setCustomProperty(KACalendar::APPNAME, FONT_COLOUR_PROPERTY,
1751  QString::fromLatin1("%1;%2;%3").arg(mBgColour.name())
1752  .arg(mFgColour.name())
1753  .arg(mUseDefaultFont ? QString() : mFont.toString()));
1754  break;
1755  }
1756  case DEFERRED_ALARM:
1757  case DEFERRED_REMINDER_ALARM:
1758  case AT_LOGIN_ALARM:
1759  case DISPLAYING_ALARM:
1760  break;
1761  }
1762  alltypes += types;
1763  if (!alltypes.isEmpty())
1764  alarm->setCustomProperty(KACalendar::APPNAME, TYPE_PROPERTY, alltypes.join(","));
1765  if (!flags.isEmpty())
1766  alarm->setCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY, flags.join(SC));
1767  return alarm;
1768 }
1769 
1770 bool KAEvent::isValid() const
1771 {
1772  return d->mAlarmCount && (d->mAlarmCount != 1 || !d->mRepeatAtLogin);
1773 }
1774 
1775 void KAEvent::setEnabled(bool enable)
1776 {
1777  d->mEnabled = enable;
1778 }
1779 
1780 bool KAEvent::enabled() const
1781 {
1782  return d->mEnabled;
1783 }
1784 
1785 #ifndef KALARMCAL_USE_KRESOURCES
1786 void KAEvent::setReadOnly(bool ro)
1787 {
1788  d->mReadOnly = ro;
1789 }
1790 
1791 bool KAEvent::isReadOnly() const
1792 {
1793  return d->mReadOnly;
1794 }
1795 #endif
1796 
1797 void KAEvent::setArchive()
1798 {
1799  d->mArchive = true;
1800 }
1801 
1802 bool KAEvent::toBeArchived() const
1803 {
1804  return d->mArchive;
1805 }
1806 
1807 bool KAEvent::mainExpired() const
1808 {
1809  return d->mMainExpired;
1810 }
1811 
1812 bool KAEvent::expired() const
1813 {
1814  return (d->mDisplaying && d->mMainExpired) || d->mCategory == CalEvent::ARCHIVED;
1815 }
1816 
1817 KAEvent::Flags KAEvent::flags() const
1818 {
1819  return d->flags();
1820 }
1821 
1822 KAEvent::Flags KAEvent::Private::flags() const
1823 {
1824  Flags result(0);
1825  if (mBeep) result |= BEEP;
1826  if (mRepeatSoundPause >= 0) result |= REPEAT_SOUND;
1827  if (mEmailBcc) result |= EMAIL_BCC;
1828  if (mStartDateTime.isDateOnly()) result |= ANY_TIME;
1829  if (mSpeak) result |= SPEAK;
1830  if (mRepeatAtLogin) result |= REPEAT_AT_LOGIN;
1831  if (mConfirmAck) result |= CONFIRM_ACK;
1832  if (mUseDefaultFont) result |= DEFAULT_FONT;
1833  if (mCommandScript) result |= SCRIPT;
1834  if (mCommandXterm) result |= EXEC_IN_XTERM;
1835  if (mCommandDisplay) result |= DISPLAY_COMMAND;
1836  if (mCopyToKOrganizer) result |= COPY_KORGANIZER;
1837  if (mExcludeHolidays) result |= EXCL_HOLIDAYS;
1838  if (mWorkTimeOnly) result |= WORK_TIME_ONLY;
1839  if (mReminderOnceOnly) result |= REMINDER_ONCE;
1840  if (mAutoClose) result |= AUTO_CLOSE;
1841  if (!mEnabled) result |= DISABLED;
1842  return result;
1843 }
1844 
1845 /******************************************************************************
1846 * Change the type of an event.
1847 * If it is being set to archived, set the archived indication in the event ID;
1848 * otherwise, remove the archived indication from the event ID.
1849 */
1850 void KAEvent::setCategory(CalEvent::Type s)
1851 {
1852  d->setCategory(s);
1853 }
1854 
1855 void KAEvent::Private::setCategory(CalEvent::Type s)
1856 {
1857  if (s == mCategory)
1858  return;
1859  mEventID = CalEvent::uid(mEventID, s);
1860  mCategory = s;
1861  mTriggerChanged = true; // templates and archived don't have trigger times
1862 }
1863 
1864 CalEvent::Type KAEvent::category() const
1865 {
1866  return d->mCategory;
1867 }
1868 
1869 void KAEvent::setEventId(const QString& id)
1870 {
1871  d->mEventID = id;
1872 }
1873 
1874 QString KAEvent::id() const
1875 {
1876  return d->mEventID;
1877 }
1878 
1879 void KAEvent::incrementRevision()
1880 {
1881  ++d->mRevision;
1882 }
1883 
1884 int KAEvent::revision() const
1885 {
1886  return d->mRevision;
1887 }
1888 
1889 #ifndef KALARMCAL_USE_KRESOURCES
1890 void KAEvent::setCollectionId(Akonadi::Collection::Id id)
1891 {
1892  d->mCollectionId = id;
1893 }
1894 
1895 void KAEvent::setCollectionId_const(Akonadi::Collection::Id id) const
1896 {
1897  d->mCollectionId = id;
1898 }
1899 
1900 Akonadi::Collection::Id KAEvent::collectionId() const
1901 {
1902  // A displaying alarm contains the event's original collection ID
1903  return d->mDisplaying ? -1 : d->mCollectionId;
1904 }
1905 
1906 void KAEvent::setItemId(Akonadi::Item::Id id)
1907 {
1908  d->mItemId = id;
1909 }
1910 
1911 Akonadi::Item::Id KAEvent::itemId() const
1912 {
1913  return d->mItemId;
1914 }
1915 
1916 /******************************************************************************
1917 * Initialise an Item with the event.
1918 * Note that the event is not updated with the Item ID.
1919 * Reply = true if successful,
1920 * false if event's category does not match collection's mime types.
1921 */
1922 bool KAEvent::setItemPayload(Akonadi::Item& item, const QStringList& collectionMimeTypes) const
1923 {
1924  QString mimetype;
1925  switch (d->mCategory)
1926  {
1927  case CalEvent::ACTIVE: mimetype = MIME_ACTIVE; break;
1928  case CalEvent::ARCHIVED: mimetype = MIME_ARCHIVED; break;
1929  case CalEvent::TEMPLATE: mimetype = MIME_TEMPLATE; break;
1930  default: Q_ASSERT(0); return false;
1931  }
1932  if (!collectionMimeTypes.contains(mimetype))
1933  return false;
1934  item.setMimeType(mimetype);
1935  item.setPayload<KAEvent>(*this);
1936  return true;
1937 }
1938 
1939 void KAEvent::setCompatibility(KACalendar::Compat c)
1940 {
1941  d->mCompatibility = c;
1942 }
1943 
1944 KACalendar::Compat KAEvent::compatibility() const
1945 {
1946  return d->mCompatibility;
1947 }
1948 
1949 QMap<QByteArray, QString> KAEvent::customProperties() const
1950 {
1951  return d->mCustomProperties;
1952 }
1953 
1954 #else
1955 void KAEvent::setResource(AlarmResource* r)
1956 {
1957  d->mResource = r;
1958 }
1959 
1960 AlarmResource* KAEvent::resource() const
1961 {
1962  return d->mResource;
1963 }
1964 #endif
1965 
1966 KAEvent::SubAction KAEvent::actionSubType() const
1967 {
1968  return d->mActionSubType;
1969 }
1970 
1971 KAEvent::Actions KAEvent::actionTypes() const
1972 {
1973  switch (d->mActionSubType)
1974  {
1975  case MESSAGE:
1976  case FILE: return ACT_DISPLAY;
1977  case COMMAND: return d->mCommandDisplay ? ACT_DISPLAY_COMMAND : ACT_COMMAND;
1978  case EMAIL: return ACT_EMAIL;
1979  case AUDIO: return ACT_AUDIO;
1980  default: return ACT_NONE;
1981  }
1982 }
1983 
1984 void KAEvent::setLateCancel(int minutes)
1985 {
1986  if (d->mRepeatAtLogin)
1987  minutes = 0;
1988  d->mLateCancel = minutes;
1989  if (!minutes)
1990  d->mAutoClose = false;
1991 }
1992 
1993 int KAEvent::lateCancel() const
1994 {
1995  return d->mLateCancel;
1996 }
1997 
1998 void KAEvent::setAutoClose(bool ac)
1999 {
2000  d->mAutoClose = ac;
2001 }
2002 
2003 bool KAEvent::autoClose() const
2004 {
2005  return d->mAutoClose;
2006 }
2007 
2008 void KAEvent::setKMailSerialNumber(unsigned long n)
2009 {
2010  d->mKMailSerialNumber = n;
2011 }
2012 
2013 unsigned long KAEvent::kmailSerialNumber() const
2014 {
2015  return d->mKMailSerialNumber;
2016 }
2017 
2018 QString KAEvent::cleanText() const
2019 {
2020  return d->mText;
2021 }
2022 
2023 QString KAEvent::message() const
2024 {
2025  return (d->mActionSubType == MESSAGE
2026  || d->mActionSubType == EMAIL) ? d->mText : QString();
2027 }
2028 
2029 QString KAEvent::displayMessage() const
2030 {
2031  return (d->mActionSubType == MESSAGE) ? d->mText : QString();
2032 }
2033 
2034 QString KAEvent::fileName() const
2035 {
2036  return (d->mActionSubType == FILE) ? d->mText : QString();
2037 }
2038 
2039 QColor KAEvent::bgColour() const
2040 {
2041  return d->mBgColour;
2042 }
2043 
2044 QColor KAEvent::fgColour() const
2045 {
2046  return d->mFgColour;
2047 }
2048 
2049 void KAEvent::setDefaultFont(const QFont& f)
2050 {
2051  Private::mDefaultFont = f;
2052 }
2053 
2054 bool KAEvent::useDefaultFont() const
2055 {
2056  return d->mUseDefaultFont;
2057 }
2058 
2059 QFont KAEvent::font() const
2060 {
2061  return d->mUseDefaultFont ? Private::mDefaultFont : d->mFont;
2062 }
2063 
2064 QString KAEvent::command() const
2065 {
2066  return (d->mActionSubType == COMMAND) ? d->mText : QString();
2067 }
2068 
2069 bool KAEvent::commandScript() const
2070 {
2071  return d->mCommandScript;
2072 }
2073 
2074 bool KAEvent::commandXterm() const
2075 {
2076  return d->mCommandXterm;
2077 }
2078 
2079 bool KAEvent::commandDisplay() const
2080 {
2081  return d->mCommandDisplay;
2082 }
2083 
2084 #ifndef KALARMCAL_USE_KRESOURCES
2085 void KAEvent::setCommandError(CmdErrType t) const
2086 {
2087  d->mCommandError = t;
2088 }
2089 
2090 #else
2091 /******************************************************************************
2092 * Set the command last error status.
2093 * If 'writeConfig' is true, the status is written to the config file.
2094 */
2095 void KAEvent::setCommandError(CmdErrType t, bool writeConfig) const
2096 {
2097  d->setCommandError(t, writeConfig);
2098 }
2099 
2100 void KAEvent::Private::setCommandError(CmdErrType error, bool writeConfig) const
2101 {
2102  kDebug() << mEventID << "," << error;
2103  if (error == mCommandError)
2104  return;
2105  mCommandError = error;
2106  if (writeConfig)
2107  {
2108  KConfigGroup config(KGlobal::config(), mCmdErrConfigGroup);
2109  if (mCommandError == CMD_NO_ERROR)
2110  config.deleteEntry(mEventID);
2111  else
2112  {
2113  QString errtext;
2114  switch (mCommandError)
2115  {
2116  case CMD_ERROR: errtext = CMD_ERROR_VALUE; break;
2117  case CMD_ERROR_PRE: errtext = CMD_ERROR_PRE_VALUE; break;
2118  case CMD_ERROR_POST: errtext = CMD_ERROR_POST_VALUE; break;
2119  case CMD_ERROR_PRE_POST:
2120  errtext = CMD_ERROR_PRE_VALUE + ',' + CMD_ERROR_POST_VALUE;
2121  break;
2122  default:
2123  break;
2124  }
2125  config.writeEntry(mEventID, errtext);
2126  }
2127  config.sync();
2128  }
2129 }
2130 
2131 /******************************************************************************
2132 * Initialise the command last error status of the alarm from the config file.
2133 */
2134 void KAEvent::setCommandError(const QString& configString)
2135 {
2136  d->setCommandError(configString);
2137 }
2138 
2139 void KAEvent::Private::setCommandError(const QString& configString)
2140 {
2141  mCommandError = CMD_NO_ERROR;
2142  const QStringList errs = configString.split(',');
2143  if (errs.indexOf(CMD_ERROR_VALUE) >= 0)
2144  mCommandError = CMD_ERROR;
2145  else
2146  {
2147  if (errs.indexOf(CMD_ERROR_PRE_VALUE) >= 0)
2148  mCommandError = CMD_ERROR_PRE;
2149  if (errs.indexOf(CMD_ERROR_POST_VALUE) >= 0)
2150  mCommandError = static_cast<CmdErrType>(mCommandError | CMD_ERROR_POST);
2151  }
2152 }
2153 
2154 QString KAEvent::commandErrorConfigGroup()
2155 {
2156  return Private::mCmdErrConfigGroup;
2157 }
2158 #endif
2159 
2160 KAEvent::CmdErrType KAEvent::commandError() const
2161 {
2162  return d->mCommandError;
2163 }
2164 
2165 void KAEvent::setLogFile(const QString& logfile)
2166 {
2167  d->mLogFile = logfile;
2168  if (!logfile.isEmpty())
2169  d->mCommandDisplay = d->mCommandXterm = false;
2170 }
2171 
2172 QString KAEvent::logFile() const
2173 {
2174  return d->mLogFile;
2175 }
2176 
2177 bool KAEvent::confirmAck() const
2178 {
2179  return d->mConfirmAck;
2180 }
2181 
2182 bool KAEvent::copyToKOrganizer() const
2183 {
2184  return d->mCopyToKOrganizer;
2185 }
2186 
2187 #ifndef KALARMCAL_USE_KRESOURCES
2188 void KAEvent::setEmail(uint from, const KCalCore::Person::List& addresses, const QString& subject,
2189  const QStringList& attachments)
2190 #else
2191 void KAEvent::setEmail(uint from, const QList<KCal::Person>& addresses, const QString& subject,
2192  const QStringList& attachments)
2193 #endif
2194 {
2195  d->mEmailFromIdentity = from;
2196  d->mEmailAddresses = addresses;
2197  d->mEmailSubject = subject;
2198  d->mEmailAttachments = attachments;
2199 }
2200 
2201 QString KAEvent::emailMessage() const
2202 {
2203  return (d->mActionSubType == EMAIL) ? d->mText : QString();
2204 }
2205 
2206 uint KAEvent::emailFromId() const
2207 {
2208  return d->mEmailFromIdentity;
2209 }
2210 
2211 #ifndef KALARMCAL_USE_KRESOURCES
2212 KCalCore::Person::List KAEvent::emailAddressees() const
2213 #else
2214 QList<KCal::Person> KAEvent::emailAddressees() const
2215 #endif
2216 {
2217  return d->mEmailAddresses;
2218 }
2219 
2220 QStringList KAEvent::emailAddresses() const
2221 {
2222  return static_cast<QStringList>(d->mEmailAddresses);
2223 }
2224 
2225 QString KAEvent::emailAddresses(const QString& sep) const
2226 {
2227  return d->mEmailAddresses.join(sep);
2228 }
2229 
2230 #ifndef KALARMCAL_USE_KRESOURCES
2231 QString KAEvent::joinEmailAddresses(const KCalCore::Person::List& addresses, const QString& separator)
2232 #else
2233 QString KAEvent::joinEmailAddresses(const QList<KCal::Person>& addresses, const QString& separator)
2234 #endif
2235 {
2236  return EmailAddressList(addresses).join(separator);
2237 }
2238 
2239 QStringList KAEvent::emailPureAddresses() const
2240 {
2241  return d->mEmailAddresses.pureAddresses();
2242 }
2243 
2244 QString KAEvent::emailPureAddresses(const QString& sep) const
2245 {
2246  return d->mEmailAddresses.pureAddresses(sep);
2247 }
2248 
2249 QString KAEvent::emailSubject() const
2250 {
2251  return d->mEmailSubject;
2252 }
2253 
2254 QStringList KAEvent::emailAttachments() const
2255 {
2256  return d->mEmailAttachments;
2257 }
2258 
2259 QString KAEvent::emailAttachments(const QString& sep) const
2260 {
2261  return d->mEmailAttachments.join(sep);
2262 }
2263 
2264 bool KAEvent::emailBcc() const
2265 {
2266  return d->mEmailBcc;
2267 }
2268 
2269 void KAEvent::setAudioFile(const QString& filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile)
2270 {
2271  d->setAudioFile(filename, volume, fadeVolume, fadeSeconds, repeatPause, allowEmptyFile);
2272 }
2273 
2274 void KAEvent::Private::setAudioFile(const QString& filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile)
2275 {
2276  mAudioFile = filename;
2277  mSoundVolume = (!allowEmptyFile && filename.isEmpty()) ? -1 : volume;
2278  if (mSoundVolume >= 0)
2279  {
2280  mFadeVolume = (fadeSeconds > 0) ? fadeVolume : -1;
2281  mFadeSeconds = (mFadeVolume >= 0) ? fadeSeconds : 0;
2282  }
2283  else
2284  {
2285  mFadeVolume = -1;
2286  mFadeSeconds = 0;
2287  }
2288  mRepeatSoundPause = repeatPause;
2289 }
2290 
2291 QString KAEvent::audioFile() const
2292 {
2293  return d->mAudioFile;
2294 }
2295 
2296 float KAEvent::soundVolume() const
2297 {
2298  return d->mSoundVolume;
2299 }
2300 
2301 float KAEvent::fadeVolume() const
2302 {
2303  return d->mSoundVolume >= 0 && d->mFadeSeconds ? d->mFadeVolume : -1;
2304 }
2305 
2306 int KAEvent::fadeSeconds() const
2307 {
2308  return d->mSoundVolume >= 0 && d->mFadeVolume >= 0 ? d->mFadeSeconds : 0;
2309 }
2310 
2311 bool KAEvent::repeatSound() const
2312 {
2313  return d->mRepeatSoundPause >= 0;
2314 }
2315 
2316 int KAEvent::repeatSoundPause() const
2317 {
2318  return d->mRepeatSoundPause;
2319 }
2320 
2321 bool KAEvent::beep() const
2322 {
2323  return d->mBeep;
2324 }
2325 
2326 bool KAEvent::speak() const
2327 {
2328  return (d->mActionSubType == MESSAGE
2329  || (d->mActionSubType == COMMAND && d->mCommandDisplay))
2330  && d->mSpeak;
2331 }
2332 
2333 /******************************************************************************
2334 * Set the event to be an alarm template.
2335 */
2336 void KAEvent::setTemplate(const QString& name, int afterTime)
2337 {
2338  d->setCategory(CalEvent::TEMPLATE);
2339  d->mTemplateName = name;
2340  d->mTemplateAfterTime = afterTime;
2341  d->mTriggerChanged = true; // templates and archived don't have trigger times
2342 }
2343 
2344 bool KAEvent::isTemplate() const
2345 {
2346  return !d->mTemplateName.isEmpty();
2347 }
2348 
2349 QString KAEvent::templateName() const
2350 {
2351  return d->mTemplateName;
2352 }
2353 
2354 bool KAEvent::usingDefaultTime() const
2355 {
2356  return d->mTemplateAfterTime == 0;
2357 }
2358 
2359 int KAEvent::templateAfterTime() const
2360 {
2361  return d->mTemplateAfterTime;
2362 }
2363 
2364 void KAEvent::setActions(const QString& pre, const QString& post, ExtraActionOptions options)
2365 {
2366  d->mPreAction = pre;
2367  d->mPostAction = post;
2368  d->mExtraActionOptions = options;
2369 }
2370 
2371 void KAEvent::setActions(const QString& pre, const QString& post, bool cancelOnError, bool dontShowError)
2372 {
2373  ExtraActionOptions opts(0);
2374  if (cancelOnError)
2375  opts |= CancelOnPreActError;
2376  if (dontShowError)
2377  opts |= DontShowPreActError;
2378  setActions(pre, post, opts);
2379 }
2380 
2381 QString KAEvent::preAction() const
2382 {
2383  return d->mPreAction;
2384 }
2385 
2386 QString KAEvent::postAction() const
2387 {
2388  return d->mPostAction;
2389 }
2390 
2391 KAEvent::ExtraActionOptions KAEvent::extraActionOptions() const
2392 {
2393  return d->mExtraActionOptions;
2394 }
2395 
2396 bool KAEvent::cancelOnPreActionError() const
2397 {
2398  return d->mExtraActionOptions & CancelOnPreActError;
2399 }
2400 
2401 bool KAEvent::dontShowPreActionError() const
2402 {
2403  return d->mExtraActionOptions & DontShowPreActError;
2404 }
2405 
2406 /******************************************************************************
2407 * Set a reminder.
2408 * 'minutes' = number of minutes BEFORE the main alarm.
2409 */
2410 void KAEvent::setReminder(int minutes, bool onceOnly)
2411 {
2412  d->setReminder(minutes, onceOnly);
2413 }
2414 
2415 void KAEvent::Private::setReminder(int minutes, bool onceOnly)
2416 {
2417  if (minutes > 0 && mRepeatAtLogin)
2418  minutes = 0;
2419  if (minutes != mReminderMinutes || (minutes && mReminderActive != ACTIVE_REMINDER))
2420  {
2421  if (minutes && mReminderActive == NO_REMINDER)
2422  ++mAlarmCount;
2423  else if (!minutes && mReminderActive != NO_REMINDER)
2424  --mAlarmCount;
2425  mReminderMinutes = minutes;
2426  mReminderActive = minutes ? ACTIVE_REMINDER : NO_REMINDER;
2427  mReminderOnceOnly = onceOnly;
2428  mReminderAfterTime = DateTime();
2429  mTriggerChanged = true;
2430  }
2431 }
2432 
2433 /******************************************************************************
2434 * Activate the event's reminder which occurs AFTER the given main alarm time.
2435 * Reply = true if successful (i.e. reminder falls before the next main alarm).
2436 */
2437 void KAEvent::activateReminderAfter(const DateTime& mainAlarmTime)
2438 {
2439  d->activateReminderAfter(mainAlarmTime);
2440 }
2441 
2442 void KAEvent::Private::activateReminderAfter(const DateTime& mainAlarmTime)
2443 {
2444  if (mReminderMinutes >= 0 || mReminderActive == ACTIVE_REMINDER || !mainAlarmTime.isValid())
2445  return;
2446  // There is a reminder AFTER the main alarm.
2447  if (checkRecur() != KARecurrence::NO_RECUR)
2448  {
2449  // For a recurring alarm, the given alarm time must be a recurrence, not a sub-repetition.
2450  DateTime next;
2451  //???? For some unknown reason, addSecs(-1) returns the recurrence after the next,
2452  //???? so addSecs(-60) is used instead.
2453  if (nextRecurrence(mainAlarmTime.addSecs(-60).effectiveKDateTime(), next) == NO_OCCURRENCE
2454  || mainAlarmTime != next)
2455  return;
2456  }
2457  else if (!mRepeatAtLogin)
2458  {
2459  // For a non-recurring alarm, the given alarm time must be the main alarm time.
2460  if (mainAlarmTime != mStartDateTime)
2461  return;
2462  }
2463 
2464  const DateTime reminderTime = mainAlarmTime.addMins(-mReminderMinutes);
2465  DateTime next;
2466  if (nextOccurrence(mainAlarmTime.effectiveKDateTime(), next, RETURN_REPETITION) != NO_OCCURRENCE
2467  && reminderTime >= next)
2468  return; // the reminder time is after the next occurrence of the main alarm
2469 
2470  kDebug() << "Setting reminder at" << reminderTime.effectiveKDateTime().dateTime();
2471  activate_reminder(true);
2472  mReminderAfterTime = reminderTime;
2473 }
2474 
2475 int KAEvent::reminderMinutes() const
2476 {
2477  return d->mReminderMinutes;
2478 }
2479 
2480 bool KAEvent::reminderActive() const
2481 {
2482  return d->mReminderActive == Private::ACTIVE_REMINDER;
2483 }
2484 
2485 bool KAEvent::reminderOnceOnly() const
2486 {
2487  return d->mReminderOnceOnly;
2488 }
2489 
2490 bool KAEvent::reminderDeferral() const
2491 {
2492  return d->mDeferral == Private::REMINDER_DEFERRAL;
2493 }
2494 
2495 /******************************************************************************
2496 * Defer the event to the specified time.
2497 * If the main alarm time has passed, the main alarm is marked as expired.
2498 * If 'adjustRecurrence' is true, ensure that the next scheduled recurrence is
2499 * after the current time.
2500 */
2501 void KAEvent::defer(const DateTime& dt, bool reminder, bool adjustRecurrence)
2502 {
2503  return d->defer(dt, reminder, adjustRecurrence);
2504 }
2505 
2506 void KAEvent::Private::defer(const DateTime& dateTime, bool reminder, bool adjustRecurrence)
2507 {
2508  startChanges(); // prevent multiple trigger time evaluation here
2509  bool setNextRepetition = false;
2510  bool checkRepetition = false;
2511  bool checkReminderAfter = false;
2512  if (checkRecur() == KARecurrence::NO_RECUR)
2513  {
2514  // Deferring a non-recurring alarm
2515  if (mReminderMinutes)
2516  {
2517  bool deferReminder = false;
2518  if (mReminderMinutes > 0)
2519  {
2520  // There's a reminder BEFORE the main alarm
2521  if (dateTime < mNextMainDateTime.effectiveKDateTime())
2522  deferReminder = true;
2523  else if (mReminderActive == ACTIVE_REMINDER || mDeferral == REMINDER_DEFERRAL)
2524  {
2525  // Deferring past the main alarm time, so adjust any existing deferral
2526  set_deferral(NO_DEFERRAL);
2527  mTriggerChanged = true;
2528  }
2529  }
2530  else if (mReminderMinutes < 0 && reminder)
2531  deferReminder = true; // deferring a reminder AFTER the main alarm
2532  if (deferReminder)
2533  {
2534  set_deferral(REMINDER_DEFERRAL); // defer reminder alarm
2535  mDeferralTime = dateTime;
2536  mTriggerChanged = true;
2537  }
2538  if (mReminderActive == ACTIVE_REMINDER)
2539  {
2540  activate_reminder(false);
2541  mTriggerChanged = true;
2542  }
2543  }
2544  if (mDeferral != REMINDER_DEFERRAL)
2545  {
2546  // We're deferring the main alarm.
2547  // Main alarm has now expired.
2548  mNextMainDateTime = mDeferralTime = dateTime;
2549  set_deferral(NORMAL_DEFERRAL);
2550  mTriggerChanged = true;
2551  checkReminderAfter = true;
2552  if (!mMainExpired)
2553  {
2554  // Mark the alarm as expired now
2555  mMainExpired = true;
2556  --mAlarmCount;
2557  if (mRepeatAtLogin)
2558  {
2559  // Remove the repeat-at-login alarm, but keep a note of it for archiving purposes
2560  mArchiveRepeatAtLogin = true;
2561  mRepeatAtLogin = false;
2562  --mAlarmCount;
2563  }
2564  }
2565  }
2566  }
2567  else if (reminder)
2568  {
2569  // Deferring a reminder for a recurring alarm
2570  if (dateTime >= mNextMainDateTime.effectiveKDateTime())
2571  {
2572  // Trying to defer it past the next main alarm (regardless of whether
2573  // the reminder triggered before or after the main alarm).
2574  set_deferral(NO_DEFERRAL); // (error)
2575  }
2576  else
2577  {
2578  set_deferral(REMINDER_DEFERRAL);
2579  mDeferralTime = dateTime;
2580  checkRepetition = true;
2581  }
2582  mTriggerChanged = true;
2583  }
2584  else
2585  {
2586  // Deferring a recurring alarm
2587  mDeferralTime = dateTime;
2588  if (mDeferral == NO_DEFERRAL)
2589  set_deferral(NORMAL_DEFERRAL);
2590  mTriggerChanged = true;
2591  checkReminderAfter = true;
2592  if (adjustRecurrence)
2593  {
2594  const KDateTime now = KDateTime::currentUtcDateTime();
2595  if (mainEndRepeatTime() < now)
2596  {
2597  // The last repetition (if any) of the current recurrence has already passed.
2598  // Adjust to the next scheduled recurrence after now.
2599  if (!mMainExpired && setNextOccurrence(now) == NO_OCCURRENCE)
2600  {
2601  mMainExpired = true;
2602  --mAlarmCount;
2603  }
2604  }
2605  else
2606  setNextRepetition = mRepetition;
2607  }
2608  else
2609  checkRepetition = true;
2610  }
2611  if (checkReminderAfter && mReminderMinutes < 0 && mReminderActive != NO_REMINDER)
2612  {
2613  // Enable/disable the active reminder AFTER the main alarm,
2614  // depending on whether the deferral is before or after the reminder.
2615  mReminderActive = (mDeferralTime < mReminderAfterTime) ? ACTIVE_REMINDER : HIDDEN_REMINDER;
2616  }
2617  if (checkRepetition)
2618  setNextRepetition = (mRepetition && mDeferralTime < mainEndRepeatTime());
2619  if (setNextRepetition)
2620  {
2621  // The alarm is repeated, and we're deferring to a time before the last repetition.
2622  // Set the next scheduled repetition to the one after the deferral.
2623  if (mNextMainDateTime >= mDeferralTime)
2624  mNextRepeat = 0;
2625  else
2626  mNextRepeat = mRepetition.nextRepeatCount(mNextMainDateTime.kDateTime(), mDeferralTime.kDateTime());
2627  mTriggerChanged = true;
2628  }
2629  endChanges();
2630 }
2631 
2632 /******************************************************************************
2633 * Cancel any deferral alarm.
2634 */
2635 void KAEvent::cancelDefer()
2636 {
2637  d->cancelDefer();
2638 }
2639 
2640 void KAEvent::Private::cancelDefer()
2641 {
2642  if (mDeferral != NO_DEFERRAL)
2643  {
2644  mDeferralTime = DateTime();
2645  set_deferral(NO_DEFERRAL);
2646  mTriggerChanged = true;
2647  }
2648 }
2649 
2650 void KAEvent::setDeferDefaultMinutes(int minutes, bool dateOnly)
2651 {
2652  d->mDeferDefaultMinutes = minutes;
2653  d->mDeferDefaultDateOnly = dateOnly;
2654 }
2655 
2656 bool KAEvent::deferred() const
2657 {
2658  return d->mDeferral > 0;
2659 }
2660 
2661 DateTime KAEvent::deferDateTime() const
2662 {
2663  return d->mDeferralTime;
2664 }
2665 
2666 /******************************************************************************
2667 * Find the latest time which the alarm can currently be deferred to.
2668 */
2669 DateTime KAEvent::deferralLimit(DeferLimitType* limitType) const
2670 {
2671  return d->deferralLimit(limitType);
2672 }
2673 
2674 DateTime KAEvent::Private::deferralLimit(DeferLimitType* limitType) const
2675 {
2676  DeferLimitType ltype = LIMIT_NONE;
2677  DateTime endTime;
2678  if (checkRecur() != KARecurrence::NO_RECUR)
2679  {
2680  // It's a recurring alarm. Find the latest time it can be deferred to:
2681  // it cannot be deferred past its next occurrence or sub-repetition,
2682  // or any advance reminder before that.
2683  DateTime reminderTime;
2684  const KDateTime now = KDateTime::currentUtcDateTime();
2685  const OccurType type = nextOccurrence(now, endTime, RETURN_REPETITION);
2686  if (type & OCCURRENCE_REPEAT)
2687  ltype = LIMIT_REPETITION;
2688  else if (type == NO_OCCURRENCE)
2689  ltype = LIMIT_NONE;
2690  else if (mReminderActive == ACTIVE_REMINDER && mReminderMinutes > 0
2691  && (now < (reminderTime = endTime.addMins(-mReminderMinutes))))
2692  {
2693  endTime = reminderTime;
2694  ltype = LIMIT_REMINDER;
2695  }
2696  else
2697  ltype = LIMIT_RECURRENCE;
2698  }
2699  else if (mReminderMinutes < 0)
2700  {
2701  // There is a reminder alarm which occurs AFTER the main alarm.
2702  // Don't allow the reminder to be deferred past the next main alarm time.
2703  if (KDateTime::currentUtcDateTime() < mNextMainDateTime.effectiveKDateTime())
2704  {
2705  endTime = mNextMainDateTime;
2706  ltype = LIMIT_MAIN;
2707  }
2708  }
2709  else if (mReminderMinutes > 0
2710  && KDateTime::currentUtcDateTime() < mNextMainDateTime.effectiveKDateTime())
2711  {
2712  // It's a reminder BEFORE the main alarm.
2713  // Don't allow it to be deferred past its main alarm time.
2714  endTime = mNextMainDateTime;
2715  ltype = LIMIT_MAIN;
2716  }
2717  if (ltype != LIMIT_NONE)
2718  endTime = endTime.addMins(-1);
2719  if (limitType)
2720  *limitType = ltype;
2721  return endTime;
2722 }
2723 
2724 int KAEvent::deferDefaultMinutes() const
2725 {
2726  return d->mDeferDefaultMinutes;
2727 }
2728 
2729 bool KAEvent::deferDefaultDateOnly() const
2730 {
2731  return d->mDeferDefaultDateOnly;
2732 }
2733 
2734 DateTime KAEvent::startDateTime() const
2735 {
2736  return d->mStartDateTime;
2737 }
2738 
2739 void KAEvent::setTime(const KDateTime& dt)
2740 {
2741  d->mNextMainDateTime = dt;
2742  d->mTriggerChanged = true;
2743 }
2744 
2745 DateTime KAEvent::mainDateTime(bool withRepeats) const
2746 {
2747  return d->mainDateTime(withRepeats);
2748 }
2749 
2750 QTime KAEvent::mainTime() const
2751 {
2752  return d->mNextMainDateTime.effectiveTime();
2753 }
2754 
2755 DateTime KAEvent::mainEndRepeatTime() const
2756 {
2757  return d->mainEndRepeatTime();
2758 }
2759 
2760 /******************************************************************************
2761 * Set the start-of-day time for date-only alarms.
2762 */
2763 void KAEvent::setStartOfDay(const QTime& startOfDay)
2764 {
2765  DateTime::setStartOfDay(startOfDay);
2766 #ifdef __GNUC__
2767 #warning Does this need all trigger times for date-only alarms to be recalculated?
2768 #endif
2769 }
2770 
2771 /******************************************************************************
2772 * Called when the user changes the start-of-day time.
2773 * Adjust the start time of the recurrence to match, for each date-only event in
2774 * a list.
2775 */
2776 void KAEvent::adjustStartOfDay(const KAEvent::List& events)
2777 {
2778  for (int i = 0, end = events.count(); i < end; ++i)
2779  {
2780  Private* const p = events[i]->d;
2781  if (p->mStartDateTime.isDateOnly() && p->checkRecur() != KARecurrence::NO_RECUR)
2782  p->mRecurrence->setStartDateTime(p->mStartDateTime.effectiveKDateTime(), true);
2783  }
2784 }
2785 
2786 DateTime KAEvent::nextTrigger(TriggerType type) const
2787 {
2788  d->calcTriggerTimes();
2789  switch (type)
2790  {
2791  case ALL_TRIGGER: return d->mAllTrigger;
2792  case MAIN_TRIGGER: return d->mMainTrigger;
2793  case ALL_WORK_TRIGGER: return d->mAllWorkTrigger;
2794  case WORK_TRIGGER: return d->mMainWorkTrigger;
2795  case DISPLAY_TRIGGER:
2796  {
2797  const bool reminderAfter = d->mMainExpired && d->mReminderActive && d->mReminderMinutes < 0;
2798  return d->checkRecur() != KARecurrence::NO_RECUR && (d->mWorkTimeOnly || d->mExcludeHolidays)
2799  ? (reminderAfter ? d->mAllWorkTrigger : d->mMainWorkTrigger)
2800  : (reminderAfter ? d->mAllTrigger : d->mMainTrigger);
2801  }
2802  default: return DateTime();
2803  }
2804 }
2805 
2806 void KAEvent::setCreatedDateTime(const KDateTime& dt)
2807 {
2808  d->mCreatedDateTime = dt;
2809 }
2810 
2811 KDateTime KAEvent::createdDateTime() const
2812 {
2813  return d->mCreatedDateTime;
2814 }
2815 
2816 /******************************************************************************
2817 * Set or clear repeat-at-login.
2818 */
2819 void KAEvent::setRepeatAtLogin(bool rl)
2820 {
2821  d->setRepeatAtLogin(rl);
2822 }
2823 
2824 void KAEvent::Private::setRepeatAtLogin(bool rl)
2825 {
2826  if (rl && !mRepeatAtLogin)
2827  {
2828  setRepeatAtLoginTrue(true); // clear incompatible statuses
2829  ++mAlarmCount;
2830  }
2831  else if (!rl && mRepeatAtLogin)
2832  --mAlarmCount;
2833  mRepeatAtLogin = rl;
2834  mTriggerChanged = true;
2835 }
2836 
2837 /******************************************************************************
2838 * Clear incompatible statuses when repeat-at-login is set.
2839 */
2840 void KAEvent::Private::setRepeatAtLoginTrue(bool clearReminder)
2841 {
2842  clearRecur(); // clear recurrences
2843  if (mReminderMinutes >= 0 && clearReminder)
2844  setReminder(0, false); // clear pre-alarm reminder
2845  mLateCancel = 0;
2846  mAutoClose = false;
2847  mCopyToKOrganizer = false;
2848 }
2849 
2850 bool KAEvent::repeatAtLogin(bool includeArchived) const
2851 {
2852  return d->mRepeatAtLogin || (includeArchived && d->mArchiveRepeatAtLogin);
2853 }
2854 
2855 void KAEvent::setExcludeHolidays(bool ex)
2856 {
2857  d->mExcludeHolidays = ex ? Private::mHolidays : 0;
2858  // Option only affects recurring alarms
2859  d->mTriggerChanged = (d->checkRecur() != KARecurrence::NO_RECUR);
2860 }
2861 
2862 bool KAEvent::holidaysExcluded() const
2863 {
2864  return d->mExcludeHolidays;
2865 }
2866 
2867 /******************************************************************************
2868 * Set a new holiday region.
2869 * Alarms which exclude holidays record the pointer to the holiday definition
2870 * at the time their next trigger times were last calculated. The change in
2871 * holiday definition pointer will cause their next trigger times to be
2872 * recalculated.
2873 */
2874 void KAEvent::setHolidays(const HolidayRegion& h)
2875 {
2876  Private::mHolidays = &h;
2877 }
2878 
2879 void KAEvent::setWorkTimeOnly(bool wto)
2880 {
2881  d->mWorkTimeOnly = wto;
2882  // Option only affects recurring alarms
2883  d->mTriggerChanged = (d->checkRecur() != KARecurrence::NO_RECUR);
2884 }
2885 
2886 bool KAEvent::workTimeOnly() const
2887 {
2888  return d->mWorkTimeOnly;
2889 }
2890 
2891 /******************************************************************************
2892 * Check whether a date/time is during working hours and/or holidays, depending
2893 * on the flags set for the specified event.
2894 */
2895 bool KAEvent::isWorkingTime(const KDateTime& dt) const
2896 {
2897  return d->isWorkingTime(dt);
2898 }
2899 
2900 bool KAEvent::Private::isWorkingTime(const KDateTime& dt) const
2901 {
2902  if ((mWorkTimeOnly && !mWorkDays.testBit(dt.date().dayOfWeek() - 1))
2903  || (mExcludeHolidays && mHolidays && mHolidays->isHoliday(dt.date())))
2904  return false;
2905  if (!mWorkTimeOnly)
2906  return true;
2907  return dt.isDateOnly()
2908  || (dt.time() >= mWorkDayStart && dt.time() < mWorkDayEnd);
2909 }
2910 
2911 /******************************************************************************
2912 * Set new working days and times.
2913 * Increment a counter so that working-time-only alarms can detect that they
2914 * need to update their next trigger time.
2915 */
2916 void KAEvent::setWorkTime(const QBitArray& days, const QTime& start, const QTime& end)
2917 {
2918  if (days != Private::mWorkDays || start != Private::mWorkDayStart || end != Private::mWorkDayEnd)
2919  {
2920  Private::mWorkDays = days;
2921  Private::mWorkDayStart = start;
2922  Private::mWorkDayEnd = end;
2923  if (!++Private::mWorkTimeIndex)
2924  ++Private::mWorkTimeIndex;
2925  }
2926 }
2927 
2928 /******************************************************************************
2929 * Clear the event's recurrence and alarm repetition data.
2930 */
2931 void KAEvent::setNoRecur()
2932 {
2933  d->clearRecur();
2934 }
2935 
2936 void KAEvent::Private::clearRecur()
2937 {
2938  if (mRecurrence || mRepetition)
2939  {
2940  delete mRecurrence;
2941  mRecurrence = 0;
2942  mRepetition.set(0, 0);
2943  mTriggerChanged = true;
2944  }
2945  mNextRepeat = 0;
2946 }
2947 
2948 /******************************************************************************
2949 * Initialise the event's recurrence from a KCal::Recurrence.
2950 * The event's start date/time is not changed.
2951 */
2952 void KAEvent::setRecurrence(const KARecurrence& recurrence)
2953 {
2954  d->setRecurrence(recurrence);
2955 }
2956 
2957 void KAEvent::Private::setRecurrence(const KARecurrence& recurrence)
2958 {
2959  startChanges(); // prevent multiple trigger time evaluation here
2960  delete mRecurrence;
2961  if (recurrence.recurs())
2962  {
2963  mRecurrence = new KARecurrence(recurrence);
2964  mRecurrence->setStartDateTime(mStartDateTime.effectiveKDateTime(), mStartDateTime.isDateOnly());
2965  mTriggerChanged = true;
2966  }
2967  else
2968  {
2969  if (mRecurrence)
2970  mTriggerChanged = true;
2971  mRecurrence = 0;
2972  }
2973 
2974  // Adjust sub-repetition values to fit the recurrence.
2975  setRepetition(mRepetition);
2976 
2977  endChanges();
2978 }
2979 
2980 /******************************************************************************
2981 * Set the recurrence to recur at a minutes interval.
2982 * Parameters:
2983 * freq = how many minutes between recurrences.
2984 * count = number of occurrences, including first and last.
2985 * = -1 to recur indefinitely.
2986 * = 0 to use 'end' instead.
2987 * end = end date/time (invalid to use 'count' instead).
2988 * Reply = false if no recurrence was set up.
2989 */
2990 bool KAEvent::setRecurMinutely(int freq, int count, const KDateTime& end)
2991 {
2992  const bool success = d->setRecur(RecurrenceRule::rMinutely, freq, count, end);
2993  d->mTriggerChanged = true;
2994  return success;
2995 }
2996 
2997 /******************************************************************************
2998 * Set the recurrence to recur daily.
2999 * Parameters:
3000 * freq = how many days between recurrences.
3001 * days = which days of the week alarms are allowed to occur on.
3002 * count = number of occurrences, including first and last.
3003 * = -1 to recur indefinitely.
3004 * = 0 to use 'end' instead.
3005 * end = end date (invalid to use 'count' instead).
3006 * Reply = false if no recurrence was set up.
3007 */
3008 bool KAEvent::setRecurDaily(int freq, const QBitArray& days, int count, const QDate& end)
3009 {
3010  const bool success = d->setRecur(RecurrenceRule::rDaily, freq, count, end);
3011  if (success)
3012  {
3013  int n = 0;
3014  for (int i = 0; i < 7; ++i)
3015  {
3016  if (days.testBit(i))
3017  ++n;
3018  }
3019  if (n < 7)
3020  d->mRecurrence->addWeeklyDays(days);
3021  }
3022  d->mTriggerChanged = true;
3023  return success;
3024 }
3025 
3026 /******************************************************************************
3027 * Set the recurrence to recur weekly, on the specified weekdays.
3028 * Parameters:
3029 * freq = how many weeks between recurrences.
3030 * days = which days of the week alarms should occur on.
3031 * count = number of occurrences, including first and last.
3032 * = -1 to recur indefinitely.
3033 * = 0 to use 'end' instead.
3034 * end = end date (invalid to use 'count' instead).
3035 * Reply = false if no recurrence was set up.
3036 */
3037 bool KAEvent::setRecurWeekly(int freq, const QBitArray& days, int count, const QDate& end)
3038 {
3039  const bool success = d->setRecur(RecurrenceRule::rWeekly, freq, count, end);
3040  if (success)
3041  d->mRecurrence->addWeeklyDays(days);
3042  d->mTriggerChanged = true;
3043  return success;
3044 }
3045 
3046 /******************************************************************************
3047 * Set the recurrence to recur monthly, on the specified days within the month.
3048 * Parameters:
3049 * freq = how many months between recurrences.
3050 * days = which days of the month alarms should occur on.
3051 * count = number of occurrences, including first and last.
3052 * = -1 to recur indefinitely.
3053 * = 0 to use 'end' instead.
3054 * end = end date (invalid to use 'count' instead).
3055 * Reply = false if no recurrence was set up.
3056 */
3057 bool KAEvent::setRecurMonthlyByDate(int freq, const QVector<int>& days, int count, const QDate& end)
3058 {
3059  const bool success = d->setRecur(RecurrenceRule::rMonthly, freq, count, end);
3060  if (success)
3061  {
3062  for (int i = 0, end = days.count(); i < end; ++i)
3063  d->mRecurrence->addMonthlyDate(days[i]);
3064  }
3065  d->mTriggerChanged = true;
3066  return success;
3067 }
3068 
3069 /******************************************************************************
3070 * Set the recurrence to recur monthly, on the specified weekdays in the
3071 * specified weeks of the month.
3072 * Parameters:
3073 * freq = how many months between recurrences.
3074 * posns = which days of the week/weeks of the month alarms should occur on.
3075 * count = number of occurrences, including first and last.
3076 * = -1 to recur indefinitely.
3077 * = 0 to use 'end' instead.
3078 * end = end date (invalid to use 'count' instead).
3079 * Reply = false if no recurrence was set up.
3080 */
3081 bool KAEvent::setRecurMonthlyByPos(int freq, const QVector<MonthPos>& posns, int count, const QDate& end)
3082 {
3083  const bool success = d->setRecur(RecurrenceRule::rMonthly, freq, count, end);
3084  if (success)
3085  {
3086  for (int i = 0, end = posns.count(); i < end; ++i)
3087  d->mRecurrence->addMonthlyPos(posns[i].weeknum, posns[i].days);
3088  }
3089  d->mTriggerChanged = true;
3090  return success;
3091 }
3092 
3093 /******************************************************************************
3094 * Set the recurrence to recur annually, on the specified start date in each
3095 * of the specified months.
3096 * Parameters:
3097 * freq = how many years between recurrences.
3098 * months = which months of the year alarms should occur on.
3099 * day = day of month, or 0 to use start date
3100 * feb29 = when February 29th should recur in non-leap years.
3101 * count = number of occurrences, including first and last.
3102 * = -1 to recur indefinitely.
3103 * = 0 to use 'end' instead.
3104 * end = end date (invalid to use 'count' instead).
3105 * Reply = false if no recurrence was set up.
3106 */
3107 bool KAEvent::setRecurAnnualByDate(int freq, const QVector<int>& months, int day, KARecurrence::Feb29Type feb29, int count, const QDate& end)
3108 {
3109  const bool success = d->setRecur(RecurrenceRule::rYearly, freq, count, end, feb29);
3110  if (success)
3111  {
3112  for (int i = 0, end = months.count(); i < end; ++i)
3113  d->mRecurrence->addYearlyMonth(months[i]);
3114  if (day)
3115  d->mRecurrence->addMonthlyDate(day);
3116  }
3117  d->mTriggerChanged = true;
3118  return success;
3119 }
3120 
3121 /******************************************************************************
3122 * Set the recurrence to recur annually, on the specified weekdays in the
3123 * specified weeks of the specified months.
3124 * Parameters:
3125 * freq = how many years between recurrences.
3126 * posns = which days of the week/weeks of the month alarms should occur on.
3127 * months = which months of the year alarms should occur on.
3128 * count = number of occurrences, including first and last.
3129 * = -1 to recur indefinitely.
3130 * = 0 to use 'end' instead.
3131 * end = end date (invalid to use 'count' instead).
3132 * Reply = false if no recurrence was set up.
3133 */
3134 bool KAEvent::setRecurAnnualByPos(int freq, const QVector<MonthPos>& posns, const QVector<int>& months, int count, const QDate& end)
3135 {
3136  const bool success = d->setRecur(RecurrenceRule::rYearly, freq, count, end);
3137  if (success)
3138  {
3139  int i = 0;
3140  int iend;
3141  for (iend = months.count(); i < iend; ++i)
3142  d->mRecurrence->addYearlyMonth(months[i]);
3143  for (i = 0, iend = posns.count(); i < iend; ++i)
3144  d->mRecurrence->addYearlyPos(posns[i].weeknum, posns[i].days);
3145  }
3146  d->mTriggerChanged = true;
3147  return success;
3148 }
3149 
3150 /******************************************************************************
3151 * Initialise the event's recurrence data.
3152 * Parameters:
3153 * freq = how many intervals between recurrences.
3154 * count = number of occurrences, including first and last.
3155 * = -1 to recur indefinitely.
3156 * = 0 to use 'end' instead.
3157 * end = end date/time (invalid to use 'count' instead).
3158 * Reply = false if no recurrence was set up.
3159 */
3160 bool KAEvent::Private::setRecur(RecurrenceRule::PeriodType recurType, int freq, int count, const QDate& end, KARecurrence::Feb29Type feb29)
3161 {
3162  KDateTime edt = mNextMainDateTime.kDateTime();
3163  edt.setDate(end);
3164  return setRecur(recurType, freq, count, edt, feb29);
3165 }
3166 bool KAEvent::Private::setRecur(RecurrenceRule::PeriodType recurType, int freq, int count, const KDateTime& end, KARecurrence::Feb29Type feb29)
3167 {
3168  if (count >= -1 && (count || end.date().isValid()))
3169  {
3170  if (!mRecurrence)
3171  mRecurrence = new KARecurrence;
3172  if (mRecurrence->init(recurType, freq, count, mNextMainDateTime.kDateTime(), end, feb29))
3173  return true;
3174  }
3175  clearRecur();
3176  return false;
3177 }
3178 
3179 bool KAEvent::recurs() const
3180 {
3181  return d->checkRecur() != KARecurrence::NO_RECUR;
3182 }
3183 
3184 KARecurrence::Type KAEvent::recurType() const
3185 {
3186  return d->checkRecur();
3187 }
3188 
3189 KARecurrence* KAEvent::recurrence() const
3190 {
3191  return d->mRecurrence;
3192 }
3193 
3194 /******************************************************************************
3195 * Return the recurrence interval in units of the recurrence period type.
3196 */
3197 int KAEvent::recurInterval() const
3198 {
3199  if (d->mRecurrence)
3200  {
3201  switch (d->mRecurrence->type())
3202  {
3203  case KARecurrence::MINUTELY:
3204  case KARecurrence::DAILY:
3205  case KARecurrence::WEEKLY:
3206  case KARecurrence::MONTHLY_DAY:
3207  case KARecurrence::MONTHLY_POS:
3208  case KARecurrence::ANNUAL_DATE:
3209  case KARecurrence::ANNUAL_POS:
3210  return d->mRecurrence->frequency();
3211  default:
3212  break;
3213  }
3214  }
3215  return 0;
3216 }
3217 
3218 Duration KAEvent::longestRecurrenceInterval() const
3219 {
3220  return d->mRecurrence ? d->mRecurrence->longestInterval() : Duration(0);
3221 }
3222 
3223 /******************************************************************************
3224 * Adjust the event date/time to the first recurrence of the event, on or after
3225 * start date/time. The event start date may not be a recurrence date, in which
3226 * case a later date will be set.
3227 */
3228 void KAEvent::setFirstRecurrence()
3229 {
3230  d->setFirstRecurrence();
3231 }
3232 
3233 void KAEvent::Private::setFirstRecurrence()
3234 {
3235  switch (checkRecur())
3236  {
3237  case KARecurrence::NO_RECUR:
3238  case KARecurrence::MINUTELY:
3239  return;
3240  case KARecurrence::ANNUAL_DATE:
3241  case KARecurrence::ANNUAL_POS:
3242  if (mRecurrence->yearMonths().isEmpty())
3243  return; // (presumably it's a template)
3244  break;
3245  case KARecurrence::DAILY:
3246  case KARecurrence::WEEKLY:
3247  case KARecurrence::MONTHLY_POS:
3248  case KARecurrence::MONTHLY_DAY:
3249  break;
3250  }
3251  const KDateTime recurStart = mRecurrence->startDateTime();
3252  if (mRecurrence->recursOn(recurStart.date(), recurStart.timeSpec()))
3253  return; // it already recurs on the start date
3254 
3255  // Set the frequency to 1 to find the first possible occurrence
3256  const int frequency = mRecurrence->frequency();
3257  mRecurrence->setFrequency(1);
3258  DateTime next;
3259  nextRecurrence(mNextMainDateTime.effectiveKDateTime(), next);
3260  if (!next.isValid())
3261  mRecurrence->setStartDateTime(recurStart, mStartDateTime.isDateOnly()); // reinstate the old value
3262  else
3263  {
3264  mRecurrence->setStartDateTime(next.effectiveKDateTime(), next.isDateOnly());
3265  mStartDateTime = mNextMainDateTime = next;
3266  mTriggerChanged = true;
3267  }
3268  mRecurrence->setFrequency(frequency); // restore the frequency
3269 }
3270 
3271 /******************************************************************************
3272 * Return the recurrence interval as text suitable for display.
3273 */
3274 QString KAEvent::recurrenceText(bool brief) const
3275 {
3276  if (d->mRepeatAtLogin)
3277  return brief ? i18nc("@info/plain Brief form of 'At Login'", "Login") : i18nc("@info/plain", "At login");
3278  if (d->mRecurrence)
3279  {
3280  const int frequency = d->mRecurrence->frequency();
3281  switch (d->mRecurrence->defaultRRuleConst()->recurrenceType())
3282  {
3283  case RecurrenceRule::rMinutely:
3284  if (frequency < 60)
3285  return i18ncp("@info/plain", "1 Minute", "%1 Minutes", frequency);
3286  else if (frequency % 60 == 0)
3287  return i18ncp("@info/plain", "1 Hour", "%1 Hours", frequency/60);
3288  else
3289  {
3290  QString mins;
3291  return i18nc("@info/plain Hours and minutes", "%1h %2m", frequency/60, mins.sprintf("%02d", frequency%60));
3292  }
3293  case RecurrenceRule::rDaily:
3294  return i18ncp("@info/plain", "1 Day", "%1 Days", frequency);
3295  case RecurrenceRule::rWeekly:
3296  return i18ncp("@info/plain", "1 Week", "%1 Weeks", frequency);
3297  case RecurrenceRule::rMonthly:
3298  return i18ncp("@info/plain", "1 Month", "%1 Months", frequency);
3299  case RecurrenceRule::rYearly:
3300  return i18ncp("@info/plain", "1 Year", "%1 Years", frequency);
3301  case RecurrenceRule::rNone:
3302  default:
3303  break;
3304  }
3305  }
3306  return brief ? QString() : i18nc("@info/plain No recurrence", "None");
3307 }
3308 
3309 /******************************************************************************
3310 * Initialise the event's sub-repetition.
3311 * The repetition length is adjusted if necessary to fit the recurrence interval.
3312 * If the event doesn't recur, the sub-repetition is cleared.
3313 * Reply = false if a non-daily interval was specified for a date-only recurrence.
3314 */
3315 bool KAEvent::setRepetition(const Repetition& r)
3316 {
3317  return d->setRepetition(r);
3318 }
3319 
3320 bool KAEvent::Private::setRepetition(const Repetition& repetition)
3321 {
3322  // Don't set mRepetition to zero at the start of this function, in case the
3323  // 'repetition' parameter passed in is a reference to mRepetition.
3324  mNextRepeat = 0;
3325  if (repetition && !mRepeatAtLogin)
3326  {
3327  Q_ASSERT(checkRecur() != KARecurrence::NO_RECUR);
3328  if (!repetition.isDaily() && mStartDateTime.isDateOnly())
3329  {
3330  mRepetition.set(0, 0);
3331  return false; // interval must be in units of days for date-only alarms
3332  }
3333  Duration longestInterval = mRecurrence->longestInterval();
3334  if (repetition.duration() >= longestInterval)
3335  {
3336  const int count = mStartDateTime.isDateOnly()
3337  ? (longestInterval.asDays() - 1) / repetition.intervalDays()
3338  : (longestInterval.asSeconds() - 1) / repetition.intervalSeconds();
3339  mRepetition.set(repetition.interval(), count);
3340  }
3341  else
3342  mRepetition = repetition;
3343  mTriggerChanged = true;
3344  }
3345  else if (mRepetition)
3346  {
3347  mRepetition.set(0, 0);
3348  mTriggerChanged = true;
3349  }
3350  return true;
3351 }
3352 
3353 Repetition KAEvent::repetition() const
3354 {
3355  return d->mRepetition;
3356 }
3357 
3358 int KAEvent::nextRepetition() const
3359 {
3360  return d->mNextRepeat;
3361 }
3362 
3363 /******************************************************************************
3364 * Return the repetition interval as text suitable for display.
3365 */
3366 QString KAEvent::repetitionText(bool brief) const
3367 {
3368  if (d->mRepetition)
3369  {
3370  if (!d->mRepetition.isDaily())
3371  {
3372  const int minutes = d->mRepetition.intervalMinutes();
3373  if (minutes < 60)
3374  return i18ncp("@info/plain", "1 Minute", "%1 Minutes", minutes);
3375  if (minutes % 60 == 0)
3376  return i18ncp("@info/plain", "1 Hour", "%1 Hours", minutes/60);
3377  QString mins;
3378  return i18nc("@info/plain Hours and minutes", "%1h %2m", minutes/60, mins.sprintf("%02d", minutes%60));
3379  }
3380  const int days = d->mRepetition.intervalDays();
3381  if (days % 7)
3382  return i18ncp("@info/plain", "1 Day", "%1 Days", days);
3383  return i18ncp("@info/plain", "1 Week", "%1 Weeks", days / 7);
3384  }
3385  return brief ? QString() : i18nc("@info/plain No repetition", "None");
3386 }
3387 
3388 /******************************************************************************
3389 * Determine whether the event will occur after the specified date/time.
3390 * If 'includeRepetitions' is true and the alarm has a sub-repetition, it
3391 * returns true if any repetitions occur after the specified date/time.
3392 */
3393 bool KAEvent::occursAfter(const KDateTime& preDateTime, bool includeRepetitions) const
3394 {
3395  return d->occursAfter(preDateTime, includeRepetitions);
3396 }
3397 
3398 bool KAEvent::Private::occursAfter(const KDateTime& preDateTime, bool includeRepetitions) const
3399 {
3400  KDateTime dt;
3401  if (checkRecur() != KARecurrence::NO_RECUR)
3402  {
3403  if (mRecurrence->duration() < 0)
3404  return true; // infinite recurrence
3405  dt = mRecurrence->endDateTime();
3406  }
3407  else
3408  dt = mNextMainDateTime.effectiveKDateTime();
3409  if (mStartDateTime.isDateOnly())
3410  {
3411  QDate pre = preDateTime.date();
3412  if (preDateTime.toTimeSpec(mStartDateTime.timeSpec()).time() < DateTime::startOfDay())
3413  pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come
3414  if (pre < dt.date())
3415  return true;
3416  }
3417  else if (preDateTime < dt)
3418  return true;
3419 
3420  if (includeRepetitions && mRepetition)
3421  {
3422  if (preDateTime < mRepetition.duration().end(dt))
3423  return true;
3424  }
3425  return false;
3426 }
3427 
3428 /******************************************************************************
3429 * Set the date/time of the event to the next scheduled occurrence after the
3430 * specified date/time, provided that this is later than its current date/time.
3431 * Any reminder alarm is adjusted accordingly.
3432 * If the alarm has a sub-repetition, and a repetition of a previous recurrence
3433 * occurs after the specified date/time, that repetition is set as the next
3434 * occurrence.
3435 */
3436 KAEvent::OccurType KAEvent::setNextOccurrence(const KDateTime& preDateTime)
3437 {
3438  return d->setNextOccurrence(preDateTime);
3439 }
3440 
3441 KAEvent::OccurType KAEvent::Private::setNextOccurrence(const KDateTime& preDateTime)
3442 {
3443  if (preDateTime < mNextMainDateTime.effectiveKDateTime())
3444  return FIRST_OR_ONLY_OCCURRENCE; // it might not be the first recurrence - tant pis
3445  KDateTime pre = preDateTime;
3446  // If there are repetitions, adjust the comparison date/time so that
3447  // we find the earliest recurrence which has a repetition falling after
3448  // the specified preDateTime.
3449  if (mRepetition)
3450  pre = mRepetition.duration(-mRepetition.count()).end(preDateTime);
3451 
3452  DateTime afterPre; // next recurrence after 'pre'
3453  OccurType type;
3454  if (pre < mNextMainDateTime.effectiveKDateTime())
3455  {
3456  afterPre = mNextMainDateTime;
3457  type = FIRST_OR_ONLY_OCCURRENCE; // may not actually be the first occurrence
3458  }
3459  else if (checkRecur() != KARecurrence::NO_RECUR)
3460  {
3461  type = nextRecurrence(pre, afterPre);
3462  if (type == NO_OCCURRENCE)
3463  return NO_OCCURRENCE;
3464  if (type != FIRST_OR_ONLY_OCCURRENCE && afterPre != mNextMainDateTime)
3465  {
3466  // Need to reschedule the next trigger date/time
3467  mNextMainDateTime = afterPre;
3468  if (mReminderMinutes > 0 && (mDeferral == REMINDER_DEFERRAL || mReminderActive != ACTIVE_REMINDER))
3469  {
3470  // Reinstate the advance reminder for the rescheduled recurrence.
3471  // Note that a reminder AFTER the main alarm will be left active.
3472  activate_reminder(!mReminderOnceOnly);
3473  }
3474  if (mDeferral == REMINDER_DEFERRAL)
3475  set_deferral(NO_DEFERRAL);
3476  mTriggerChanged = true;
3477  }
3478  }
3479  else
3480  return NO_OCCURRENCE;
3481 
3482  if (mRepetition)
3483  {
3484  if (afterPre <= preDateTime)
3485  {
3486  // The next occurrence is a sub-repetition.
3487  type = static_cast<OccurType>(type | OCCURRENCE_REPEAT);
3488  mNextRepeat = mRepetition.nextRepeatCount(afterPre.effectiveKDateTime(), preDateTime);
3489  // Repetitions can't have a reminder, so remove any.
3490  activate_reminder(false);
3491  if (mDeferral == REMINDER_DEFERRAL)
3492  set_deferral(NO_DEFERRAL);
3493  mTriggerChanged = true;
3494  }
3495  else if (mNextRepeat)
3496  {
3497  // The next occurrence is the main occurrence, not a repetition
3498  mNextRepeat = 0;
3499  mTriggerChanged = true;
3500  }
3501  }
3502  return type;
3503 }
3504 
3505 /******************************************************************************
3506 * Get the date/time of the next occurrence of the event, after the specified
3507 * date/time.
3508 * 'result' = date/time of next occurrence, or invalid date/time if none.
3509 */
3510 KAEvent::OccurType KAEvent::nextOccurrence(const KDateTime& preDateTime, DateTime& result, OccurOption o) const
3511 {
3512  return d->nextOccurrence(preDateTime, result, o);
3513 }
3514 
3515 KAEvent::OccurType KAEvent::Private::nextOccurrence(const KDateTime& preDateTime, DateTime& result,
3516  OccurOption includeRepetitions) const
3517 {
3518  KDateTime pre = preDateTime;
3519  if (includeRepetitions != IGNORE_REPETITION)
3520  { // RETURN_REPETITION or ALLOW_FOR_REPETITION
3521  if (!mRepetition)
3522  includeRepetitions = IGNORE_REPETITION;
3523  else
3524  pre = mRepetition.duration(-mRepetition.count()).end(preDateTime);
3525  }
3526 
3527  OccurType type;
3528  const bool recurs = (checkRecur() != KARecurrence::NO_RECUR);
3529  if (recurs)
3530  type = nextRecurrence(pre, result);
3531  else if (pre < mNextMainDateTime.effectiveKDateTime())
3532  {
3533  result = mNextMainDateTime;
3534  type = FIRST_OR_ONLY_OCCURRENCE;
3535  }
3536  else
3537  {
3538  result = DateTime();
3539  type = NO_OCCURRENCE;
3540  }
3541 
3542  if (type != NO_OCCURRENCE && result <= preDateTime && includeRepetitions != IGNORE_REPETITION)
3543  { // RETURN_REPETITION or ALLOW_FOR_REPETITION
3544  // The next occurrence is a sub-repetition
3545  int repetition = mRepetition.nextRepeatCount(result.kDateTime(), preDateTime);
3546  const DateTime repeatDT = mRepetition.duration(repetition).end(result.kDateTime());
3547  if (recurs)
3548  {
3549  // We've found a recurrence before the specified date/time, which has
3550  // a sub-repetition after the date/time.
3551  // However, if the intervals between recurrences vary, we could possibly
3552  // have missed a later recurrence which fits the criterion, so check again.
3553  DateTime dt;
3554  const OccurType newType = previousOccurrence(repeatDT.effectiveKDateTime(), dt, false);
3555  if (dt > result)
3556  {
3557  type = newType;
3558  result = dt;
3559  if (includeRepetitions == RETURN_REPETITION && result <= preDateTime)
3560  {
3561  // The next occurrence is a sub-repetition
3562  repetition = mRepetition.nextRepeatCount(result.kDateTime(), preDateTime);
3563  result = mRepetition.duration(repetition).end(result.kDateTime());
3564  type = static_cast<OccurType>(type | OCCURRENCE_REPEAT);
3565  }
3566  return type;
3567  }
3568  }
3569  if (includeRepetitions == RETURN_REPETITION)
3570  {
3571  // The next occurrence is a sub-repetition
3572  result = repeatDT;
3573  type = static_cast<OccurType>(type | OCCURRENCE_REPEAT);
3574  }
3575  }
3576  return type;
3577 }
3578 
3579 /******************************************************************************
3580 * Get the date/time of the last previous occurrence of the event, before the
3581 * specified date/time.
3582 * If 'includeRepetitions' is true and the alarm has a sub-repetition, the
3583 * last previous repetition is returned if appropriate.
3584 * 'result' = date/time of previous occurrence, or invalid date/time if none.
3585 */
3586 KAEvent::OccurType KAEvent::previousOccurrence(const KDateTime& afterDateTime, DateTime& result, bool includeRepetitions) const
3587 {
3588  return d->previousOccurrence(afterDateTime, result, includeRepetitions);
3589 }
3590 
3591 KAEvent::OccurType KAEvent::Private::previousOccurrence(const KDateTime& afterDateTime, DateTime& result,
3592  bool includeRepetitions) const
3593 {
3594  Q_ASSERT(!afterDateTime.isDateOnly());
3595  if (mStartDateTime >= afterDateTime)
3596  {
3597  result = KDateTime();
3598  return NO_OCCURRENCE; // the event starts after the specified date/time
3599  }
3600 
3601  // Find the latest recurrence of the event
3602  OccurType type;
3603  if (checkRecur() == KARecurrence::NO_RECUR)
3604  {
3605  result = mStartDateTime;
3606  type = FIRST_OR_ONLY_OCCURRENCE;
3607  }
3608  else
3609  {
3610  const KDateTime recurStart = mRecurrence->startDateTime();
3611  KDateTime after = afterDateTime.toTimeSpec(mStartDateTime.timeSpec());
3612  if (mStartDateTime.isDateOnly() && afterDateTime.time() > DateTime::startOfDay())
3613  after = after.addDays(1); // today's recurrence (if today recurs) has passed
3614  const KDateTime dt = mRecurrence->getPreviousDateTime(after);
3615  result = dt;
3616  result.setDateOnly(mStartDateTime.isDateOnly());
3617  if (!dt.isValid())
3618  return NO_OCCURRENCE;
3619  if (dt == recurStart)
3620  type = FIRST_OR_ONLY_OCCURRENCE;
3621  else if (mRecurrence->getNextDateTime(dt).isValid())
3622  type = result.isDateOnly() ? RECURRENCE_DATE : RECURRENCE_DATE_TIME;
3623  else
3624  type = LAST_RECURRENCE;
3625  }
3626 
3627  if (includeRepetitions && mRepetition)
3628  {
3629  // Find the latest repetition which is before the specified time.
3630  const int repetition = mRepetition.previousRepeatCount(result.effectiveKDateTime(), afterDateTime);
3631  if (repetition > 0)
3632  {
3633  result = mRepetition.duration(qMin(repetition, mRepetition.count())).end(result.kDateTime());
3634  return static_cast<OccurType>(type | OCCURRENCE_REPEAT);
3635  }
3636  }
3637  return type;
3638 }
3639 
3640 /******************************************************************************
3641 * Set the event to be a copy of the specified event, making the specified
3642 * alarm the 'displaying' alarm.
3643 * The purpose of setting up a 'displaying' alarm is to be able to reinstate
3644 * the alarm message in case of a crash, or to reinstate it should the user
3645 * choose to defer the alarm. Note that even repeat-at-login alarms need to be
3646 * saved in case their end time expires before the next login.
3647 * Reply = true if successful, false if alarm was not copied.
3648 */
3649 #ifndef KALARMCAL_USE_KRESOURCES
3650 bool KAEvent::setDisplaying(const KAEvent& e, KAAlarm::Type t, Akonadi::Collection::Id id, const KDateTime& dt, bool showEdit, bool showDefer)
3651 #else
3652 bool KAEvent::setDisplaying(const KAEvent& e, KAAlarm::Type t, const QString& id, const KDateTime& dt, bool showEdit, bool showDefer)
3653 #endif
3654 {
3655  return d->setDisplaying(*e.d, t, id, dt, showEdit, showDefer);
3656 }
3657 
3658 #ifndef KALARMCAL_USE_KRESOURCES
3659 bool KAEvent::Private::setDisplaying(const KAEvent::Private& event, KAAlarm::Type alarmType, Akonadi::Collection::Id collectionId,
3660  const KDateTime& repeatAtLoginTime, bool showEdit, bool showDefer)
3661 #else
3662 bool KAEvent::Private::setDisplaying(const KAEvent::Private& event, KAAlarm::Type alarmType, const QString& resourceID,
3663  const KDateTime& repeatAtLoginTime, bool showEdit, bool showDefer)
3664 #endif
3665 {
3666  if (!mDisplaying
3667  && (alarmType == KAAlarm::MAIN_ALARM
3668  || alarmType == KAAlarm::REMINDER_ALARM
3669  || alarmType == KAAlarm::DEFERRED_REMINDER_ALARM
3670  || alarmType == KAAlarm::DEFERRED_ALARM
3671  || alarmType == KAAlarm::AT_LOGIN_ALARM))
3672  {
3673 //kDebug()<<event.id()<<","<<(alarmType==KAAlarm::MAIN_ALARM?"MAIN":alarmType==KAAlarm::REMINDER_ALARM?"REMINDER":alarmType==KAAlarm::DEFERRED_REMINDER_ALARM?"REMINDER_DEFERRAL":alarmType==KAAlarm::DEFERRED_ALARM?"DEFERRAL":"LOGIN")<<"): time="<<repeatAtLoginTime.toString();
3674  KAAlarm al = event.alarm(alarmType);
3675  if (al.isValid())
3676  {
3677  *this = event;
3678  // Change the event ID to avoid duplicating the same unique ID as the original event
3679  setCategory(CalEvent::DISPLAYING);
3680 #ifndef KALARMCAL_USE_KRESOURCES
3681  mItemId = -1; // the display event doesn't have an associated Item
3682  mCollectionId = collectionId; // original collection ID which contained the event
3683 #else
3684  mOriginalResourceId = resourceID;
3685 #endif
3686  mDisplayingDefer = showDefer;
3687  mDisplayingEdit = showEdit;
3688  mDisplaying = true;
3689  mDisplayingTime = (alarmType == KAAlarm::AT_LOGIN_ALARM) ? repeatAtLoginTime : al.dateTime().kDateTime();
3690  switch (al.type())
3691  {
3692  case KAAlarm::AT_LOGIN_ALARM: mDisplayingFlags = REPEAT_AT_LOGIN; break;
3693  case KAAlarm::REMINDER_ALARM: mDisplayingFlags = REMINDER; break;
3694  case KAAlarm::DEFERRED_REMINDER_ALARM: mDisplayingFlags = al.timedDeferral() ? (REMINDER | TIME_DEFERRAL) : (REMINDER | DATE_DEFERRAL); break;
3695  case KAAlarm::DEFERRED_ALARM: mDisplayingFlags = al.timedDeferral() ? TIME_DEFERRAL : DATE_DEFERRAL; break;
3696  default: mDisplayingFlags = 0; break;
3697  }
3698  ++mAlarmCount;
3699  return true;
3700  }
3701  }
3702  return false;
3703 }
3704 
3705 /******************************************************************************
3706 * Reinstate the original event from the 'displaying' event.
3707 */
3708 #ifndef KALARMCAL_USE_KRESOURCES
3709 void KAEvent::reinstateFromDisplaying(const KCalCore::Event::Ptr& e, Akonadi::Collection::Id& id, bool& showEdit, bool& showDefer)
3710 #else
3711 void KAEvent::reinstateFromDisplaying(const KCal::Event* e, QString& id, bool& showEdit, bool& showDefer)
3712 #endif
3713 {
3714  d->reinstateFromDisplaying(e, id, showEdit, showDefer);
3715 }
3716 
3717 #ifndef KALARMCAL_USE_KRESOURCES
3718 void KAEvent::Private::reinstateFromDisplaying(const Event::Ptr& kcalEvent, Akonadi::Collection::Id& collectionId, bool& showEdit, bool& showDefer)
3719 #else
3720 void KAEvent::Private::reinstateFromDisplaying(const Event* kcalEvent, QString& resourceID, bool& showEdit, bool& showDefer)
3721 #endif
3722 {
3723  set(kcalEvent);
3724  if (mDisplaying)
3725  {
3726  // Retrieve the original event's unique ID
3727  setCategory(CalEvent::ACTIVE);
3728 #ifndef KALARMCAL_USE_KRESOURCES
3729  collectionId = mCollectionId;
3730  mCollectionId = -1;
3731 #else
3732  resourceID = mOriginalResourceId;
3733  mOriginalResourceId.clear();
3734 #endif
3735  showDefer = mDisplayingDefer;
3736  showEdit = mDisplayingEdit;
3737  mDisplaying = false;
3738  --mAlarmCount;
3739  }
3740 }
3741 
3742 /******************************************************************************
3743 * Return the original alarm which the displaying alarm refers to.
3744 * Note that the caller is responsible for ensuring that the event was a
3745 * displaying event, since this is normally called after
3746 * reinstateFromDisplaying(), which clears mDisplaying.
3747 */
3748 KAAlarm KAEvent::convertDisplayingAlarm() const
3749 {
3750  KAAlarm al = alarm(KAAlarm::DISPLAYING_ALARM);
3751  KAAlarm::Private* const al_d = al.d;
3752  const int displayingFlags = d->mDisplayingFlags;
3753  if (displayingFlags & REPEAT_AT_LOGIN)
3754  {
3755  al_d->mRepeatAtLogin = true;
3756  al_d->mType = KAAlarm::AT_LOGIN_ALARM;
3757  }
3758  else if (displayingFlags & Private::DEFERRAL)
3759  {
3760  al_d->mDeferred = true;
3761  al_d->mTimedDeferral = (displayingFlags & Private::TIMED_FLAG);
3762  al_d->mType = (displayingFlags & Private::REMINDER) ? KAAlarm::DEFERRED_REMINDER_ALARM : KAAlarm::DEFERRED_ALARM;
3763  }
3764  else if (displayingFlags & Private::REMINDER)
3765  al_d->mType = KAAlarm::REMINDER_ALARM;
3766  else
3767  al_d->mType = KAAlarm::MAIN_ALARM;
3768  return al;
3769 }
3770 
3771 bool KAEvent::displaying() const
3772 {
3773  return d->mDisplaying;
3774 }
3775 
3776 /******************************************************************************
3777 * Return the alarm of the specified type.
3778 */
3779 KAAlarm KAEvent::alarm(KAAlarm::Type t) const
3780 {
3781  return d->alarm(t);
3782 }
3783 
3784 KAAlarm KAEvent::Private::alarm(KAAlarm::Type type) const
3785 {
3786  checkRecur(); // ensure recurrence/repetition data is consistent
3787  KAAlarm al; // this sets type to INVALID_ALARM
3788  KAAlarm::Private* const al_d = al.d;
3789  if (mAlarmCount)
3790  {
3791  al_d->mActionType = (KAAlarm::Action)mActionSubType;
3792  al_d->mRepeatAtLogin = false;
3793  al_d->mDeferred = false;
3794  switch (type)
3795  {
3796  case KAAlarm::MAIN_ALARM:
3797  if (!mMainExpired)
3798  {
3799  al_d->mType = KAAlarm::MAIN_ALARM;
3800  al_d->mNextMainDateTime = mNextMainDateTime;
3801  al_d->mRepetition = mRepetition;
3802  al_d->mNextRepeat = mNextRepeat;
3803  }
3804  break;
3805  case KAAlarm::REMINDER_ALARM:
3806  if (mReminderActive == ACTIVE_REMINDER)
3807  {
3808  al_d->mType = KAAlarm::REMINDER_ALARM;
3809  if (mReminderMinutes < 0)
3810  al_d->mNextMainDateTime = mReminderAfterTime;
3811  else if (mReminderOnceOnly)
3812  al_d->mNextMainDateTime = mStartDateTime.addMins(-mReminderMinutes);
3813  else
3814  al_d->mNextMainDateTime = mNextMainDateTime.addMins(-mReminderMinutes);
3815  }
3816  break;
3817  case KAAlarm::DEFERRED_REMINDER_ALARM:
3818  if (mDeferral != REMINDER_DEFERRAL)
3819  break;
3820  // fall through to DEFERRED_ALARM
3821  case KAAlarm::DEFERRED_ALARM:
3822  if (mDeferral != NO_DEFERRAL)
3823  {
3824  al_d->mType = (mDeferral == REMINDER_DEFERRAL) ? KAAlarm::DEFERRED_REMINDER_ALARM : KAAlarm::DEFERRED_ALARM;
3825  al_d->mNextMainDateTime = mDeferralTime;
3826  al_d->mDeferred = true;
3827  al_d->mTimedDeferral = !mDeferralTime.isDateOnly();
3828  }
3829  break;
3830  case KAAlarm::AT_LOGIN_ALARM:
3831  if (mRepeatAtLogin)
3832  {
3833  al_d->mType = KAAlarm::AT_LOGIN_ALARM;
3834  al_d->mNextMainDateTime = mAtLoginDateTime;
3835  al_d->mRepeatAtLogin = true;
3836  }
3837  break;
3838  case KAAlarm::DISPLAYING_ALARM:
3839  if (mDisplaying)
3840  {
3841  al_d->mType = KAAlarm::DISPLAYING_ALARM;
3842  al_d->mNextMainDateTime = mDisplayingTime;
3843  }
3844  break;
3845  case KAAlarm::INVALID_ALARM:
3846  default:
3847  break;
3848  }
3849  }
3850  return al;
3851 }
3852 
3853 /******************************************************************************
3854 * Return the main alarm for the event.
3855 * If the main alarm does not exist, one of the subsidiary ones is returned if
3856 * possible.
3857 * N.B. a repeat-at-login alarm can only be returned if it has been read from/
3858 * written to the calendar file.
3859 */
3860 KAAlarm KAEvent::firstAlarm() const
3861 {
3862  return d->firstAlarm();
3863 }
3864 
3865 KAAlarm KAEvent::Private::firstAlarm() const
3866 {
3867  if (mAlarmCount)
3868  {
3869  if (!mMainExpired)
3870  return alarm(KAAlarm::MAIN_ALARM);
3871  return nextAlarm(KAAlarm::MAIN_ALARM);
3872  }
3873  return KAAlarm();
3874 }
3875 
3876 /******************************************************************************
3877 * Return the next alarm for the event, after the specified alarm.
3878 * N.B. a repeat-at-login alarm can only be returned if it has been read from/
3879 * written to the calendar file.
3880 */
3881 KAAlarm KAEvent::nextAlarm(const KAAlarm& previousAlarm) const
3882 {
3883  return d->nextAlarm(previousAlarm.type());
3884 }
3885 
3886 KAAlarm KAEvent::nextAlarm(KAAlarm::Type previousType) const
3887 {
3888  return d->nextAlarm(previousType);
3889 }
3890 
3891 KAAlarm KAEvent::Private::nextAlarm(KAAlarm::Type previousType) const
3892 {
3893  switch (previousType)
3894  {
3895  case KAAlarm::MAIN_ALARM:
3896  if (mReminderActive == ACTIVE_REMINDER)
3897  return alarm(KAAlarm::REMINDER_ALARM);
3898  // fall through to REMINDER_ALARM
3899  case KAAlarm::REMINDER_ALARM:
3900  // There can only be one deferral alarm
3901  if (mDeferral == REMINDER_DEFERRAL)
3902  return alarm(KAAlarm::DEFERRED_REMINDER_ALARM);
3903  if (mDeferral == NORMAL_DEFERRAL)
3904  return alarm(KAAlarm::DEFERRED_ALARM);
3905  // fall through to DEFERRED_ALARM
3906  case KAAlarm::DEFERRED_REMINDER_ALARM:
3907  case KAAlarm::DEFERRED_ALARM:
3908  if (mRepeatAtLogin)
3909  return alarm(KAAlarm::AT_LOGIN_ALARM);
3910  // fall through to AT_LOGIN_ALARM
3911  case KAAlarm::AT_LOGIN_ALARM:
3912  if (mDisplaying)
3913  return alarm(KAAlarm::DISPLAYING_ALARM);
3914  // fall through to DISPLAYING_ALARM
3915  case KAAlarm::DISPLAYING_ALARM:
3916  // fall through to default
3917  case KAAlarm::INVALID_ALARM:
3918  default:
3919  break;
3920  }
3921  return KAAlarm();
3922 }
3923 
3924 int KAEvent::alarmCount() const
3925 {
3926  return d->mAlarmCount;
3927 }
3928 
3929 /******************************************************************************
3930 * Remove the alarm of the specified type from the event.
3931 * This must only be called to remove an alarm which has expired, not to
3932 * reconfigure the event.
3933 */
3934 void KAEvent::removeExpiredAlarm(KAAlarm::Type type)
3935 {
3936  d->removeExpiredAlarm(type);
3937 }
3938 
3939 void KAEvent::Private::removeExpiredAlarm(KAAlarm::Type type)
3940 {
3941  const int count = mAlarmCount;
3942  switch (type)
3943  {
3944  case KAAlarm::MAIN_ALARM:
3945  if (!mReminderActive || mReminderMinutes > 0)
3946  {
3947  mAlarmCount = 0; // removing main alarm - also remove subsidiary alarms
3948  break;
3949  }
3950  // There is a reminder after the main alarm - retain the
3951  // reminder and remove other subsidiary alarms.
3952  mMainExpired = true; // mark the alarm as expired now
3953  --mAlarmCount;
3954  set_deferral(NO_DEFERRAL);
3955  if (mDisplaying)
3956  {
3957  mDisplaying = false;
3958  --mAlarmCount;
3959  }
3960  // fall through to AT_LOGIN_ALARM
3961  case KAAlarm::AT_LOGIN_ALARM:
3962  if (mRepeatAtLogin)
3963  {
3964  // Remove the at-login alarm, but keep a note of it for archiving purposes
3965  mArchiveRepeatAtLogin = true;
3966  mRepeatAtLogin = false;
3967  --mAlarmCount;
3968  }
3969  break;
3970  case KAAlarm::REMINDER_ALARM:
3971  // Remove any reminder alarm, but keep a note of it for archiving purposes
3972  // and for restoration after the next recurrence.
3973  activate_reminder(false);
3974  break;
3975  case KAAlarm::DEFERRED_REMINDER_ALARM:
3976  case KAAlarm::DEFERRED_ALARM:
3977  set_deferral(NO_DEFERRAL);
3978  break;
3979  case KAAlarm::DISPLAYING_ALARM:
3980  if (mDisplaying)
3981  {
3982  mDisplaying = false;
3983  --mAlarmCount;
3984  }
3985  break;
3986  case KAAlarm::INVALID_ALARM:
3987  default:
3988  break;
3989  }
3990  if (mAlarmCount != count)
3991  mTriggerChanged = true;
3992 }
3993 
3994 void KAEvent::startChanges()
3995 {
3996  d->startChanges();
3997 }
3998 
3999 /******************************************************************************
4000 * Indicate that changes to the instance are complete.
4001 * This allows trigger times to be recalculated if any changes have occurred.
4002 */
4003 void KAEvent::endChanges()
4004 {
4005  d->endChanges();
4006 }
4007 
4008 void KAEvent::Private::endChanges()
4009 {
4010  if (mChangeCount > 0)
4011  --mChangeCount;
4012 }
4013 
4014 #ifndef KALARMCAL_USE_KRESOURCES
4015 /******************************************************************************
4016 * Return a list of pointers to KAEvent objects.
4017 */
4018 KAEvent::List KAEvent::ptrList(QVector<KAEvent>& objList)
4019 {
4020  KAEvent::List ptrs;
4021  for (int i = 0, count = objList.count(); i < count; ++i)
4022  ptrs += &objList[i];
4023  return ptrs;
4024 }
4025 #endif
4026 
4027 void KAEvent::dumpDebug() const
4028 {
4029 #ifndef KDE_NO_DEBUG_OUTPUT
4030  d->dumpDebug();
4031 #endif
4032 }
4033 
4034 #ifndef KDE_NO_DEBUG_OUTPUT
4035 void KAEvent::Private::dumpDebug() const
4036 {
4037  kDebug() << "KAEvent dump:";
4038 #ifdef KALARMCAL_USE_KRESOURCES
4039  if (mResource) { kDebug() << "-- mResource:" << (void*)mResource; }
4040 #endif
4041  kDebug() << "-- mEventID:" << mEventID;
4042  kDebug() << "-- mActionSubType:" << (mActionSubType == MESSAGE ? "MESSAGE" : mActionSubType == FILE ? "FILE" : mActionSubType == COMMAND ? "COMMAND" : mActionSubType == EMAIL ? "EMAIL" : mActionSubType == AUDIO ? "AUDIO" : "??");
4043  kDebug() << "-- mNextMainDateTime:" << mNextMainDateTime.toString();
4044  kDebug() << "-- mCommandError:" << mCommandError;
4045  kDebug() << "-- mAllTrigger:" << mAllTrigger.toString();
4046  kDebug() << "-- mMainTrigger:" << mMainTrigger.toString();
4047  kDebug() << "-- mAllWorkTrigger:" << mAllWorkTrigger.toString();
4048  kDebug() << "-- mMainWorkTrigger:" << mMainWorkTrigger.toString();
4049  kDebug() << "-- mCategory:" << mCategory;
4050  if (!mTemplateName.isEmpty())
4051  {
4052  kDebug() << "-- mTemplateName:" << mTemplateName;
4053  kDebug() << "-- mTemplateAfterTime:" << mTemplateAfterTime;
4054  }
4055  kDebug() << "-- mText:" << mText;
4056  if (mActionSubType == MESSAGE || mActionSubType == FILE)
4057  {
4058  kDebug() << "-- mBgColour:" << mBgColour.name();
4059  kDebug() << "-- mFgColour:" << mFgColour.name();
4060  kDebug() << "-- mUseDefaultFont:" << mUseDefaultFont;
4061  if (!mUseDefaultFont)
4062  kDebug() << "-- mFont:" << mFont.toString();
4063  kDebug() << "-- mSpeak:" << mSpeak;
4064  kDebug() << "-- mAudioFile:" << mAudioFile;
4065  kDebug() << "-- mPreAction:" << mPreAction;
4066  kDebug() << "-- mExecPreActOnDeferral:" << (mExtraActionOptions & ExecPreActOnDeferral);
4067  kDebug() << "-- mCancelOnPreActErr:" << (mExtraActionOptions & CancelOnPreActError);
4068  kDebug() << "-- mDontShowPreActErr:" << (mExtraActionOptions & DontShowPreActError);
4069  kDebug() << "-- mPostAction:" << mPostAction;
4070  kDebug() << "-- mLateCancel:" << mLateCancel;
4071  kDebug() << "-- mAutoClose:" << mAutoClose;
4072  }
4073  else if (mActionSubType == COMMAND)
4074  {
4075  kDebug() << "-- mCommandScript:" << mCommandScript;
4076  kDebug() << "-- mCommandXterm:" << mCommandXterm;
4077  kDebug() << "-- mCommandDisplay:" << mCommandDisplay;
4078  kDebug() << "-- mLogFile:" << mLogFile;
4079  }
4080  else if (mActionSubType == EMAIL)
4081  {
4082  kDebug() << "-- mEmail: FromKMail:" << mEmailFromIdentity;
4083  kDebug() << "-- Addresses:" << mEmailAddresses.join(",");
4084  kDebug() << "-- Subject:" << mEmailSubject;
4085  kDebug() << "-- Attachments:" << mEmailAttachments.join(",");
4086  kDebug() << "-- Bcc:" << mEmailBcc;
4087  }
4088  else if (mActionSubType == AUDIO)
4089  kDebug() << "-- mAudioFile:" << mAudioFile;
4090  kDebug() << "-- mBeep:" << mBeep;
4091  if (mActionSubType == AUDIO || !mAudioFile.isEmpty())
4092  {
4093  if (mSoundVolume >= 0)
4094  {
4095  kDebug() << "-- mSoundVolume:" << mSoundVolume;
4096  if (mFadeVolume >= 0)
4097  {
4098  kDebug() << "-- mFadeVolume:" << mFadeVolume;
4099  kDebug() << "-- mFadeSeconds:" << mFadeSeconds;
4100  }
4101  else
4102  kDebug() << "-- mFadeVolume:-:";
4103  }
4104  else
4105  kDebug() << "-- mSoundVolume:-:";
4106  kDebug() << "-- mRepeatSoundPause:" << mRepeatSoundPause;
4107  }
4108  kDebug() << "-- mKMailSerialNumber:" << mKMailSerialNumber;
4109  kDebug() << "-- mCopyToKOrganizer:" << mCopyToKOrganizer;
4110  kDebug() << "-- mExcludeHolidays:" << (bool)mExcludeHolidays;
4111  kDebug() << "-- mWorkTimeOnly:" << mWorkTimeOnly;
4112  kDebug() << "-- mStartDateTime:" << mStartDateTime.toString();
4113  kDebug() << "-- mCreatedDateTime:" << mCreatedDateTime;
4114  kDebug() << "-- mRepeatAtLogin:" << mRepeatAtLogin;
4115  if (mRepeatAtLogin)
4116  kDebug() << "-- mAtLoginDateTime:" << mAtLoginDateTime;
4117  kDebug() << "-- mArchiveRepeatAtLogin:" << mArchiveRepeatAtLogin;
4118  kDebug() << "-- mConfirmAck:" << mConfirmAck;
4119  kDebug() << "-- mEnabled:" << mEnabled;
4120 #ifndef KALARMCAL_USE_KRESOURCES
4121  kDebug() << "-- mItemId:" << mItemId;
4122  kDebug() << "-- mCollectionId:" << mCollectionId;
4123  kDebug() << "-- mCompatibility:" << mCompatibility;
4124  kDebug() << "-- mReadOnly:" << mReadOnly;
4125 #endif
4126  if (mReminderMinutes)
4127  {
4128  kDebug() << "-- mReminderMinutes:" << mReminderMinutes;
4129  kDebug() << "-- mReminderActive:" << (mReminderActive == ACTIVE_REMINDER ? "active" : mReminderActive == HIDDEN_REMINDER ? "hidden" : "no");
4130  kDebug() << "-- mReminderOnceOnly:" << mReminderOnceOnly;
4131  }
4132  else if (mDeferral > 0)
4133  {
4134  kDebug() << "-- mDeferral:" << (mDeferral == NORMAL_DEFERRAL ? "normal" : "reminder");
4135  kDebug() << "-- mDeferralTime:" << mDeferralTime.toString();
4136  }
4137  kDebug() << "-- mDeferDefaultMinutes:" << mDeferDefaultMinutes;
4138  if (mDeferDefaultMinutes)
4139  kDebug() << "-- mDeferDefaultDateOnly:" << mDeferDefaultDateOnly;
4140  if (mDisplaying)
4141  {
4142  kDebug() << "-- mDisplayingTime:" << mDisplayingTime.toString();
4143  kDebug() << "-- mDisplayingFlags:" << mDisplayingFlags;
4144  kDebug() << "-- mDisplayingDefer:" << mDisplayingDefer;
4145  kDebug() << "-- mDisplayingEdit:" << mDisplayingEdit;
4146  }
4147  kDebug() << "-- mRevision:" << mRevision;
4148  kDebug() << "-- mRecurrence:" << mRecurrence;
4149  if (!mRepetition)
4150  kDebug() << "-- mRepetition: 0";
4151  else if (mRepetition.isDaily())
4152  kDebug() << "-- mRepetition: count:" << mRepetition.count() << ", interval:" << mRepetition.intervalDays() << "days";
4153  else
4154  kDebug() << "-- mRepetition: count:" << mRepetition.count() << ", interval:" << mRepetition.intervalMinutes() << "minutes";
4155  kDebug() << "-- mNextRepeat:" << mNextRepeat;
4156  kDebug() << "-- mAlarmCount:" << mAlarmCount;
4157  kDebug() << "-- mMainExpired:" << mMainExpired;
4158  kDebug() << "-- mDisplaying:" << mDisplaying;
4159  kDebug() << "KAEvent dump end";
4160 }
4161 #endif
4162 
4163 
4164 /******************************************************************************
4165 * Fetch the start and next date/time for a KCal::Event.
4166 * Reply = next main date/time.
4167 */
4168 #ifndef KALARMCAL_USE_KRESOURCES
4169 DateTime KAEvent::Private::readDateTime(const Event::Ptr& event, bool dateOnly, DateTime& start)
4170 #else
4171 DateTime KAEvent::Private::readDateTime(const Event* event, bool dateOnly, DateTime& start)
4172 #endif
4173 {
4174  start = event->dtStart();
4175  if (dateOnly)
4176  {
4177  // A date-only event is indicated by the X-KDE-KALARM-FLAGS:DATE property, not
4178  // by a date-only start date/time (for the reasons given in updateKCalEvent()).
4179  start.setDateOnly(true);
4180  }
4181  DateTime next = start;
4182  QString prop = event->customProperty(KACalendar::APPNAME, Private::NEXT_RECUR_PROPERTY);
4183  if (prop.length() >= 8)
4184  {
4185  // The next due recurrence time is specified
4186  const QDate d(prop.left(4).toInt(), prop.mid(4,2).toInt(), prop.mid(6,2).toInt());
4187  if (d.isValid())
4188  {
4189  if (dateOnly && prop.length() == 8)
4190  next.setDate(d);
4191  else if (!dateOnly && prop.length() == 15 && prop[8] == QChar('T'))
4192  {
4193  const QTime t(prop.mid(9,2).toInt(), prop.mid(11,2).toInt(), prop.mid(13,2).toInt());
4194  if (t.isValid())
4195  {
4196  next.setDate(d);
4197  next.setTime(t);
4198  }
4199  }
4200  if (next < start)
4201  next = start; // ensure next recurrence time is valid
4202  }
4203  }
4204  return next;
4205 }
4206 
4207 /******************************************************************************
4208 * Parse the alarms for a KCal::Event.
4209 * Reply = map of alarm data, indexed by KAAlarm::Type
4210 */
4211 #ifndef KALARMCAL_USE_KRESOURCES
4212 void KAEvent::Private::readAlarms(const Event::Ptr& event, void* almap, bool cmdDisplay)
4213 #else
4214 void KAEvent::Private::readAlarms(const Event* event, void* almap, bool cmdDisplay)
4215 #endif
4216 {
4217  AlarmMap* alarmMap = (AlarmMap*)almap;
4218  Alarm::List alarms = event->alarms();
4219 
4220  // Check if it's an audio event with no display alarm
4221  bool audioOnly = false;
4222  for (int i = 0, end = alarms.count(); i < end; ++i)
4223  {
4224  switch (alarms[i]->type())
4225  {
4226  case Alarm::Display:
4227  case Alarm::Procedure:
4228  audioOnly = false;
4229  i = end; // exit from the 'for' loop
4230  break;
4231  case Alarm::Audio:
4232  audioOnly = true;
4233  break;
4234  default:
4235  break;
4236  }
4237  }
4238 
4239  for (int i = 0, end = alarms.count(); i < end; ++i)
4240  {
4241  // Parse the next alarm's text
4242  AlarmData data;
4243  readAlarm(alarms[i], data, audioOnly, cmdDisplay);
4244  if (data.type != INVALID_ALARM)
4245  alarmMap->insert(data.type, data);
4246  }
4247 }
4248 
4249 /******************************************************************************
4250 * Parse a KCal::Alarm.
4251 * If 'audioMain' is true, the event contains an audio alarm but no display alarm.
4252 * Reply = alarm ID (sequence number)
4253 */
4254 #ifndef KALARMCAL_USE_KRESOURCES
4255 void KAEvent::Private::readAlarm(const Alarm::Ptr& alarm, AlarmData& data, bool audioMain, bool cmdDisplay)
4256 #else
4257 void KAEvent::Private::readAlarm(const Alarm* alarm, AlarmData& data, bool audioMain, bool cmdDisplay)
4258 #endif
4259 {
4260  // Parse the next alarm's text
4261  data.alarm = alarm;
4262  data.displayingFlags = 0;
4263  data.isEmailText = false;
4264  data.speak = false;
4265  data.hiddenReminder = false;
4266  data.timedDeferral = false;
4267  data.nextRepeat = 0;
4268  data.repeatSoundPause = -1;
4269  if (alarm->repeatCount())
4270  {
4271  bool ok;
4272  const QString property = alarm->customProperty(KACalendar::APPNAME, Private::NEXT_REPEAT_PROPERTY);
4273  int n = static_cast<int>(property.toUInt(&ok));
4274  if (ok)
4275  data.nextRepeat = n;
4276  }
4277  QString property = alarm->customProperty(KACalendar::APPNAME, Private::FLAGS_PROPERTY);
4278  const QStringList flags = property.split(Private::SC, QString::SkipEmptyParts);
4279  switch (alarm->type())
4280  {
4281  case Alarm::Procedure:
4282  data.action = KAAlarm::COMMAND;
4283  data.cleanText = alarm->programFile();
4284  data.commandScript = data.cleanText.isEmpty(); // blank command indicates a script
4285  if (!alarm->programArguments().isEmpty())
4286  {
4287  if (!data.commandScript)
4288  data.cleanText += ' ';
4289  data.cleanText += alarm->programArguments();
4290  }
4291  data.extraActionOptions = 0;
4292  if (flags.contains(Private::EXEC_ON_DEFERRAL_FLAG))
4293  data.extraActionOptions |= ExecPreActOnDeferral;
4294  if (flags.contains(Private::CANCEL_ON_ERROR_FLAG))
4295  data.extraActionOptions |= CancelOnPreActError;
4296  if (flags.contains(Private::DONT_SHOW_ERROR_FLAG))
4297  data.extraActionOptions |= DontShowPreActError;
4298  if (!cmdDisplay)
4299  break;
4300  // fall through to Display
4301  case Alarm::Display:
4302  {
4303  if (alarm->type() == Alarm::Display)
4304  {
4305  data.action = KAAlarm::MESSAGE;
4306  data.cleanText = AlarmText::fromCalendarText(alarm->text(), data.isEmailText);
4307  }
4308  const QString property = alarm->customProperty(KACalendar::APPNAME, Private::FONT_COLOUR_PROPERTY);
4309  const QStringList list = property.split(QLatin1Char(';'), QString::KeepEmptyParts);
4310  data.bgColour = QColor(255, 255, 255); // white
4311  data.fgColour = QColor(0, 0, 0); // black
4312  const int n = list.count();
4313  if (n > 0)
4314  {
4315  if (!list[0].isEmpty())
4316  {
4317  QColor c(list[0]);
4318  if (c.isValid())
4319  data.bgColour = c;
4320  }
4321  if (n > 1 && !list[1].isEmpty())
4322  {
4323  QColor c(list[1]);
4324  if (c.isValid())
4325  data.fgColour = c;
4326  }
4327  }
4328  data.defaultFont = (n <= 2 || list[2].isEmpty());
4329  if (!data.defaultFont)
4330  data.font.fromString(list[2]);
4331  break;
4332  }
4333  case Alarm::Email:
4334  {
4335  data.action = KAAlarm::EMAIL;
4336  data.cleanText = alarm->mailText();
4337  const int i = flags.indexOf(Private::EMAIL_ID_FLAG);
4338  data.emailFromId = (i >= 0 && i + 1 < flags.count()) ? flags[i + 1].toUInt() : 0;
4339  break;
4340  }
4341  case Alarm::Audio:
4342  {
4343  data.action = KAAlarm::AUDIO;
4344  data.cleanText = alarm->audioFile();
4345  data.repeatSoundPause = (alarm->repeatCount() == -2) ? alarm->snoozeTime().asSeconds()
4346  : (alarm->repeatCount() == -1) ? 0 : -1;
4347  data.soundVolume = -1;
4348  data.fadeVolume = -1;
4349  data.fadeSeconds = 0;
4350  QString property = alarm->customProperty(KACalendar::APPNAME, Private::VOLUME_PROPERTY);
4351  if (!property.isEmpty())
4352  {
4353  bool ok;
4354  float fadeVolume;
4355  int fadeSecs = 0;
4356  const QStringList list = property.split(QLatin1Char(';'), QString::KeepEmptyParts);
4357  data.soundVolume = list[0].toFloat(&ok);
4358  if (!ok || data.soundVolume > 1.0f)
4359  data.soundVolume = -1;
4360  if (data.soundVolume >= 0 && list.count() >= 3)
4361  {
4362  fadeVolume = list[1].toFloat(&ok);
4363  if (ok)
4364  fadeSecs = static_cast<int>(list[2].toUInt(&ok));
4365  if (ok && fadeVolume >= 0 && fadeVolume <= 1.0f && fadeSecs > 0)
4366  {
4367  data.fadeVolume = fadeVolume;
4368  data.fadeSeconds = fadeSecs;
4369  }
4370  }
4371  }
4372  if (!audioMain)
4373  {
4374  data.type = AUDIO_ALARM;
4375  data.speak = flags.contains(Private::SPEAK_FLAG);
4376  return;
4377  }
4378  break;
4379  }
4380  case Alarm::Invalid:
4381  data.type = INVALID_ALARM;
4382  return;
4383  }
4384 
4385  bool atLogin = false;
4386  bool reminder = false;
4387  bool deferral = false;
4388  bool dateDeferral = false;
4389  bool repeatSound = false;
4390  data.type = MAIN_ALARM;
4391  property = alarm->customProperty(KACalendar::APPNAME, Private::TYPE_PROPERTY);
4392  const QStringList types = property.split(QLatin1Char(','), QString::SkipEmptyParts);
4393  for (int i = 0, end = types.count(); i < end; ++i)
4394  {
4395  const QString type = types[i];
4396  if (type == Private::AT_LOGIN_TYPE)
4397  atLogin = true;
4398  else if (type == Private::FILE_TYPE && data.action == KAAlarm::MESSAGE)
4399  data.action = KAAlarm::FILE;
4400  else if (type == Private::REMINDER_TYPE)
4401  reminder = true;
4402  else if (type == Private::TIME_DEFERRAL_TYPE)
4403  deferral = true;
4404  else if (type == Private::DATE_DEFERRAL_TYPE)
4405  dateDeferral = deferral = true;
4406  else if (type == Private::DISPLAYING_TYPE)
4407  data.type = DISPLAYING_ALARM;
4408  else if (type == Private::PRE_ACTION_TYPE && data.action == KAAlarm::COMMAND)
4409  data.type = PRE_ACTION_ALARM;
4410  else if (type == Private::POST_ACTION_TYPE && data.action == KAAlarm::COMMAND)
4411  data.type = POST_ACTION_ALARM;
4412  else if (type == Private::SOUND_REPEAT_TYPE && data.action == KAAlarm::AUDIO)
4413  {
4414  repeatSound = true;
4415  if (i + 1 < end)
4416  {
4417  bool ok;
4418  uint n = types[i + 1].toUInt(&ok);
4419  if (ok)
4420  {
4421  data.repeatSoundPause = n;
4422  ++i;
4423  }
4424  }
4425  }
4426  }
4427  if (repeatSound && data.repeatSoundPause < 0)
4428  data.repeatSoundPause = 0;
4429  else if (!repeatSound)
4430  data.repeatSoundPause = -1;
4431 
4432  if (reminder)
4433  {
4434  if (data.type == MAIN_ALARM)
4435  {
4436  data.type = deferral ? DEFERRED_REMINDER_ALARM : REMINDER_ALARM;
4437  data.timedDeferral = (deferral && !dateDeferral);
4438  }
4439  else if (data.type == DISPLAYING_ALARM)
4440  data.displayingFlags = dateDeferral ? REMINDER | DATE_DEFERRAL
4441  : deferral ? REMINDER | TIME_DEFERRAL : REMINDER;
4442  else if (data.type == REMINDER_ALARM
4443  && flags.contains(Private::HIDDEN_REMINDER_FLAG))
4444  data.hiddenReminder = true;
4445  }
4446  else if (deferral)
4447  {
4448  if (data.type == MAIN_ALARM)
4449  {
4450  data.type = DEFERRED_ALARM;
4451  data.timedDeferral = !dateDeferral;
4452  }
4453  else if (data.type == DISPLAYING_ALARM)
4454  data.displayingFlags = dateDeferral ? DATE_DEFERRAL : TIME_DEFERRAL;
4455  }
4456  if (atLogin)
4457  {
4458  if (data.type == MAIN_ALARM)
4459  data.type = AT_LOGIN_ALARM;
4460  else if (data.type == DISPLAYING_ALARM)
4461  data.displayingFlags = REPEAT_AT_LOGIN;
4462  }
4463 //kDebug()<<"text="<<alarm->text()<<", time="<<alarm->time().toString()<<", valid time="<<alarm->time().isValid();
4464 }
4465 
4466 /******************************************************************************
4467 * Calculate the next trigger times of the alarm.
4468 * This should only be called when changes have actually occurred which might
4469 * affect the event's trigger times.
4470 * mMainTrigger is set to the next scheduled recurrence/sub-repetition, or the
4471 * deferral time if a deferral is pending.
4472 * mAllTrigger is the same as mMainTrigger, but takes account of reminders.
4473 * mMainWorkTrigger is set to the next scheduled recurrence/sub-repetition
4474 * which occurs in working hours, if working-time-only is set.
4475 * mAllWorkTrigger is the same as mMainWorkTrigger, but takes account of reminders.
4476 */
4477 void KAEvent::Private::calcTriggerTimes() const
4478 {
4479  if (mChangeCount)
4480  return;
4481 #ifdef __GNUC__
4482 #warning May need to set date-only alarms to after start-of-day time in working-time checks
4483 #endif
4484  bool recurs = (checkRecur() != KARecurrence::NO_RECUR);
4485  if ((recurs && mWorkTimeOnly && mWorkTimeOnly != mWorkTimeIndex)
4486  || (recurs && mExcludeHolidays && mExcludeHolidays != mHolidays))
4487  {
4488  // It's a work time alarm, and work days/times have changed, or
4489  // it excludes holidays, and the holidays definition has changed.
4490  mTriggerChanged = true;
4491  }
4492  else if (!mTriggerChanged)
4493  return;
4494  mTriggerChanged = false;
4495  if (recurs && mWorkTimeOnly)
4496  mWorkTimeOnly = mWorkTimeIndex; // note which work time definition was used in calculation
4497  if (recurs && mExcludeHolidays)
4498  mExcludeHolidays = mHolidays; // note which holiday definition was used in calculation
4499 
4500  if (mCategory == CalEvent::ARCHIVED || mCategory == CalEvent::TEMPLATE)
4501  {
4502  // It's a template or archived
4503  mAllTrigger = mMainTrigger = mAllWorkTrigger = mMainWorkTrigger = KDateTime();
4504  }
4505  else if (mDeferral == NORMAL_DEFERRAL)
4506  {
4507  // For a deferred alarm, working time setting is ignored
4508  mAllTrigger = mMainTrigger = mAllWorkTrigger = mMainWorkTrigger = mDeferralTime;
4509  }
4510  else
4511  {
4512  mMainTrigger = mainDateTime(true); // next recurrence or sub-repetition
4513  mAllTrigger = (mDeferral == REMINDER_DEFERRAL) ? mDeferralTime
4514  : (mReminderActive != ACTIVE_REMINDER) ? mMainTrigger
4515  : (mReminderMinutes < 0) ? mReminderAfterTime
4516  : mMainTrigger.addMins(-mReminderMinutes);
4517  // It's not deferred.
4518  // If only-during-working-time is set and it recurs, it won't actually trigger
4519  // unless it falls during working hours.
4520  if ((!mWorkTimeOnly && !mExcludeHolidays)
4521  || !recurs
4522  || isWorkingTime(mMainTrigger.kDateTime()))
4523  {
4524  // It only occurs once, or it complies with any working hours/holiday
4525  // restrictions.
4526  mMainWorkTrigger = mMainTrigger;
4527  mAllWorkTrigger = mAllTrigger;
4528  }
4529  else if (mWorkTimeOnly)
4530  {
4531  // The alarm is restricted to working hours.
4532  // Finding the next occurrence during working hours can sometimes take a long time,
4533  // so mark the next actual trigger as invalid until the calculation completes.
4534  // Note that reminders are only triggered if the main alarm is during working time.
4535  if (!mExcludeHolidays)
4536  {
4537  // There are no holiday restrictions.
4538  calcNextWorkingTime(mMainTrigger);
4539  }
4540  else if (mHolidays)
4541  {
4542  // Holidays are excluded.
4543  DateTime nextTrigger = mMainTrigger;
4544  KDateTime kdt;
4545  for (int i = 0; i < 20; ++i)
4546  {
4547  calcNextWorkingTime(nextTrigger);
4548  if (!mHolidays->isHoliday(mMainWorkTrigger.date()))
4549  return; // found a non-holiday occurrence
4550  kdt = mMainWorkTrigger.effectiveKDateTime();
4551  kdt.setTime(QTime(23,59,59));
4552  const OccurType type = nextOccurrence(kdt, nextTrigger, RETURN_REPETITION);
4553  if (!nextTrigger.isValid())
4554  break;
4555  if (isWorkingTime(nextTrigger.kDateTime()))
4556  {
4557  const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm
4558  mMainWorkTrigger = nextTrigger;
4559  mAllWorkTrigger = (type & OCCURRENCE_REPEAT) ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder);
4560  return; // found a non-holiday occurrence
4561  }
4562  }
4563  mMainWorkTrigger = mAllWorkTrigger = DateTime();
4564  }
4565  }
4566  else if (mExcludeHolidays && mHolidays)
4567  {
4568  // Holidays are excluded.
4569  DateTime nextTrigger = mMainTrigger;
4570  KDateTime kdt;
4571  for (int i = 0; i < 20; ++i)
4572  {
4573  kdt = nextTrigger.effectiveKDateTime();
4574  kdt.setTime(QTime(23,59,59));
4575  const OccurType type = nextOccurrence(kdt, nextTrigger, RETURN_REPETITION);
4576  if (!nextTrigger.isValid())
4577  break;
4578  if (!mHolidays->isHoliday(nextTrigger.date()))
4579  {
4580  const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm
4581  mMainWorkTrigger = nextTrigger;
4582  mAllWorkTrigger = (type & OCCURRENCE_REPEAT) ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder);
4583  return; // found a non-holiday occurrence
4584  }
4585  }
4586  mMainWorkTrigger = mAllWorkTrigger = DateTime();
4587  }
4588  }
4589 }
4590 
4591 /******************************************************************************
4592 * Return the time of the next scheduled occurrence of the event during working
4593 * hours, for an alarm which is restricted to working hours.
4594 * On entry, 'nextTrigger' = the next recurrence or repetition (as returned by
4595 * mainDateTime(true) ).
4596 */
4597 void KAEvent::Private::calcNextWorkingTime(const DateTime& nextTrigger) const
4598 {
4599  kDebug() << "next=" << nextTrigger.kDateTime().dateTime();
4600  mMainWorkTrigger = mAllWorkTrigger = DateTime();
4601 
4602  for (int i = 0; ; ++i)
4603  {
4604  if (i >= 7)
4605  return; // no working days are defined
4606  if (mWorkDays.testBit(i))
4607  break;
4608  }
4609  const KARecurrence::Type recurType = checkRecur();
4610  KDateTime kdt = nextTrigger.effectiveKDateTime();
4611  const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm
4612  // Check if it always falls on the same day(s) of the week.
4613  const RecurrenceRule* rrule = mRecurrence->defaultRRuleConst();
4614  if (!rrule)
4615  return; // no recurrence rule!
4616  unsigned allDaysMask = 0x7F; // mask bits for all days of week
4617  bool noWorkPos = false; // true if no recurrence day position is working day
4618  const QList<RecurrenceRule::WDayPos> pos = rrule->byDays();
4619  const int nDayPos = pos.count(); // number of day positions
4620  if (nDayPos)
4621  {
4622  noWorkPos = true;
4623  allDaysMask = 0;
4624  for (int i = 0; i < nDayPos; ++i)
4625  {
4626  const int day = pos[i].day() - 1; // Monday = 0
4627  if (mWorkDays.testBit(day))
4628  noWorkPos = false; // found a working day occurrence
4629  allDaysMask |= 1 << day;
4630  }
4631  if (noWorkPos && !mRepetition)
4632  return; // never occurs on a working day
4633  }
4634  DateTime newdt;
4635 
4636  if (mStartDateTime.isDateOnly())
4637  {
4638  // It's a date-only alarm.
4639  // Sub-repetitions also have to be date-only.
4640  const int repeatFreq = mRepetition.intervalDays();
4641  const bool weeklyRepeat = mRepetition && !(repeatFreq % 7);
4642  const Duration interval = mRecurrence->regularInterval();
4643  if ((interval && !(interval.asDays() % 7))
4644  || nDayPos == 1)
4645  {
4646  // It recurs on the same day each week
4647  if (!mRepetition || weeklyRepeat)
4648  return; // any repetitions are also weekly
4649 
4650  // It's a weekly recurrence with a non-weekly sub-repetition.
4651  // Check one cycle of repetitions for the next one that lands
4652  // on a working day.
4653  KDateTime dt(nextTrigger.kDateTime().addDays(1));
4654  dt.setTime(QTime(0,0,0));
4655  previousOccurrence(dt, newdt, false);
4656  if (!newdt.isValid())
4657  return; // this should never happen
4658  kdt = newdt.effectiveKDateTime();
4659  const int day = kdt.date().dayOfWeek() - 1; // Monday = 0
4660  for (int repeatNum = mNextRepeat + 1; ; ++repeatNum)
4661  {
4662  if (repeatNum > mRepetition.count())
4663  repeatNum = 0;
4664  if (repeatNum == mNextRepeat)
4665  break;
4666  if (!repeatNum)
4667  {
4668  nextOccurrence(newdt.kDateTime(), newdt, IGNORE_REPETITION);
4669  if (mWorkDays.testBit(day))
4670  {
4671  mMainWorkTrigger = newdt;
4672  mAllWorkTrigger = mMainWorkTrigger.addMins(-reminder);
4673  return;
4674  }
4675  kdt = newdt.effectiveKDateTime();
4676  }
4677  else
4678  {
4679  const int inc = repeatFreq * repeatNum;
4680  if (mWorkDays.testBit((day + inc) % 7))
4681  {
4682  kdt = kdt.addDays(inc);
4683  kdt.setDateOnly(true);
4684  mMainWorkTrigger = mAllWorkTrigger = kdt;
4685  return;
4686  }
4687  }
4688  }
4689  return;
4690  }
4691  if (!mRepetition || weeklyRepeat)
4692  {
4693  // It's a date-only alarm with either no sub-repetition or a
4694  // sub-repetition which always falls on the same day of the week
4695  // as the recurrence (if any).
4696  unsigned days = 0;
4697  for ( ; ; )
4698  {
4699  kdt.setTime(QTime(23,59,59));
4700  nextOccurrence(kdt, newdt, IGNORE_REPETITION);
4701  if (!newdt.isValid())
4702  return;
4703  kdt = newdt.effectiveKDateTime();
4704  const int day = kdt.date().dayOfWeek() - 1;
4705  if (mWorkDays.testBit(day))
4706  break; // found a working day occurrence
4707  // Prevent indefinite looping (which should never happen anyway)
4708  if ((days & allDaysMask) == allDaysMask)
4709  return; // found a recurrence on every possible day of the week!?!
4710  days |= 1 << day;
4711  }
4712  kdt.setDateOnly(true);
4713  mMainWorkTrigger = kdt;
4714  mAllWorkTrigger = kdt.addSecs(-60 * reminder);
4715  return;
4716  }
4717 
4718  // It's a date-only alarm which recurs on different days of the week,
4719  // as does the sub-repetition.
4720  // Find the previous recurrence (as opposed to sub-repetition)
4721  unsigned days = 1 << (kdt.date().dayOfWeek() - 1);
4722  KDateTime dt(nextTrigger.kDateTime().addDays(1));
4723  dt.setTime(QTime(0,0,0));
4724  previousOccurrence(dt, newdt, false);
4725  if (!newdt.isValid())
4726  return; // this should never happen
4727  kdt = newdt.effectiveKDateTime();
4728  int day = kdt.date().dayOfWeek() - 1; // Monday = 0
4729  for (int repeatNum = mNextRepeat; ; repeatNum = 0)
4730  {
4731  while (++repeatNum <= mRepetition.count())
4732  {
4733  const int inc = repeatFreq * repeatNum;
4734  if (mWorkDays.testBit((day + inc) % 7))
4735  {
4736  kdt = kdt.addDays(inc);
4737  kdt.setDateOnly(true);
4738  mMainWorkTrigger = mAllWorkTrigger = kdt;
4739  return;
4740  }
4741  if ((days & allDaysMask) == allDaysMask)
4742  return; // found an occurrence on every possible day of the week!?!
4743  days |= 1 << day;
4744  }
4745  nextOccurrence(kdt, newdt, IGNORE_REPETITION);
4746  if (!newdt.isValid())
4747  return;
4748  kdt = newdt.effectiveKDateTime();
4749  day = kdt.date().dayOfWeek() - 1;
4750  if (mWorkDays.testBit(day))
4751  {
4752  kdt.setDateOnly(true);
4753  mMainWorkTrigger = kdt;
4754  mAllWorkTrigger = kdt.addSecs(-60 * reminder);
4755  return;
4756  }
4757  if ((days & allDaysMask) == allDaysMask)
4758  return; // found an occurrence on every possible day of the week!?!
4759  days |= 1 << day;
4760  }
4761  return;
4762  }
4763 
4764  // It's a date-time alarm.
4765 
4766  /* Check whether the recurrence or sub-repetition occurs at the same time
4767  * every day. Note that because of seasonal time changes, a recurrence
4768  * defined in terms of minutes will vary its time of day even if its value
4769  * is a multiple of a day (24*60 minutes). Sub-repetitions are considered
4770  * to repeat at the same time of day regardless of time changes if they
4771  * are multiples of a day, which doesn't strictly conform to the iCalendar
4772  * format because this only allows their interval to be recorded in seconds.
4773  */
4774  const bool recurTimeVaries = (recurType == KARecurrence::MINUTELY);
4775  const bool repeatTimeVaries = (mRepetition && !mRepetition.isDaily());
4776 
4777  if (!recurTimeVaries && !repeatTimeVaries)
4778  {
4779  // The alarm always occurs at the same time of day.
4780  // Check whether it can ever occur during working hours.
4781  if (!mayOccurDailyDuringWork(kdt))
4782  return; // never occurs during working hours
4783 
4784  // Find the next working day it occurs on
4785  bool repetition = false;
4786  unsigned days = 0;
4787  for ( ; ; )
4788  {
4789  OccurType type = nextOccurrence(kdt, newdt, RETURN_REPETITION);
4790  if (!newdt.isValid())
4791  return;
4792  repetition = (type & OCCURRENCE_REPEAT);
4793  kdt = newdt.effectiveKDateTime();
4794  const int day = kdt.date().dayOfWeek() - 1;
4795  if (mWorkDays.testBit(day))
4796  break; // found a working day occurrence
4797  // Prevent indefinite looping (which should never happen anyway)
4798  if (!repetition)
4799  {
4800  if ((days & allDaysMask) == allDaysMask)
4801  return; // found a recurrence on every possible day of the week!?!
4802  days |= 1 << day;
4803  }
4804  }
4805  mMainWorkTrigger = nextTrigger;
4806  mMainWorkTrigger.setDate(kdt.date());
4807  mAllWorkTrigger = repetition ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder);
4808  return;
4809  }
4810 
4811  // The alarm occurs at different times of day.
4812  // We may need to check for a full annual cycle of seasonal time changes, in
4813  // case it only occurs during working hours after a time change.
4814  KTimeZone tz = kdt.timeZone();
4815  if (tz.isValid() && tz.type() == "KSystemTimeZone")
4816  {
4817  // It's a system time zone, so fetch full transition information
4818  const KTimeZone ktz = KSystemTimeZones::readZone(tz.name());
4819  if (ktz.isValid())
4820  tz = ktz;
4821  }
4822  const QList<KTimeZone::Transition> tzTransitions = tz.transitions();
4823 
4824  if (recurTimeVaries)
4825  {
4826  /* The alarm recurs at regular clock intervals, at different times of day.
4827  * Note that for this type of recurrence, it's necessary to avoid the
4828  * performance overhead of Recurrence class calls since these can in the
4829  * worst case cause the program to hang for a significant length of time.
4830  * In this case, we can calculate the next recurrence by simply adding the
4831  * recurrence interval, since KAlarm offers no facility to regularly miss
4832  * recurrences. (But exception dates/times need to be taken into account.)
4833  */
4834  KDateTime kdtRecur;
4835  int repeatFreq = 0;
4836  int repeatNum = 0;
4837  if (mRepetition)
4838  {
4839  // It's a repetition inside a recurrence, each of which occurs
4840  // at different times of day (bearing in mind that the repetition
4841  // may occur at daily intervals after each recurrence).
4842  // Find the previous recurrence (as opposed to sub-repetition)
4843  repeatFreq = mRepetition.intervalSeconds();
4844  previousOccurrence(kdt.addSecs(1), newdt, false);
4845  if (!newdt.isValid())
4846  return; // this should never happen
4847  kdtRecur = newdt.effectiveKDateTime();
4848  repeatNum = kdtRecur.secsTo(kdt) / repeatFreq;
4849  kdt = kdtRecur.addSecs(repeatNum * repeatFreq);
4850  }
4851  else
4852  {
4853  // There is no sub-repetition.
4854  // (N.B. Sub-repetitions can't exist without a recurrence.)
4855  // Check until the original time wraps round, but ensure that
4856  // if there are seasonal time changes, that all other subsequent
4857  // time offsets within the next year are checked.
4858  // This does not guarantee to find the next working time,
4859  // particularly if there are exceptions, but it's a
4860  // reasonable try.
4861  kdtRecur = kdt;
4862  }
4863  QTime firstTime = kdtRecur.time();
4864  int firstOffset = kdtRecur.utcOffset();
4865  int currentOffset = firstOffset;
4866  int dayRecur = kdtRecur.date().dayOfWeek() - 1; // Monday = 0
4867  int firstDay = dayRecur;
4868  QDate finalDate;
4869  const bool subdaily = (repeatFreq < 24*3600);
4870 // int period = mRecurrence->frequency() % (24*60); // it is by definition a MINUTELY recurrence
4871 // int limit = (24*60 + period - 1) / period; // number of times until recurrence wraps round
4872  int transitionIndex = -1;
4873  for (int n = 0; n < 7*24*60; ++n)
4874  {
4875  if (mRepetition)
4876  {
4877  // Check the sub-repetitions for this recurrence
4878  for ( ; ; )
4879  {
4880  // Find the repeat count to the next start of the working day
4881  const int inc = subdaily ? nextWorkRepetition(kdt) : 1;
4882  repeatNum += inc;
4883  if (repeatNum > mRepetition.count())
4884  break;
4885  kdt = kdt.addSecs(inc * repeatFreq);
4886  const QTime t = kdt.time();
4887  if (t >= mWorkDayStart && t < mWorkDayEnd)
4888  {
4889  if (mWorkDays.testBit(kdt.date().dayOfWeek() - 1))
4890  {
4891  mMainWorkTrigger = mAllWorkTrigger = kdt;
4892  return;
4893  }
4894  }
4895  }
4896  repeatNum = 0;
4897  }
4898  nextOccurrence(kdtRecur, newdt, IGNORE_REPETITION);
4899  if (!newdt.isValid())
4900  return;
4901  kdtRecur = newdt.effectiveKDateTime();
4902  dayRecur = kdtRecur.date().dayOfWeek() - 1; // Monday = 0
4903  const QTime t = kdtRecur.time();
4904  if (t >= mWorkDayStart && t < mWorkDayEnd)
4905  {
4906  if (mWorkDays.testBit(dayRecur))
4907  {
4908  mMainWorkTrigger = kdtRecur;
4909  mAllWorkTrigger = kdtRecur.addSecs(-60 * reminder);
4910  return;
4911  }
4912  }
4913  if (kdtRecur.utcOffset() != currentOffset)
4914  currentOffset = kdtRecur.utcOffset();
4915  if (t == firstTime && dayRecur == firstDay && currentOffset == firstOffset)
4916  {
4917  // We've wrapped round to the starting day and time.
4918  // If there are seasonal time changes, check for up
4919  // to the next year in other time offsets in case the
4920  // alarm occurs inside working hours then.
4921  if (!finalDate.isValid())
4922  finalDate = kdtRecur.date();
4923  const int i = tz.transitionIndex(kdtRecur.toUtc().dateTime());
4924  if (i < 0)
4925  return;
4926  if (i > transitionIndex)
4927  transitionIndex = i;
4928  if (++transitionIndex >= static_cast<int>(tzTransitions.count()))
4929  return;
4930  previousOccurrence(KDateTime(tzTransitions[transitionIndex].time(), KDateTime::UTC), newdt, IGNORE_REPETITION);
4931  kdtRecur = newdt.effectiveKDateTime();
4932  if (finalDate.daysTo(kdtRecur.date()) > 365)
4933  return;
4934  firstTime = kdtRecur.time();
4935  firstOffset = kdtRecur.utcOffset();
4936  currentOffset = firstOffset;
4937  firstDay = kdtRecur.date().dayOfWeek() - 1;
4938  }
4939  kdt = kdtRecur;
4940  }
4941 //kDebug()<<"-----exit loop: count="<<limit<<endl;
4942  return; // too many iterations
4943  }
4944 
4945  if (repeatTimeVaries)
4946  {
4947  /* There's a sub-repetition which occurs at different times of
4948  * day, inside a recurrence which occurs at the same time of day.
4949  * We potentially need to check recurrences starting on each day.
4950  * Then, it is still possible that a working time sub-repetition
4951  * could occur immediately after a seasonal time change.
4952  */
4953  // Find the previous recurrence (as opposed to sub-repetition)
4954  const int repeatFreq = mRepetition.intervalSeconds();
4955  previousOccurrence(kdt.addSecs(1), newdt, false);
4956  if (!newdt.isValid())
4957  return; // this should never happen
4958  KDateTime kdtRecur = newdt.effectiveKDateTime();
4959  const bool recurDuringWork = (kdtRecur.time() >= mWorkDayStart && kdtRecur.time() < mWorkDayEnd);
4960 
4961  // Use the previous recurrence as a base for checking whether
4962  // our tests have wrapped round to the same time/day of week.
4963  const bool subdaily = (repeatFreq < 24*3600);
4964  unsigned days = 0;
4965  bool checkTimeChangeOnly = false;
4966  int transitionIndex = -1;
4967  for (int limit = 10; --limit >= 0; )
4968  {
4969  // Check the next seasonal time change (for an arbitrary 10 times,
4970  // even though that might not guarantee the correct result)
4971  QDate dateRecur = kdtRecur.date();
4972  int dayRecur = dateRecur.dayOfWeek() - 1; // Monday = 0
4973  int repeatNum = kdtRecur.secsTo(kdt) / repeatFreq;
4974  kdt = kdtRecur.addSecs(repeatNum * repeatFreq);
4975 
4976  // Find the next recurrence, which sets the limit on possible sub-repetitions.
4977  // Note that for a monthly recurrence, for example, a sub-repetition could
4978  // be defined which is longer than the recurrence interval in short months.
4979  // In these cases, the sub-repetition is truncated by the following
4980  // recurrence.
4981  nextOccurrence(kdtRecur, newdt, IGNORE_REPETITION);
4982  KDateTime kdtNextRecur = newdt.effectiveKDateTime();
4983 
4984  int repeatsToCheck = mRepetition.count();
4985  int repeatsDuringWork = 0; // 0=unknown, 1=does, -1=never
4986  for ( ; ; )
4987  {
4988  // Check the sub-repetitions for this recurrence
4989  if (repeatsDuringWork >= 0)
4990  {
4991  for ( ; ; )
4992  {
4993  // Find the repeat count to the next start of the working day
4994  int inc = subdaily ? nextWorkRepetition(kdt) : 1;
4995  repeatNum += inc;
4996  const bool pastEnd = (repeatNum > mRepetition.count());
4997  if (pastEnd)
4998  inc -= repeatNum - mRepetition.count();
4999  repeatsToCheck -= inc;
5000  kdt = kdt.addSecs(inc * repeatFreq);
5001  if (kdtNextRecur.isValid() && kdt >= kdtNextRecur)
5002  {
5003  // This sub-repetition is past the next recurrence,
5004  // so start the check again from the next recurrence.
5005  repeatsToCheck = mRepetition.count();
5006  break;
5007  }
5008  if (pastEnd)
5009  break;
5010  const QTime t = kdt.time();
5011  if (t >= mWorkDayStart && t < mWorkDayEnd)
5012  {
5013  if (mWorkDays.testBit(kdt.date().dayOfWeek() - 1))
5014  {
5015  mMainWorkTrigger = mAllWorkTrigger = kdt;
5016  return;
5017  }
5018  repeatsDuringWork = 1;
5019  }
5020  else if (!repeatsDuringWork && repeatsToCheck <= 0)
5021  {
5022  // Sub-repetitions never occur during working hours
5023  repeatsDuringWork = -1;
5024  break;
5025  }
5026  }
5027  }
5028  repeatNum = 0;
5029  if (repeatsDuringWork < 0 && !recurDuringWork)
5030  break; // it never occurs during working hours
5031 
5032  // Check the next recurrence
5033  if (!kdtNextRecur.isValid())
5034  return;
5035  if (checkTimeChangeOnly || (days & allDaysMask) == allDaysMask)
5036  break; // found a recurrence on every possible day of the week!?!
5037  kdtRecur = kdtNextRecur;
5038  nextOccurrence(kdtRecur, newdt, IGNORE_REPETITION);
5039  kdtNextRecur = newdt.effectiveKDateTime();
5040  dateRecur = kdtRecur.date();
5041  dayRecur = dateRecur.dayOfWeek() - 1;
5042  if (recurDuringWork && mWorkDays.testBit(dayRecur))
5043  {
5044  mMainWorkTrigger = kdtRecur;
5045  mAllWorkTrigger = kdtRecur.addSecs(-60 * reminder);
5046  return;
5047  }
5048  days |= 1 << dayRecur;
5049  kdt = kdtRecur;
5050  }
5051 
5052  // Find the next recurrence before a seasonal time change,
5053  // and ensure the time change is after the last one processed.
5054  checkTimeChangeOnly = true;
5055  const int i = tz.transitionIndex(kdtRecur.toUtc().dateTime());
5056  if (i < 0)
5057  return;
5058  if (i > transitionIndex)
5059  transitionIndex = i;
5060  if (++transitionIndex >= static_cast<int>(tzTransitions.count()))
5061  return;
5062  kdt = KDateTime(tzTransitions[transitionIndex].time(), KDateTime::UTC);
5063  previousOccurrence(kdt, newdt, IGNORE_REPETITION);
5064  kdtRecur = newdt.effectiveKDateTime();
5065  }
5066  return; // not found - give up
5067  }
5068 }
5069 
5070 /******************************************************************************
5071 * Find the repeat count to the next start of a working day.
5072 * This allows for possible daylight saving time changes during the repetition.
5073 * Use for repetitions which occur at different times of day.
5074 */
5075 int KAEvent::Private::nextWorkRepetition(const KDateTime& pre) const
5076 {
5077  KDateTime nextWork(pre);
5078  if (pre.time() < mWorkDayStart)
5079  nextWork.setTime(mWorkDayStart);
5080  else
5081  {
5082  const int preDay = pre.date().dayOfWeek() - 1; // Monday = 0
5083  for (int n = 1; ; ++n)
5084  {
5085  if (n >= 7)
5086  return mRepetition.count() + 1; // should never happen
5087  if (mWorkDays.testBit((preDay + n) % 7))
5088  {
5089  nextWork = nextWork.addDays(n);
5090  nextWork.setTime(mWorkDayStart);
5091  break;
5092  }
5093  }
5094  }
5095  return (pre.secsTo(nextWork) - 1) / mRepetition.intervalSeconds() + 1;
5096 }
5097 
5098 /******************************************************************************
5099 * Check whether an alarm which recurs at the same time of day can possibly
5100 * occur during working hours.
5101 * This does not determine whether it actually does, but rather whether it could
5102 * potentially given enough repetitions.
5103 * Reply = false if it can never occur during working hours, true if it might.
5104 */
5105 bool KAEvent::Private::mayOccurDailyDuringWork(const KDateTime& kdt) const
5106 {
5107  if (!kdt.isDateOnly()
5108  && (kdt.time() < mWorkDayStart || kdt.time() >= mWorkDayEnd))
5109  return false; // its time is outside working hours
5110  // Check if it always occurs on the same day of the week
5111  const Duration interval = mRecurrence->regularInterval();
5112  if (interval && interval.isDaily() && !(interval.asDays() % 7))
5113  {
5114  // It recurs weekly
5115  if (!mRepetition || (mRepetition.isDaily() && !(mRepetition.intervalDays() % 7)))
5116  return false; // any repetitions are also weekly
5117  // Repetitions are daily. Check if any occur on working days
5118  // by checking the first recurrence and up to 6 repetitions.
5119  int day = mRecurrence->startDateTime().date().dayOfWeek() - 1; // Monday = 0
5120  const int repeatDays = mRepetition.intervalDays();
5121  const int maxRepeat = (mRepetition.count() < 6) ? mRepetition.count() : 6;
5122  for (int i = 0; !mWorkDays.testBit(day); ++i, day = (day + repeatDays) % 7)
5123  {
5124  if (i >= maxRepeat)
5125  return false; // no working day occurrences
5126  }
5127  }
5128  return true;
5129 }
5130 
5131 /******************************************************************************
5132 * Set the specified alarm to be an audio alarm with the given file name.
5133 */
5134 #ifndef KALARMCAL_USE_KRESOURCES
5135 void KAEvent::Private::setAudioAlarm(const Alarm::Ptr& alarm) const
5136 #else
5137 void KAEvent::Private::setAudioAlarm(Alarm* alarm) const
5138 #endif
5139 {
5140  alarm->setAudioAlarm(mAudioFile); // empty for a beep or for speaking
5141  if (mSoundVolume >= 0)
5142  alarm->setCustomProperty(KACalendar::APPNAME, VOLUME_PROPERTY,
5143  QString::fromLatin1("%1;%2;%3;%4").arg(QString::number(mSoundVolume, 'f', 2))
5144  .arg(QString::number(mFadeVolume, 'f', 2))
5145  .arg(mFadeSeconds));
5146 }
5147 
5148 /******************************************************************************
5149 * Get the date/time of the next recurrence of the event, after the specified
5150 * date/time.
5151 * 'result' = date/time of next occurrence, or invalid date/time if none.
5152 */
5153 KAEvent::OccurType KAEvent::Private::nextRecurrence(const KDateTime& preDateTime, DateTime& result) const
5154 {
5155  const KDateTime recurStart = mRecurrence->startDateTime();
5156  KDateTime pre = preDateTime.toTimeSpec(mStartDateTime.timeSpec());
5157  if (mStartDateTime.isDateOnly() && !pre.isDateOnly() && pre.time() < DateTime::startOfDay())
5158  {
5159  pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come
5160  pre.setTime(DateTime::startOfDay());
5161  }
5162  const KDateTime dt = mRecurrence->getNextDateTime(pre);
5163  result = dt;
5164  result.setDateOnly(mStartDateTime.isDateOnly());
5165  if (!dt.isValid())
5166  return NO_OCCURRENCE;
5167  if (dt == recurStart)
5168  return FIRST_OR_ONLY_OCCURRENCE;
5169  if (mRecurrence->duration() >= 0 && dt == mRecurrence->endDateTime())
5170  return LAST_RECURRENCE;
5171  return result.isDateOnly() ? RECURRENCE_DATE : RECURRENCE_DATE_TIME;
5172 }
5173 
5174 /******************************************************************************
5175 * Validate the event's recurrence data, correcting any inconsistencies (which
5176 * should never occur!).
5177 * Reply = recurrence period type.
5178 */
5179 KARecurrence::Type KAEvent::Private::checkRecur() const
5180 {
5181  if (mRecurrence)
5182  {
5183  KARecurrence::Type type = mRecurrence->type();
5184  switch (type)
5185  {
5186  case KARecurrence::MINUTELY: // hourly
5187  case KARecurrence::DAILY: // daily
5188  case KARecurrence::WEEKLY: // weekly on multiple days of week
5189  case KARecurrence::MONTHLY_DAY: // monthly on multiple dates in month
5190  case KARecurrence::MONTHLY_POS: // monthly on multiple nth day of week
5191  case KARecurrence::ANNUAL_DATE: // annually on multiple months (day of month = start date)
5192  case KARecurrence::ANNUAL_POS: // annually on multiple nth day of week in multiple months
5193  return type;
5194  default:
5195  if (mRecurrence)
5196  const_cast<KAEvent::Private*>(this)->clearRecur(); // this shouldn't ever be necessary!!
5197  break;
5198  }
5199  }
5200  if (mRepetition) // can't have a repetition without a recurrence
5201  const_cast<KAEvent::Private*>(this)->clearRecur(); // this shouldn't ever be necessary!!
5202  return KARecurrence::NO_RECUR;
5203 }
5204 
5205 /******************************************************************************
5206 * If the calendar was written by a previous version of KAlarm, do any
5207 * necessary format conversions on the events to ensure that when the calendar
5208 * is saved, no information is lost or corrupted.
5209 * Reply = true if any conversions were done.
5210 */
5211 #ifndef KALARMCAL_USE_KRESOURCES
5212 bool KAEvent::convertKCalEvents(const Calendar::Ptr& calendar, int calendarVersion)
5213 #else
5214 bool KAEvent::convertKCalEvents(CalendarLocal& calendar, int calendarVersion)
5215 #endif
5216 {
5217  // KAlarm pre-0.9 codes held in the alarm's DESCRIPTION property
5218  static const QChar SEPARATOR = QLatin1Char(';');
5219  static const QChar LATE_CANCEL_CODE = QLatin1Char('C');
5220  static const QChar AT_LOGIN_CODE = QLatin1Char('L'); // subsidiary alarm at every login
5221  static const QChar DEFERRAL_CODE = QLatin1Char('D'); // extra deferred alarm
5222  static const QString TEXT_PREFIX = QLatin1String("TEXT:");
5223  static const QString FILE_PREFIX = QLatin1String("FILE:");
5224  static const QString COMMAND_PREFIX = QLatin1String("CMD:");
5225 
5226  // KAlarm pre-0.9.2 codes held in the event's CATEGORY property
5227  static const QString BEEP_CATEGORY = QLatin1String("BEEP");
5228 
5229  // KAlarm pre-1.1.1 LATECANCEL category with no parameter
5230  static const QString LATE_CANCEL_CAT = QLatin1String("LATECANCEL");
5231 
5232  // KAlarm pre-1.3.0 TMPLDEFTIME category with no parameter
5233  static const QString TEMPL_DEF_TIME_CAT = QLatin1String("TMPLDEFTIME");
5234 
5235  // KAlarm pre-1.3.1 XTERM category
5236  static const QString EXEC_IN_XTERM_CAT = QLatin1String("XTERM");
5237 
5238  // KAlarm pre-1.9.0 categories
5239  static const QString DATE_ONLY_CATEGORY = QLatin1String("DATE");
5240  static const QString EMAIL_BCC_CATEGORY = QLatin1String("BCC");
5241  static const QString CONFIRM_ACK_CATEGORY = QLatin1String("ACKCONF");
5242  static const QString KORGANIZER_CATEGORY = QLatin1String("KORG");
5243  static const QString DEFER_CATEGORY = QLatin1String("DEFER;");
5244  static const QString ARCHIVE_CATEGORY = QLatin1String("SAVE");
5245  static const QString ARCHIVE_CATEGORIES = QLatin1String("SAVE:");
5246  static const QString LATE_CANCEL_CATEGORY = QLatin1String("LATECANCEL;");
5247  static const QString AUTO_CLOSE_CATEGORY = QLatin1String("LATECLOSE;");
5248  static const QString TEMPL_AFTER_TIME_CATEGORY = QLatin1String("TMPLAFTTIME;");
5249  static const QString KMAIL_SERNUM_CATEGORY = QLatin1String("KMAIL:");
5250  static const QString LOG_CATEGORY = QLatin1String("LOG:");
5251 
5252  // KAlarm pre-1.5.0/1.9.9 properties
5253  static const QByteArray KMAIL_ID_PROPERTY("KMAILID"); // X-KDE-KALARM-KMAILID property
5254 
5255  // KAlarm pre-2.6.0 properties
5256  static const QByteArray ARCHIVE_PROPERTY("ARCHIVE"); // X-KDE-KALARM-ARCHIVE property
5257  static const QString ARCHIVE_REMINDER_ONCE_TYPE = QLatin1String("ONCE");
5258  static const QString REMINDER_ONCE_TYPE = QLatin1String("REMINDER_ONCE");
5259  static const QByteArray EMAIL_ID_PROPERTY("EMAILID"); // X-KDE-KALARM-EMAILID property
5260  static const QByteArray SPEAK_PROPERTY("SPEAK"); // X-KDE-KALARM-SPEAK property
5261  static const QByteArray CANCEL_ON_ERROR_PROPERTY("ERRCANCEL");// X-KDE-KALARM-ERRCANCEL property
5262  static const QByteArray DONT_SHOW_ERROR_PROPERTY("ERRNOSHOW");// X-KDE-KALARM-ERRNOSHOW property
5263 
5264  bool adjustSummerTime = false;
5265  if (calendarVersion == -Version(0,5,7))
5266  {
5267  // The calendar file was written by the KDE 3.0.0 version of KAlarm 0.5.7.
5268  // Summer time was ignored when converting to UTC.
5269  calendarVersion = -calendarVersion;
5270  adjustSummerTime = true;
5271  }
5272 
5273  if (calendarVersion >= currentCalendarVersion())
5274  return false;
5275 
5276  kDebug() << "Adjusting version" << calendarVersion;
5277  const bool pre_0_7 = (calendarVersion < Version(0,7,0));
5278  const bool pre_0_9 = (calendarVersion < Version(0,9,0));
5279  const bool pre_0_9_2 = (calendarVersion < Version(0,9,2));
5280  const bool pre_1_1_1 = (calendarVersion < Version(1,1,1));
5281  const bool pre_1_2_1 = (calendarVersion < Version(1,2,1));
5282  const bool pre_1_3_0 = (calendarVersion < Version(1,3,0));
5283  const bool pre_1_3_1 = (calendarVersion < Version(1,3,1));
5284  const bool pre_1_4_14 = (calendarVersion < Version(1,4,14));
5285  const bool pre_1_5_0 = (calendarVersion < Version(1,5,0));
5286  const bool pre_1_9_0 = (calendarVersion < Version(1,9,0));
5287  const bool pre_1_9_2 = (calendarVersion < Version(1,9,2));
5288  const bool pre_1_9_7 = (calendarVersion < Version(1,9,7));
5289  const bool pre_1_9_9 = (calendarVersion < Version(1,9,9));
5290  const bool pre_1_9_10 = (calendarVersion < Version(1,9,10));
5291  const bool pre_2_2_9 = (calendarVersion < Version(2,2,9));
5292  const bool pre_2_3_0 = (calendarVersion < Version(2,3,0));
5293  const bool pre_2_3_2 = (calendarVersion < Version(2,3,2));
5294  const bool pre_2_7_0 = (calendarVersion < Version(2,7,0));
5295  Q_ASSERT(currentCalendarVersion() == Version(2,7,0));
5296 
5297  KTimeZone localZone;
5298  if (pre_1_9_2)
5299  localZone = KSystemTimeZones::local();
5300 
5301  bool converted = false;
5302 #ifndef KALARMCAL_USE_KRESOURCES
5303  const Event::List events = calendar->rawEvents();
5304 #else
5305  const Event::List events = calendar.rawEvents();
5306 #endif
5307  for (int ei = 0, eend = events.count(); ei < eend; ++ei)
5308  {
5309 #ifndef KALARMCAL_USE_KRESOURCES
5310  Event::Ptr event = events[ei];
5311 #else
5312  Event* event = events[ei];
5313 #endif
5314  const Alarm::List alarms = event->alarms();
5315  if (alarms.isEmpty())
5316  continue; // KAlarm isn't interested in events without alarms
5317  event->startUpdates(); // prevent multiple update notifications
5318  const bool readOnly = event->isReadOnly();
5319  if (readOnly)
5320  event->setReadOnly(false);
5321  QStringList cats = event->categories();
5322  bool addLateCancel = false;
5323  QStringList flags;
5324 
5325  if (pre_0_7 && event->allDay())
5326  {
5327  // It's a KAlarm pre-0.7 calendar file.
5328  // Ensure that when the calendar is saved, the alarm time isn't lost.
5329  event->setAllDay(false);
5330  }
5331 
5332  if (pre_0_9)
5333  {
5334  /*
5335  * It's a KAlarm pre-0.9 calendar file.
5336  * All alarms were of type DISPLAY. Instead of the X-KDE-KALARM-TYPE
5337  * alarm property, characteristics were stored as a prefix to the
5338  * alarm DESCRIPTION property, as follows:
5339  * SEQNO;[FLAGS];TYPE:TEXT
5340  * where
5341  * SEQNO = sequence number of alarm within the event
5342  * FLAGS = C for late-cancel, L for repeat-at-login, D for deferral
5343  * TYPE = TEXT or FILE or CMD
5344  * TEXT = message text, file name/URL or command
5345  */
5346  for (int ai = 0, aend = alarms.count(); ai < aend; ++ai)
5347  {
5348 #ifndef KALARMCAL_USE_KRESOURCES
5349  Alarm::Ptr alarm = alarms[ai];
5350 #else
5351  Alarm* alarm = alarms[ai];
5352 #endif
5353  bool atLogin = false;
5354  bool deferral = false;
5355  bool lateCancel = false;
5356  KAAlarm::Action action = KAAlarm::MESSAGE;
5357  QString txt = alarm->text();
5358  const int length = txt.length();
5359  int i = 0;
5360  if (txt[0].isDigit())
5361  {
5362  while (++i < length && txt[i].isDigit()) ;
5363  if (i < length && txt[i++] == SEPARATOR)
5364  {
5365  while (i < length)
5366  {
5367  const QChar ch = txt[i++];
5368  if (ch == SEPARATOR)
5369  break;
5370  if (ch == LATE_CANCEL_CODE)
5371  lateCancel = true;
5372  else if (ch == AT_LOGIN_CODE)
5373  atLogin = true;
5374  else if (ch == DEFERRAL_CODE)
5375  deferral = true;
5376  }
5377  }
5378  else
5379  i = 0; // invalid prefix
5380  }
5381  if (txt.indexOf(TEXT_PREFIX, i) == i)
5382  i += TEXT_PREFIX.length();
5383  else if (txt.indexOf(FILE_PREFIX, i) == i)
5384  {
5385  action = KAAlarm::FILE;
5386  i += FILE_PREFIX.length();
5387  }
5388  else if (txt.indexOf(COMMAND_PREFIX, i) == i)
5389  {
5390  action = KAAlarm::COMMAND;
5391  i += COMMAND_PREFIX.length();
5392  }
5393  else
5394  i = 0;
5395  txt = txt.mid(i);
5396 
5397  QStringList types;
5398  switch (action)
5399  {
5400  case KAAlarm::FILE:
5401  types += Private::FILE_TYPE;
5402  // fall through to MESSAGE
5403  case KAAlarm::MESSAGE:
5404  alarm->setDisplayAlarm(txt);
5405  break;
5406  case KAAlarm::COMMAND:
5407  setProcedureAlarm(alarm, txt);
5408  break;
5409  case KAAlarm::EMAIL: // email alarms were introduced in KAlarm 0.9
5410  case KAAlarm::AUDIO: // audio alarms (with no display) were introduced in KAlarm 2.3.2
5411  break;
5412  }
5413  if (atLogin)
5414  {
5415  types += Private::AT_LOGIN_TYPE;
5416  lateCancel = false;
5417  }
5418  else if (deferral)
5419  types += Private::TIME_DEFERRAL_TYPE;
5420  if (lateCancel)
5421  addLateCancel = true;
5422  if (types.count() > 0)
5423  alarm->setCustomProperty(KACalendar::APPNAME, Private::TYPE_PROPERTY, types.join(","));
5424 
5425  if (pre_0_7 && alarm->repeatCount() > 0 && alarm->snoozeTime().value() > 0)
5426  {
5427  // It's a KAlarm pre-0.7 calendar file.
5428  // Minutely recurrences were stored differently.
5429  Recurrence* recur = event->recurrence();
5430  if (recur && recur->recurs())
5431  {
5432  recur->setMinutely(alarm->snoozeTime().asSeconds() / 60);
5433  recur->setDuration(alarm->repeatCount() + 1);
5434  alarm->setRepeatCount(0);
5435  alarm->setSnoozeTime(0);
5436  }
5437  }
5438 
5439  if (adjustSummerTime)
5440  {
5441  // The calendar file was written by the KDE 3.0.0 version of KAlarm 0.5.7.
5442  // Summer time was ignored when converting to UTC.
5443  KDateTime dt = alarm->time();
5444  const time_t t = dt.toTime_t();
5445  const struct tm* dtm = localtime(&t);
5446  if (dtm->tm_isdst)
5447  {
5448  dt = dt.addSecs(-3600);
5449  alarm->setTime(dt);
5450  }
5451  }
5452  }
5453  }
5454 
5455  if (pre_0_9_2)
5456  {
5457  /*
5458  * It's a KAlarm pre-0.9.2 calendar file.
5459  * For the archive calendar, set the CREATED time to the DTEND value.
5460  * Convert date-only DTSTART to date/time, and add category "DATE".
5461  * Set the DTEND time to the DTSTART time.
5462  * Convert all alarm times to DTSTART offsets.
5463  * For display alarms, convert the first unlabelled category to an
5464  * X-KDE-KALARM-FONTCOLOUR property.
5465  * Convert BEEP category into an audio alarm with no audio file.
5466  */
5467  if (CalEvent::status(event) == CalEvent::ARCHIVED)
5468  event->setCreated(event->dtEnd());
5469  KDateTime start = event->dtStart();
5470  if (event->allDay())
5471  {
5472  event->setAllDay(false);
5473  start.setTime(QTime(0, 0));
5474  flags += Private::DATE_ONLY_FLAG;
5475  }
5476  event->setHasEndDate(false);
5477 
5478  for (int ai = 0, aend = alarms.count(); ai < aend; ++ai)
5479  {
5480 #ifndef KALARMCAL_USE_KRESOURCES
5481  Alarm::Ptr alarm = alarms[ai];
5482 #else
5483  Alarm* alarm = alarms[ai];
5484 #endif
5485  alarm->setStartOffset(start.secsTo(alarm->time()));
5486  }
5487 
5488  if (!cats.isEmpty())
5489  {
5490  for (int ai = 0, aend = alarms.count(); ai < aend; ++ai)
5491  {
5492 #ifndef KALARMCAL_USE_KRESOURCES
5493  Alarm::Ptr alarm = alarms[ai];
5494 #else
5495  Alarm* alarm = alarms[ai];
5496 #endif
5497  if (alarm->type() == Alarm::Display)
5498  alarm->setCustomProperty(KACalendar::APPNAME, Private::FONT_COLOUR_PROPERTY,
5499  QString::fromLatin1("%1;;").arg(cats[0]));
5500  }
5501  cats.removeAt(0);
5502  }
5503 
5504  for (int i = 0, end = cats.count(); i < end; ++i)
5505  {
5506  if (cats[i] == BEEP_CATEGORY)
5507  {
5508  cats.removeAt(i);
5509 
5510 #ifndef KALARMCAL_USE_KRESOURCES
5511  Alarm::Ptr alarm = event->newAlarm();
5512 #else
5513  Alarm* alarm = event->newAlarm();
5514 #endif
5515  alarm->setEnabled(true);
5516  alarm->setAudioAlarm();
5517  KDateTime dt = event->dtStart(); // default
5518 
5519  // Parse and order the alarms to know which one's date/time to use
5520  Private::AlarmMap alarmMap;
5521  Private::readAlarms(event, &alarmMap);
5522  Private::AlarmMap::ConstIterator it = alarmMap.constBegin();
5523  if (it != alarmMap.constEnd())
5524  {
5525  dt = it.value().alarm->time();
5526  break;
5527  }
5528  alarm->setStartOffset(start.secsTo(dt));
5529  break;
5530  }
5531  }
5532  }
5533 
5534  if (pre_1_1_1)
5535  {
5536  /*
5537  * It's a KAlarm pre-1.1.1 calendar file.
5538  * Convert simple LATECANCEL category to LATECANCEL:n where n = minutes late.
5539  */
5540  int i;
5541  while ((i = cats.indexOf(LATE_CANCEL_CAT)) >= 0)
5542  {
5543  cats.removeAt(i);
5544  addLateCancel = true;
5545  }
5546  }
5547 
5548  if (pre_1_2_1)
5549  {
5550  /*
5551  * It's a KAlarm pre-1.2.1 calendar file.
5552  * Convert email display alarms from translated to untranslated header prefixes.
5553  */
5554  for (int ai = 0, aend = alarms.count(); ai < aend; ++ai)
5555  {
5556 #ifndef KALARMCAL_USE_KRESOURCES
5557  Alarm::Ptr alarm = alarms[ai];
5558 #else
5559  Alarm* alarm = alarms[ai];
5560 #endif
5561  if (alarm->type() == Alarm::Display)
5562  {
5563  const QString oldtext = alarm->text();
5564  const QString newtext = AlarmText::toCalendarText(oldtext);
5565  if (oldtext != newtext)
5566  alarm->setDisplayAlarm(newtext);
5567  }
5568  }
5569  }
5570 
5571  if (pre_1_3_0)
5572  {
5573  /*
5574  * It's a KAlarm pre-1.3.0 calendar file.
5575  * Convert simple TMPLDEFTIME category to TMPLAFTTIME:n where n = minutes after.
5576  */
5577  int i;
5578  while ((i = cats.indexOf(TEMPL_DEF_TIME_CAT)) >= 0)
5579  {
5580  cats.removeAt(i);
5581  (flags += Private::TEMPL_AFTER_TIME_FLAG) += QLatin1String("0");
5582  }
5583  }
5584 
5585  if (pre_1_3_1)
5586  {
5587  /*
5588  * It's a KAlarm pre-1.3.1 calendar file.
5589  * Convert simple XTERM category to LOG:xterm:
5590  */
5591  int i;
5592  while ((i = cats.indexOf(EXEC_IN_XTERM_CAT)) >= 0)
5593  {
5594  cats.removeAt(i);
5595  event->setCustomProperty(KACalendar::APPNAME, Private::LOG_PROPERTY, Private::xtermURL);
5596  }
5597  }
5598 
5599  if (pre_1_9_0)
5600  {
5601  /*
5602  * It's a KAlarm pre-1.9 calendar file.
5603  * Add the X-KDE-KALARM-STATUS custom property.
5604  * Convert KAlarm categories to custom fields.
5605  */
5606  CalEvent::setStatus(event, CalEvent::status(event));
5607  for (int i = 0; i < cats.count(); )
5608  {
5609  QString cat = cats[i];
5610  if (cat == DATE_ONLY_CATEGORY)
5611  flags += Private::DATE_ONLY_FLAG;
5612  else if (cat == CONFIRM_ACK_CATEGORY)
5613  flags += Private::CONFIRM_ACK_FLAG;
5614  else if (cat == EMAIL_BCC_CATEGORY)
5615  flags += Private::EMAIL_BCC_FLAG;
5616  else if (cat == KORGANIZER_CATEGORY)
5617  flags += Private::KORGANIZER_FLAG;
5618  else if (cat.startsWith(DEFER_CATEGORY))
5619  (flags += Private::DEFER_FLAG) += cat.mid(DEFER_CATEGORY.length());
5620  else if (cat.startsWith(TEMPL_AFTER_TIME_CATEGORY))
5621  (flags += Private::TEMPL_AFTER_TIME_FLAG) += cat.mid(TEMPL_AFTER_TIME_CATEGORY.length());
5622  else if (cat.startsWith(LATE_CANCEL_CATEGORY))
5623  (flags += Private::LATE_CANCEL_FLAG) += cat.mid(LATE_CANCEL_CATEGORY.length());
5624  else if (cat.startsWith(AUTO_CLOSE_CATEGORY))
5625  (flags += Private::AUTO_CLOSE_FLAG) += cat.mid(AUTO_CLOSE_CATEGORY.length());
5626  else if (cat.startsWith(KMAIL_SERNUM_CATEGORY))
5627  (flags += Private::KMAIL_SERNUM_FLAG) += cat.mid(KMAIL_SERNUM_CATEGORY.length());
5628  else if (cat == ARCHIVE_CATEGORY)
5629  event->setCustomProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY, QLatin1String("0"));
5630  else if (cat.startsWith(ARCHIVE_CATEGORIES))
5631  event->setCustomProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY, cat.mid(ARCHIVE_CATEGORIES.length()));
5632  else if (cat.startsWith(LOG_CATEGORY))
5633  event->setCustomProperty(KACalendar::APPNAME, Private::LOG_PROPERTY, cat.mid(LOG_CATEGORY.length()));
5634  else
5635  {
5636  ++i; // Not a KAlarm category, so leave it
5637  continue;
5638  }
5639  cats.removeAt(i);
5640  }
5641  }
5642 
5643  if (pre_1_9_2)
5644  {
5645  /*
5646  * It's a KAlarm pre-1.9.2 calendar file.
5647  * Convert from clock time to the local system time zone.
5648  */
5649  event->shiftTimes(KDateTime::ClockTime, localZone);
5650  converted = true;
5651  }
5652 
5653  if (addLateCancel)
5654  (flags += Private::LATE_CANCEL_FLAG) += QLatin1String("1");
5655  if (!flags.isEmpty())
5656  event->setCustomProperty(KACalendar::APPNAME, Private::FLAGS_PROPERTY, flags.join(Private::SC));
5657  event->setCategories(cats);
5658 
5659 
5660  if ((pre_1_4_14 || (pre_1_9_7 && !pre_1_9_0))
5661  && event->recurrence() && event->recurrence()->recurs())
5662  {
5663  /*
5664  * It's a KAlarm pre-1.4.14 or KAlarm 1.9 series pre-1.9.7 calendar file.
5665  * For recurring events, convert the main alarm offset to an absolute
5666  * time in the X-KDE-KALARM-NEXTRECUR property, and set main alarm
5667  * offsets to zero, and convert deferral alarm offsets to be relative to
5668  * the next recurrence.
5669  */
5670  const QStringList flags = event->customProperty(KACalendar::APPNAME, Private::FLAGS_PROPERTY).split(Private::SC, QString::SkipEmptyParts);
5671  const bool dateOnly = flags.contains(Private::DATE_ONLY_FLAG);
5672  KDateTime startDateTime = event->dtStart();
5673  if (dateOnly)
5674  startDateTime.setDateOnly(true);
5675  // Convert the main alarm and get the next main trigger time from it
5676  KDateTime nextMainDateTime;
5677  bool mainExpired = true;
5678  for (int i = 0, alend = alarms.count(); i < alend; ++i)
5679  {
5680 #ifndef KALARMCAL_USE_KRESOURCES
5681  Alarm::Ptr alarm = alarms[i];
5682 #else
5683  Alarm* alarm = alarms[i];
5684 #endif
5685  if (!alarm->hasStartOffset())
5686  continue;
5687  // Find whether the alarm triggers at the same time as the main
5688  // alarm, in which case its offset needs to be set to 0. The
5689  // following trigger with the main alarm:
5690  // - Additional audio alarm
5691  // - PRE_ACTION_TYPE
5692  // - POST_ACTION_TYPE
5693  // - DISPLAYING_TYPE
5694  bool mainAlarm = true;
5695  QString property = alarm->customProperty(KACalendar::APPNAME, Private::TYPE_PROPERTY);
5696  QStringList types = property.split(QChar(','), QString::SkipEmptyParts);
5697  for (int t = 0; t < types.count(); ++t)
5698  {
5699  QString type = types[t];
5700  if (type == Private::AT_LOGIN_TYPE
5701  || type == Private::TIME_DEFERRAL_TYPE
5702  || type == Private::DATE_DEFERRAL_TYPE
5703  || type == Private::REMINDER_TYPE
5704  || type == REMINDER_ONCE_TYPE)
5705  {
5706  mainAlarm = false;
5707  break;
5708  }
5709  }
5710  if (mainAlarm)
5711  {
5712  if (mainExpired)
5713  {
5714  // All main alarms are supposed to be at the same time, so
5715  // don't readjust the event's time for subsequent main alarms.
5716  mainExpired = false;
5717  nextMainDateTime = alarm->time();
5718  nextMainDateTime.setDateOnly(dateOnly);
5719  nextMainDateTime = nextMainDateTime.toTimeSpec(startDateTime);
5720  if (nextMainDateTime != startDateTime)
5721  {
5722  QDateTime dt = nextMainDateTime.dateTime();
5723  event->setCustomProperty(KACalendar::APPNAME, Private::NEXT_RECUR_PROPERTY,
5724  dt.toString(dateOnly ? "yyyyMMdd" : "yyyyMMddThhmmss"));
5725  }
5726  }
5727  alarm->setStartOffset(0);
5728  converted = true;
5729  }
5730  }
5731  int adjustment;
5732  if (mainExpired)
5733  {
5734  // It's an expired recurrence.
5735  // Set the alarm offset relative to the first actual occurrence
5736  // (taking account of possible exceptions).
5737  KDateTime dt = event->recurrence()->getNextDateTime(startDateTime.addDays(-1));
5738  dt.setDateOnly(dateOnly);
5739  adjustment = startDateTime.secsTo(dt);
5740  }
5741  else
5742  adjustment = startDateTime.secsTo(nextMainDateTime);
5743  if (adjustment)
5744  {
5745  // Convert deferred alarms
5746  for (int i = 0, alend = alarms.count(); i < alend; ++i)
5747  {
5748 #ifndef KALARMCAL_USE_KRESOURCES
5749  Alarm::Ptr alarm = alarms[i];
5750 #else
5751  Alarm* alarm = alarms[i];
5752 #endif
5753  if (!alarm->hasStartOffset())
5754  continue;
5755  const QString property = alarm->customProperty(KACalendar::APPNAME, Private::TYPE_PROPERTY);
5756  const QStringList types = property.split(QChar(','), QString::SkipEmptyParts);
5757  for (int t = 0; t < types.count(); ++t)
5758  {
5759  const QString type = types[t];
5760  if (type == Private::TIME_DEFERRAL_TYPE
5761  || type == Private::DATE_DEFERRAL_TYPE)
5762  {
5763  alarm->setStartOffset(alarm->startOffset().asSeconds() - adjustment);
5764  converted = true;
5765  break;
5766  }
5767  }
5768  }
5769  }
5770  }
5771 
5772  if (pre_1_5_0 || (pre_1_9_9 && !pre_1_9_0))
5773  {
5774  /*
5775  * It's a KAlarm pre-1.5.0 or KAlarm 1.9 series pre-1.9.9 calendar file.
5776  * Convert email identity names to uoids.
5777  */
5778  for (int i = 0, alend = alarms.count(); i < alend; ++i)
5779  {
5780 #ifndef KALARMCAL_USE_KRESOURCES
5781  Alarm::Ptr alarm = alarms[i];
5782 #else
5783  Alarm* alarm = alarms[i];
5784 #endif
5785  const QString name = alarm->customProperty(KACalendar::APPNAME, KMAIL_ID_PROPERTY);
5786  if (name.isEmpty())
5787  continue;
5788  const uint id = Identities::identityUoid(name);
5789  if (id)
5790  alarm->setCustomProperty(KACalendar::APPNAME, EMAIL_ID_PROPERTY, QString::number(id));
5791  alarm->removeCustomProperty(KACalendar::APPNAME, KMAIL_ID_PROPERTY);
5792  converted = true;
5793  }
5794  }
5795 
5796  if (pre_1_9_10)
5797  {
5798  /*
5799  * It's a KAlarm pre-1.9.10 calendar file.
5800  * Convert simple repetitions without a recurrence, to a recurrence.
5801  */
5802  if (Private::convertRepetition(event))
5803  converted = true;
5804  }
5805 
5806  if (pre_2_2_9 || (pre_2_3_2 && !pre_2_3_0))
5807  {
5808  /*
5809  * It's a KAlarm pre-2.2.9 or KAlarm 2.3 series pre-2.3.2 calendar file.
5810  * Set the time in the calendar for all date-only alarms to 00:00.
5811  */
5812  if (Private::convertStartOfDay(event))
5813  converted = true;
5814  }
5815 
5816  if (pre_2_7_0)
5817  {
5818  /*
5819  * It's a KAlarm pre-2.7.0 calendar file.
5820  * Archive and at-login flags were stored in event's ARCHIVE property when the main alarm had expired.
5821  * Reminder parameters were stored in event's ARCHIVE property when no reminder was pending.
5822  * Negative reminder periods (i.e. alarm offset > 0) were invalid, so convert to 0.
5823  * Now store reminder information in FLAGS property, whether reminder is pending or not.
5824  * Move EMAILID, SPEAK, ERRCANCEL and ERRNOSHOW alarm properties into new FLAGS property.
5825  */
5826  bool flagsValid = false;
5827  QStringList flags;
5828  QString reminder;
5829  bool reminderOnce = false;
5830  const QString prop = event->customProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY);
5831  if (!prop.isEmpty())
5832  {
5833  // Convert the event's ARCHIVE property to parameters in the FLAGS property
5834  flags = event->customProperty(KACalendar::APPNAME, Private::FLAGS_PROPERTY).split(Private::SC, QString::SkipEmptyParts);
5835  flags << Private::ARCHIVE_FLAG;
5836  flagsValid = true;
5837  if (prop != QLatin1String("0")) // "0" was a dummy parameter if no others were present
5838  {
5839  // It's the archive property containing a reminder time and/or repeat-at-login flag.
5840  // This was present when no reminder/at-login alarm was pending.
5841  const QStringList list = prop.split(Private::SC, QString::SkipEmptyParts);
5842  for (int i = 0; i < list.count(); ++i)
5843  {
5844  if (list[i] == Private::AT_LOGIN_TYPE)
5845  flags << Private::AT_LOGIN_TYPE;
5846  else if (list[i] == ARCHIVE_REMINDER_ONCE_TYPE)
5847  reminderOnce = true;
5848  else if (!list[i].isEmpty() && !list[i].startsWith(QChar::fromLatin1('-')))
5849  reminder = list[i];
5850  }
5851  }
5852  event->setCustomProperty(KACalendar::APPNAME, Private::FLAGS_PROPERTY, flags.join(Private::SC));
5853  event->removeCustomProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY);
5854  }
5855 
5856  for (int i = 0, alend = alarms.count(); i < alend; ++i)
5857  {
5858 #ifndef KALARMCAL_USE_KRESOURCES
5859  Alarm::Ptr alarm = alarms[i];
5860 #else
5861  Alarm* alarm = alarms[i];
5862 #endif
5863  // Convert EMAILID, SPEAK, ERRCANCEL, ERRNOSHOW properties
5864  QStringList flags;
5865  QString property = alarm->customProperty(KACalendar::APPNAME, EMAIL_ID_PROPERTY);
5866  if (!property.isEmpty())
5867  {
5868  flags << Private::EMAIL_ID_FLAG << property;
5869  alarm->removeCustomProperty(KACalendar::APPNAME, EMAIL_ID_PROPERTY);
5870  }
5871  if (!alarm->customProperty(KACalendar::APPNAME, SPEAK_PROPERTY).isEmpty())
5872  {
5873  flags << Private::SPEAK_FLAG;
5874  alarm->removeCustomProperty(KACalendar::APPNAME, SPEAK_PROPERTY);
5875  }
5876  if (!alarm->customProperty(KACalendar::APPNAME, CANCEL_ON_ERROR_PROPERTY).isEmpty())
5877  {
5878  flags << Private::CANCEL_ON_ERROR_FLAG;
5879  alarm->removeCustomProperty(KACalendar::APPNAME, CANCEL_ON_ERROR_PROPERTY);
5880  }
5881  if (!alarm->customProperty(KACalendar::APPNAME, DONT_SHOW_ERROR_PROPERTY).isEmpty())
5882  {
5883  flags << Private::DONT_SHOW_ERROR_FLAG;
5884  alarm->removeCustomProperty(KACalendar::APPNAME, DONT_SHOW_ERROR_PROPERTY);
5885  }
5886  if (!flags.isEmpty())
5887  alarm->setCustomProperty(KACalendar::APPNAME, Private::FLAGS_PROPERTY, flags.join(Private::SC));
5888 
5889  // Invalidate negative reminder periods in alarms
5890  if (!alarm->hasStartOffset())
5891  continue;
5892  property = alarm->customProperty(KACalendar::APPNAME, Private::TYPE_PROPERTY);
5893  QStringList types = property.split(QChar::fromLatin1(','), QString::SkipEmptyParts);
5894  const int r = types.indexOf(REMINDER_ONCE_TYPE);
5895  if (r >= 0)
5896  {
5897  // Move reminder-once indicator from the alarm to the event's FLAGS property
5898  types[r] = Private::REMINDER_TYPE;
5899  alarm->setCustomProperty(KACalendar::APPNAME, Private::TYPE_PROPERTY, types.join(QChar::fromLatin1(',')));
5900  reminderOnce = true;
5901  }
5902  if (r >= 0 || types.contains(Private::REMINDER_TYPE))
5903  {
5904  // The alarm is a reminder alarm
5905  const int offset = alarm->startOffset().asSeconds();
5906  if (offset > 0)
5907  {
5908  alarm->setStartOffset(0);
5909  converted = true;
5910  }
5911  else if (offset < 0)
5912  reminder = reminderToString(offset / 60);
5913  }
5914  }
5915  if (!reminder.isEmpty())
5916  {
5917  // Write reminder parameters into the event's FLAGS property
5918  if (!flagsValid)
5919  flags = event->customProperty(KACalendar::APPNAME, Private::FLAGS_PROPERTY).split(Private::SC, QString::SkipEmptyParts);
5920  if (flags.indexOf(Private::REMINDER_TYPE) < 0)
5921  {
5922  flags += Private::REMINDER_TYPE;
5923  if (reminderOnce)
5924  flags += Private::REMINDER_ONCE_FLAG;
5925  flags += reminder;
5926  }
5927  }
5928  }
5929 
5930  if (readOnly)
5931  event->setReadOnly(true);
5932  event->endUpdates(); // finally issue an update notification
5933  }
5934  return converted;
5935 }
5936 
5937 /******************************************************************************
5938 * Set the time for a date-only event to 00:00.
5939 * Reply = true if the event was updated.
5940 */
5941 #ifndef KALARMCAL_USE_KRESOURCES
5942 bool KAEvent::Private::convertStartOfDay(const Event::Ptr& event)
5943 #else
5944 bool KAEvent::Private::convertStartOfDay(Event* event)
5945 #endif
5946 {
5947  bool changed = false;
5948  const QTime midnight(0, 0);
5949  const QStringList flags = event->customProperty(KACalendar::APPNAME, Private::FLAGS_PROPERTY).split(Private::SC, QString::SkipEmptyParts);
5950  if (flags.indexOf(Private::DATE_ONLY_FLAG) >= 0)
5951  {
5952  // It's an untimed event, so fix it
5953  const KDateTime oldDt = event->dtStart();
5954  const int adjustment = oldDt.time().secsTo(midnight);
5955  if (adjustment)
5956  {
5957  event->setDtStart(KDateTime(oldDt.date(), midnight, oldDt.timeSpec()));
5958  int deferralOffset = 0;
5959  AlarmMap alarmMap;
5960  readAlarms(event, &alarmMap);
5961  for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it)
5962  {
5963  const AlarmData& data = it.value();
5964  if (!data.alarm->hasStartOffset())
5965  continue;
5966  if (data.timedDeferral)
5967  {
5968  // Found a timed deferral alarm, so adjust the offset
5969  deferralOffset = data.alarm->startOffset().asSeconds();
5970 #ifndef KALARMCAL_USE_KRESOURCES
5971  const_cast<Alarm*>(data.alarm.data())->setStartOffset(deferralOffset - adjustment);
5972 #else
5973  const_cast<Alarm*>(data.alarm)->setStartOffset(deferralOffset - adjustment);
5974 #endif
5975  }
5976  else if (data.type == AUDIO_ALARM
5977  && data.alarm->startOffset().asSeconds() == deferralOffset)
5978  {
5979  // Audio alarm is set for the same time as the above deferral alarm
5980 #ifndef KALARMCAL_USE_KRESOURCES
5981  const_cast<Alarm*>(data.alarm.data())->setStartOffset(deferralOffset - adjustment);
5982 #else
5983  const_cast<Alarm*>(data.alarm)->setStartOffset(deferralOffset - adjustment);
5984 #endif
5985  }
5986  }
5987  changed = true;
5988  }
5989  }
5990  else
5991  {
5992  // It's a timed event. Fix any untimed alarms.
5993  bool foundDeferral = false;
5994  int deferralOffset = 0;
5995  int newDeferralOffset = 0;
5996  DateTime start;
5997  const KDateTime nextMainDateTime = readDateTime(event, false, start).kDateTime();
5998  AlarmMap alarmMap;
5999  readAlarms(event, &alarmMap);
6000  for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it)
6001  {
6002  const AlarmData& data = it.value();
6003  if (!data.alarm->hasStartOffset())
6004  continue;
6005  if ((data.type & DEFERRED_ALARM) && !data.timedDeferral)
6006  {
6007  // Found a date-only deferral alarm, so adjust its time
6008  KDateTime altime = data.alarm->startOffset().end(nextMainDateTime);
6009  altime.setTime(midnight);
6010  deferralOffset = data.alarm->startOffset().asSeconds();
6011  newDeferralOffset = event->dtStart().secsTo(altime);
6012 #ifndef KALARMCAL_USE_KRESOURCES
6013  const_cast<Alarm*>(data.alarm.data())->setStartOffset(newDeferralOffset);
6014 #else
6015  const_cast<Alarm*>(data.alarm)->setStartOffset(newDeferralOffset);
6016 #endif
6017  foundDeferral = true;
6018  changed = true;
6019  }
6020  else if (foundDeferral
6021  && data.type == AUDIO_ALARM
6022  && data.alarm->startOffset().asSeconds() == deferralOffset)
6023  {
6024  // Audio alarm is set for the same time as the above deferral alarm
6025 #ifndef KALARMCAL_USE_KRESOURCES
6026  const_cast<Alarm*>(data.alarm.data())->setStartOffset(newDeferralOffset);
6027 #else
6028  const_cast<Alarm*>(data.alarm)->setStartOffset(newDeferralOffset);
6029 #endif
6030  changed = true;
6031  }
6032  }
6033  }
6034  return changed;
6035 }
6036 
6037 /******************************************************************************
6038 * Convert simple repetitions in an event without a recurrence, to a
6039 * recurrence. Repetitions which are an exact multiple of 24 hours are converted
6040 * to daily recurrences; else they are converted to minutely recurrences. Note
6041 * that daily and minutely recurrences produce different results when they span
6042 * a daylight saving time change.
6043 * Reply = true if any conversions were done.
6044 */
6045 #ifndef KALARMCAL_USE_KRESOURCES
6046 bool KAEvent::Private::convertRepetition(const Event::Ptr& event)
6047 #else
6048 bool KAEvent::Private::convertRepetition(Event* event)
6049 #endif
6050 {
6051  const Alarm::List alarms = event->alarms();
6052  if (alarms.isEmpty())
6053  return false;
6054  Recurrence* recur = event->recurrence(); // guaranteed to return non-null
6055  if (recur->recurs())
6056  return false;
6057  bool converted = false;
6058  const bool readOnly = event->isReadOnly();
6059  for (int ai = 0, aend = alarms.count(); ai < aend; ++ai)
6060  {
6061 #ifndef KALARMCAL_USE_KRESOURCES
6062  Alarm::Ptr alarm = alarms[ai];
6063 #else
6064  Alarm* alarm = alarms[ai];
6065 #endif
6066  if (alarm->repeatCount() > 0 && alarm->snoozeTime().value() > 0)
6067  {
6068  if (!converted)
6069  {
6070  event->startUpdates(); // prevent multiple update notifications
6071  if (readOnly)
6072  event->setReadOnly(false);
6073  if ((alarm->snoozeTime().asSeconds() % (24*3600)) != 0)
6074  recur->setMinutely(alarm->snoozeTime().asSeconds() / 60);
6075  else
6076  recur->setDaily(alarm->snoozeTime().asDays());
6077  recur->setDuration(alarm->repeatCount() + 1);
6078  converted = true;
6079  }
6080  alarm->setRepeatCount(0);
6081  alarm->setSnoozeTime(0);
6082  }
6083  }
6084  if (converted)
6085  {
6086  if (readOnly)
6087  event->setReadOnly(true);
6088  event->endUpdates(); // finally issue an update notification
6089  }
6090  return converted;
6091 }
6092 
6093 
6094 
6095 /*=============================================================================
6096 = Class KAAlarm
6097 = Corresponds to a single KCal::Alarm instance.
6098 =============================================================================*/
6099 
6100 KAAlarm::KAAlarm()
6101  : d(new Private)
6102 {
6103 }
6104 
6105 KAAlarm::Private::Private()
6106  : mType(INVALID_ALARM),
6107  mNextRepeat(0),
6108  mRepeatAtLogin(false),
6109  mDeferred(false)
6110 {
6111 }
6112 
6113 KAAlarm::KAAlarm(const KAAlarm& other)
6114  : d(new Private(*other.d))
6115 {
6116 }
6117 
6118 KAAlarm::~KAAlarm()
6119 {
6120  delete d;
6121 }
6122 
6123 KAAlarm& KAAlarm::operator=(const KAAlarm& other)
6124 {
6125  if (&other != this)
6126  *d = *other.d;
6127  return *this;
6128 }
6129 
6130 KAAlarm::Action KAAlarm::action() const
6131 {
6132  return d->mActionType;
6133 }
6134 
6135 bool KAAlarm::isValid() const
6136 {
6137  return d->mType != INVALID_ALARM;
6138 }
6139 
6140 KAAlarm::Type KAAlarm::type() const
6141 {
6142  return d->mType;
6143 }
6144 
6145 DateTime KAAlarm::dateTime(bool withRepeats) const
6146 {
6147  return (withRepeats && d->mNextRepeat && d->mRepetition)
6148  ? d->mRepetition.duration(d->mNextRepeat).end(d->mNextMainDateTime.kDateTime())
6149  : d->mNextMainDateTime;
6150 }
6151 
6152 QDate KAAlarm::date() const
6153 {
6154  return d->mNextMainDateTime.date();
6155 }
6156 
6157 QTime KAAlarm::time() const
6158 {
6159  return d->mNextMainDateTime.effectiveTime();
6160 }
6161 
6162 bool KAAlarm::repeatAtLogin() const
6163 {
6164  return d->mRepeatAtLogin;
6165 }
6166 
6167 bool KAAlarm::isReminder() const
6168 {
6169  return d->mType == REMINDER_ALARM;
6170 }
6171 
6172 bool KAAlarm::deferred() const
6173 {
6174  return d->mDeferred;
6175 }
6176 
6177 bool KAAlarm::timedDeferral() const
6178 {
6179  return d->mDeferred && d->mTimedDeferral;
6180 }
6181 
6182 void KAAlarm::setTime(const DateTime& dt)
6183 {
6184  d->mNextMainDateTime = dt;
6185 }
6186 
6187 void KAAlarm::setTime(const KDateTime& dt)
6188 {
6189  d->mNextMainDateTime = dt;
6190 }
6191 
6192 #ifdef KDE_NO_DEBUG_OUTPUT
6193 const char* KAAlarm::debugType(Type) { return ""; }
6194 #else
6195 const char* KAAlarm::debugType(Type type)
6196 {
6197  switch (type)
6198  {
6199  case MAIN_ALARM: return "MAIN";
6200  case REMINDER_ALARM: return "REMINDER";
6201  case DEFERRED_ALARM: return "DEFERRED";
6202  case DEFERRED_REMINDER_ALARM: return "DEFERRED_REMINDER";
6203  case AT_LOGIN_ALARM: return "LOGIN";
6204  case DISPLAYING_ALARM: return "DISPLAYING";
6205  default: return "INVALID";
6206  }
6207 }
6208 #endif
6209 
6210 
6211 /*=============================================================================
6212 = Class EmailAddressList
6213 =============================================================================*/
6214 
6215 /******************************************************************************
6216 * Sets the list of email addresses, removing any empty addresses.
6217 * Reply = false if empty addresses were found.
6218 */
6219 #ifndef KALARMCAL_USE_KRESOURCES
6220 EmailAddressList& EmailAddressList::operator=(const Person::List& addresses)
6221 #else
6222 EmailAddressList& EmailAddressList::operator=(const QList<Person>& addresses)
6223 #endif
6224 {
6225  clear();
6226  for (int p = 0, end = addresses.count(); p < end; ++p)
6227  {
6228 #ifndef KALARMCAL_USE_KRESOURCES
6229  if (!addresses[p]->email().isEmpty())
6230 #else
6231  if (!addresses[p].email().isEmpty())
6232 #endif
6233  append(addresses[p]);
6234  }
6235  return *this;
6236 }
6237 
6238 /******************************************************************************
6239 * Return the email address list as a string list of email addresses.
6240 */
6241 EmailAddressList::operator QStringList() const
6242 {
6243  QStringList list;
6244  for (int p = 0, end = count(); p < end; ++p)
6245  list += address(p);
6246  return list;
6247 }
6248 
6249 /******************************************************************************
6250 * Return the email address list as a string, each address being delimited by
6251 * the specified separator string.
6252 */
6253 QString EmailAddressList::join(const QString& separator) const
6254 {
6255  QString result;
6256  bool first = true;
6257  for (int p = 0, end = count(); p < end; ++p)
6258  {
6259  if (first)
6260  first = false;
6261  else
6262  result += separator;
6263  result += address(p);
6264  }
6265  return result;
6266 }
6267 
6268 /******************************************************************************
6269 * Convert one item into an email address, including name.
6270 */
6271 QString EmailAddressList::address(int index) const
6272 {
6273  if (index < 0 || index > count())
6274  return QString();
6275  QString result;
6276  bool quote = false;
6277 #ifndef KALARMCAL_USE_KRESOURCES
6278  const Person::Ptr person = (*this)[index];
6279  const QString name = person->name();
6280 #else
6281  const Person person = (*this)[index];
6282  const QString name = person.name();
6283 #endif
6284  if (!name.isEmpty())
6285  {
6286  // Need to enclose the name in quotes if it has any special characters
6287  for (int i = 0, len = name.length(); i < len; ++i)
6288  {
6289  const QChar ch = name[i];
6290  if (!ch.isLetterOrNumber())
6291  {
6292  quote = true;
6293  result += '\"';
6294  break;
6295  }
6296  }
6297 #ifndef KALARMCAL_USE_KRESOURCES
6298  result += (*this)[index]->name();
6299 #else
6300  result += (*this)[index].name();
6301 #endif
6302  result += (quote ? "\" <" : " <");
6303  quote = true; // need angle brackets round email address
6304  }
6305 
6306 #ifndef KALARMCAL_USE_KRESOURCES
6307  result += person->email();
6308 #else
6309  result += person.email();
6310 #endif
6311  if (quote)
6312  result += '>';
6313  return result;
6314 }
6315 
6316 /******************************************************************************
6317 * Return a list of the pure email addresses, excluding names.
6318 */
6319 QStringList EmailAddressList::pureAddresses() const
6320 {
6321  QStringList list;
6322  for (int p = 0, end = count(); p < end; ++p)
6323 #ifndef KALARMCAL_USE_KRESOURCES
6324  list += at(p)->email();
6325 #else
6326  list += at(p).email();
6327 #endif
6328  return list;
6329 }
6330 
6331 /******************************************************************************
6332 * Return a list of the pure email addresses, excluding names, as a string.
6333 */
6334 QString EmailAddressList::pureAddresses(const QString& separator) const
6335 {
6336  QString result;
6337  bool first = true;
6338  for (int p = 0, end = count(); p < end; ++p)
6339  {
6340  if (first)
6341  first = false;
6342  else
6343  result += separator;
6344 #ifndef KALARMCAL_USE_KRESOURCES
6345  result += at(p)->email();
6346 #else
6347  result += at(p).email();
6348 #endif
6349  }
6350  return result;
6351 }
6352 
6353 
6354 /*=============================================================================
6355 = Static functions
6356 =============================================================================*/
6357 
6358 /******************************************************************************
6359 * Set the specified alarm to be a procedure alarm with the given command line.
6360 * The command line is first split into its program file and arguments before
6361 * initialising the alarm.
6362 */
6363 #ifndef KALARMCAL_USE_KRESOURCES
6364 static void setProcedureAlarm(const Alarm::Ptr& alarm, const QString& commandLine)
6365 #else
6366 static void setProcedureAlarm(Alarm* alarm, const QString& commandLine)
6367 #endif
6368 {
6369  QString command;
6370  QString arguments;
6371  QChar quoteChar;
6372  bool quoted = false;
6373  const uint posMax = commandLine.length();
6374  uint pos;
6375  for (pos = 0; pos < posMax; ++pos)
6376  {
6377  const QChar ch = commandLine[pos];
6378  if (quoted)
6379  {
6380  if (ch == quoteChar)
6381  {
6382  ++pos; // omit the quote character
6383  break;
6384  }
6385  command += ch;
6386  }
6387  else
6388  {
6389  bool done = false;
6390  switch (ch.toLatin1())
6391  {
6392  case ' ':
6393  case ';':
6394  case '|':
6395  case '<':
6396  case '>':
6397  done = !command.isEmpty();
6398  break;
6399  case '\'':
6400  case '"':
6401  if (command.isEmpty())
6402  {
6403  // Start of a quoted string. Omit the quote character.
6404  quoted = true;
6405  quoteChar = ch;
6406  break;
6407  }
6408  // fall through to default
6409  default:
6410  command += ch;
6411  break;
6412  }
6413  if (done)
6414  break;
6415  }
6416  }
6417 
6418  // Skip any spaces after the command
6419  for ( ; pos < posMax && commandLine[pos] == QLatin1Char(' '); ++pos) ;
6420  arguments = commandLine.mid(pos);
6421 
6422  alarm->setProcedureAlarm(command, arguments);
6423 }
6424 
6425 /******************************************************************************
6426 * Converts a reminder interval into a parameter string for the
6427 * X-KDE-KALARM-FLAGS property.
6428 */
6429 QString reminderToString(int minutes)
6430 {
6431  char unit = 'M';
6432  int count = abs(minutes);
6433  if (count % 1440 == 0)
6434  {
6435  unit = 'D';
6436  count /= 1440;
6437  }
6438  else if (count % 60 == 0)
6439  {
6440  unit = 'H';
6441  count /= 60;
6442  }
6443  if (minutes < 0)
6444  count = -count;
6445  return QString("%1%2").arg(count).arg(unit);
6446 }
6447 
6448 } // namespace KAlarmCal
6449 
6450 // vim: et sw=4:
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jul 13 2013 01:30:15 by doxygen 1.8.3.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KAlarm Library

Skip menu "KAlarm Library"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Related Pages

kdepimlibs-4.10.5 API Reference

Skip menu "kdepimlibs-4.10.5 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal