diff --git a/DESIGN.html b/DESIGN.html index 456c423..c960d82 100644 --- a/DESIGN.html +++ b/DESIGN.html @@ -1,164 +1,164 @@ KAlarmCal Design Notes

KAlarmCal Design Notes

The terms "alarm" and "event" are often used interchangeably in the context of KAlarm (including in source code comments). Technically speaking, a KAlarm "alarm" is really an event which contains one or more alarms. Each event is held in a KAEvent object and is stored as an iCalendar VEVENT. Each event normally contains the "main" alarm together with other subsidiary alarms; subsidiary alarms are used to represent reminders, deferrals, sounds, at-login alarms, pre-alarm actions and post-alarm actions. However, if the "main" alarm has expired but a subsidiary alarm is still due (e.g. a deferral), the event may no longer contain the "main" alarm. Individual alarms in the event may be fetched as KAAlarm objects.

Repeat-at-login events do not allow a reminder before the alarm, late-cancellation, or copying to KOrganizer, since these are incompatible with an alarm with no fixed trigger time.

The limit for deferring an alarm is as follows. Note that reminders are not triggered for sub-repetitions, just recurrences.
Recurs?Sub-
repetition?
Reminder
type
Deferral limit
-
  • No limit
  • Before
  • Main alarm: no limit
  • Reminder: before main alarm
  • After
  • Main alarm: no limit
  • Reminder: no limit
  • Y-
  • Last recurrence: no limit
  • Other recurrences: before next recurrence
  • YBefore
  • Main alarm last recurrence: no limit
  • Main alarm other recurrences: before next reminder
  • YAfter
  • Main alarm last recurrence: no limit
  • Main alarm other recurrences: before next recurrence
  • Reminder last recurrence: no limit
  • Reminder other recurrences: before next recurrence
  • YY-
  • Last sub-repetition of last recurrence: no limit
  • Other occurrences: before next recurrence or sub-repetition
  • YYBefore
  • Main alarm last sub-repetition of last recurrence: no limit
  • Main alarm other occurrences: before next recurrence or sub-repetition or reminder
  • Reminder: before next recurrence
  • YYAfter
  • Main alarm last sub-repetition of last recurrence: no limit
  • Main alarm other occurrences: before next recurrence or sub-repetition
  • Reminder: before next sub-repetition
  • Calendar Storage

    KAlarm uses the iCalendar format defined in RFC2445.

    The DTSTART property is always set as a date/time value, never a date-only value; a date-only event is indicated by the DATE parameter in the X-KDE-KALARM-FLAGS property. This is necessary for two reasons: 1) to allow a time zone to be specified for a date-only event; 2) KAlarm allows the trigger time to float within the 24-hour period defined by the start-of-day time (which is user-dependent and therefore can't be written into the calendar) rather than midnight to midnight, and there is no RFC2445 conformant way to specify this.

    The following VALARM components are stored within a VEVENT:
    Alarm typeACTION
    property
    Description
    Main alarmanyThe main alarm. This is always present unless it is a recurring alarm and the last recurrence has already triggered, but a subsidiary alarm is still pending. Possible pending alarms are either sub-repetitions, a deferral, or a reminder after the main alarm.
    At-login alarmanyIf the event is configured to trigger each time KAlarm starts up (normally at each login), this alarm has a trigger time in the past, which ensures that it always triggers at startup.
    Reminder alarmDISPLAYIf a reminder is configured, and it is due before the next or only recurrence of the main alarm, this alarm triggers the reminder.
    Deferral alarmDISPLAYIf the user has deferred an alarm (either the main alarm or a reminder), this alarm triggers at the deferral time.
    Audio alarmAUDIOIf it is a display event, and any type of sound is configured (beep, speaking or audio file), this alarm is set to trigger at the same time as the next due alarm (main, at-login, reminder, or deferral).
    Special values of REPEAT may be used to specify that an audio file should be played repeatedly in a loop until the user clicks 'stop': see Audio file loop below.
    Pre-alarm action alarmPROCEDUREIf a pre-alarm action is configured, this alarm is set to trigger at the same time as the next due alarm.
    Post‑alarm action alarmPROCEDUREIf a post-alarm action is configured, this alarm is set to trigger at the same time as the next due alarm.
    Displaying alarmDISPLAYIf an alarm is currently being displayed, this alarm held in the displaying calendar contains a copy of the main alarm, plus other information necessary to redisplay the alarm window after a logout or crash.

    Alarm trigger times are always set as an offset from the next or current recurrence of the main alarm as set in DTSTART (for a non-recurring alarm) or in the X-KDE-KALARM-NEXTRECUR property (for a recurring alarm). An offset is used because RFC2445 stipulates that if the TRIGGER property value is an absolute time, it must be a UTC date/time value, which is in general not suitable. Note that the main alarm's trigger offset must always be zero, because if it was non-zero, exception dates and rules would not work since they apply to the event time, not the alarm time.

    Custom properties

    Compliant with the iCalendar specification, KAlarm defines a number of custom fields prefixed by X-KDE-KALARM- for use in the calendar. These are listed here, together with their possible parameters. All properties are optional unless stated to be mandatory.

    Note that in the iCalendar format, the property name is separated from its parameter(s) by a semi-colon. Multiple parameters are also semi-colon separated.

    - +
    Calendar-level custom properties
    The following custom properties are used within a VCALENDAR element.
    Property    ParameterDescription
    X-KDE-KALARM-VERSIONMandatory. The single parameter contains the KAlarm calendar format version used to write this calendar. Note that this version may be older than the actual KAlarm application version; the calendar format version is only updated when the calendar format changes.
    versionKAlarm calendar format version, in the format n.n.n . E.g. 2.3.10
    Event-level custom properties
    The following custom properties are used within a VEVENT element.
    PropertyParameterDescription
    X-KDE-KALARM-TYPEMandatory. The parameter holds the event's type, in one of the following two forms:
    typeThe event's type, one of ACTIVE, ARCHIVED, TEMPLATE, DISPLAYING
    DISPLAYING;resource[;DEFER][;EDIT]Specifies the event's type as DISPLAYING. The parameter's components are:
    resourceThe saved Akonadi collection ID, or KResources resource ID. Note that this is not the collection/resource which the event is in.
    DEFERFor an event in the displaying calendar, indicates that the Defer button should be shown in the alarm window. Optional.
    EDITFor an event in the displaying calendar, indicates that the Edit button should be shown in the alarm window. Optional.
    X-KDE-KALARM-FLAGSMultiple parameters specify various properties of the event
    LOCALThe event time is specified using the local system time zone, and any TZID specifier in DTSTART is to be ignored. Note that this is used because the PIM libraries set the current local time zone as the TZID value when storing local times in the calendar.
    DATEThe event time is date-only, i.e. its time is ignored and it triggers at the first opportunity on the specified date (after the start-of-day time configured by the user). Note that this is used instead of specifying a start date (DTSTART) without a time.
    ACKCONFFor a display alarm, when the user acknowledges the alarm (i.e. closes the alarm window), a confirmation prompt will be output
    KORGThe event was copied as an alarm to KOrganizer
    EXHOLIDAYSThe alarm will not trigger on holidays (as defined for the holiday region configured by the user)
    WORKTIMEThe alarm will not trigger during working hours on working days (as configured by the user)
    DEFER;intervalRecords the default deferral parameters for the alarm, used when the Defer dialogue is displayed. interval holds the deferral interval in minutes, with an optional D suffix to specify a date-only deferral. E.g. DEFER;1440D = 1 day, date-only
    LATECANCEL;intervalHow late the alarm can trigger (in minutes) before it will be cancelled; default = 1 minute
    LATECLOSE;intervalFor a display alarm, how long after the trigger time (in minutes) until the alarm window is automatically closed; default = 1 minute. It will also be cancelled if it triggers after this time.
    TMPLAFTTIME;intervalFor a template alarm, holds the value (in minutes) of the "After time" option
    KMAIL;sernumThe KMail serial number of the email whose text forms the alarm message. The alarm message must be in the form From: ...\nTo: ...\n[Cc: ...\n]Date: ...\nSubject: ...
    KMAIL;itemidThe Akonadi Item ID of the email whose text forms the alarm message. The alarm message must be in the form From: ...\nTo: ...\n[Cc: ...\n]Date: ...\nSubject: ...
    BCCFor an email alarm, the email will be blind copied to the user
    REMINDER;[ONCE;]intervalThe reminder interval for the alarm: < 0 for a reminder before the main alarm, > 0 for a reminder after the main alarm. A suffix indicates the time units: M = minutes, H = hours, D = days. E.g. -12M. If ONCE is specified, this indicates that the reminder should be shown only for the first recurrence.
    LOGINFor an active alarm, when its main alarm has expired, records that the original alarm was triggered each time KAlarm started up (normally at each login)
    ARCHIVEFor an active alarm, indicates that the alarm has triggered at least once and should be archived when it expires or is deleted
    X-KDE-KALARM-NEXTRECURThe single parameter records the next due recurrence date/time of the main alarm or, if sub-repetitions are still pending after the latest recurrence, the date/time of that recurrence
    date-timeThe date and optional time, in the format YYYYMMDDTHHMMSS for a date/time alarm, or YYYYMMDD for a date-only alarm. E.g. 19990131T120000
    X-KDE-KALARM-REPEATUsed for an active alarm when its main alarm has expired (e.g. when the last occurrence of the alarm has been deferred), holds the alarm's sub-repetition information. (Normally, this is held in the main alarm.)
    interval:countSub-repetition interval (in minutes) and count
    X-KDE-KALARM-LOGThe single parameter specifies where command alarm output should go; default = discard
    xterm:Display command output in a terminal window
    display:Display command output in an alarm window
    filenameStore command output in the specified file
    Alarm-level custom properties
    The following custom properties are used within a VALARM element.
    PropertyParameterDescription
    X-KDE-KALARM-TYPEMultiple parameters hold information about the alarm's type. If this property is missing, the alarm is the "main" alarm for the event.
    LOGINThe alarm should be triggered each time KAlarm starts up (normally at each login)
    FILEFor a display alarm, indicates that the alarm text holds the name of a file whose contents should form the alarm message
    REMINDERThe alarm is a subsidiary reminder alarm. If combined with DEFERRAL or DATE_DEFERRAL, the alarm is a deferred reminder alarm.
    DEFERRALThe alarm is a subsidiary timed deferral alarm
    DATE_DEFERRALThe alarm is a subsidiary date-only deferral alarm
    PREThe alarm is a subsidiary pre-alarm action command, which is executed immediately before the main alarm
    POSTThe alarm is a subsidiary post-alarm action command, which is executed immediately after the user acknowledges the main display alarm
    SOUNDREPEAT,pauseFor an audio alarm, indicates that the sound file should be played repeatedly in a loop until the user clicks 'stop', each time it triggers. The argument is the pause in seconds between repetitions. This is used only for "main" alarms: see Audio file loop below.
    DISPLAYINGThe alarm is currently being displayed, i.e. in the displaying calendar
    X-KDE-KALARM-FLAGSMultiple parameters specify various properties of the alarm
    HIDEFor an alarm of type REMINDER, indicates that it is suppressed by a deferral alarm whose trigger time is later than this alarm. Allows the reminder alarm to be reinstated if the deferral is subsequently cancelled.
    SPEAKFor an audio alarm in an event whose main alarm is a display or command alarm, specifies that the alarm text should be spoken
    EXECDEFERFor a command alarm containing a pre-alarm action, indicates that the pre-alarm action should be executed when a deferred alarm triggers. Normally, it is executed only when the alarm proper triggers.
    ERRCANCELFor a command alarm containing a pre-alarm action, indicates that the alarm should be cancelled if the pre-alarm action fails
    ERRNOSHOWFor a command alarm containing a pre-alarm action, indicates that there should be no error notification if the pre-alarm action fails
    EMAILID;uoidFor an email alarm, holds the email ID (the KDE email UOID) to use in the 'From' field.
    X-KDE-KALARM-NEXTREPEATThe single parameter holds the repetition count of the next due sub-repetition: 0 = the main recurrence, 1 = the first sub-repetition, etc.
    countRepetition number
    X-KDE-KALARM-FONTCOLORFor a display alarm, holds the colours to use in the alarm message window
    [background][;foreground]The optional background colour (default = white) and optional foreground colour (default = black)
    X-KDE-KALARM-VOLUMEHolds volume settings for an audio alarm
    [volume][;fadevolume;fadeinterval]Contains the optional volume (floating point, range 0 - 1), and optionally the fade volume (floating point, range 0 - 1) and fade interval in seconds. E.g. 0.75 to set volume with no fade, ;0.3;10 for default volume with fade, 0.75;0.3;10 to specify volume and fade

    Custom status field values

    The following custom parameter value is used in the STATUS property in VEVENT:

    Other

    Audio file loop

    An audio alarm (ACTION:AUDIO) can specify that each time the alarm triggers, the audio file should be played repeatedly in a loop until the user presses 'stop'. This is independent of the overall event recurrence or sub-repetition, and is indicated as follows. diff --git a/autotests/kaeventtest.cpp b/autotests/kaeventtest.cpp index 3499bac..5b4a2ad 100644 --- a/autotests/kaeventtest.cpp +++ b/autotests/kaeventtest.cpp @@ -1,1277 +1,1277 @@ /* This file is part of kalarmcal library, which provides access to KAlarm calendar data. - Copyright (c) 2018 David Jarvie + Copyright © 2018,2019 David Jarvie This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kaeventtest.h" #include "kaevent.h" using namespace KAlarmCal; #include #include using namespace KCalCore; #include #include using namespace KHolidays; #include #include QTEST_GUILESS_MAIN(KAEventTest) namespace { const QString SC = QStringLiteral(";"); } ////////////////////////////////////////////////////// // Constructors and basic property information methods ////////////////////////////////////////////////////// void KAEventTest::constructors() { const KADateTime dt(QDate(2010,5,13), QTime(3, 45, 0), QTimeZone("Europe/London")); const QString text(QStringLiteral("message")); const QColor fgColour(130, 110, 240); const QColor bgColour(20, 70, 140); const QFont font(QStringLiteral("Helvetica"), 10, QFont::Bold, true); const KAEvent::Flags flags(KAEvent::CONFIRM_ACK | KAEvent::AUTO_CLOSE); { // Display alarm KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::MESSAGE, 3, flags); QCOMPARE(event.startDateTime(), DateTime(dt)); QCOMPARE(event.cleanText(), text); QCOMPARE(event.message(), text); QCOMPARE(event.displayMessage(), text); QVERIFY(event.fileName().isEmpty()); QVERIFY(event.command().isEmpty()); QVERIFY(event.emailMessage().isEmpty()); QVERIFY(event.audioFile().isEmpty()); QCOMPARE(event.bgColour(), bgColour); QCOMPARE(event.fgColour(), fgColour); QCOMPARE(event.font(), font); QCOMPARE(event.flags(), flags); QCOMPARE(event.actionTypes(), KAEvent::ACT_DISPLAY); QCOMPARE(event.actionSubType(), KAEvent::MESSAGE); QCOMPARE(event.lateCancel(), 3); QVERIFY(!event.isReadOnly()); } { // Display file alarm KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::FILE, 3, flags); QCOMPARE(event.startDateTime(), DateTime(dt)); QCOMPARE(event.cleanText(), text); QVERIFY(event.message().isEmpty()); QVERIFY(event.displayMessage().isEmpty()); QCOMPARE(event.fileName(), text); QVERIFY(event.command().isEmpty()); QVERIFY(event.emailMessage().isEmpty()); QVERIFY(event.audioFile().isEmpty()); QCOMPARE(event.bgColour(), bgColour); QCOMPARE(event.fgColour(), fgColour); QCOMPARE(event.font(), font); QCOMPARE(event.flags(), flags); QCOMPARE(event.actionTypes(), KAEvent::ACT_DISPLAY); QCOMPARE(event.actionSubType(), KAEvent::FILE); QCOMPARE(event.lateCancel(), 3); QVERIFY(!event.isReadOnly()); } { // Command alarm KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::COMMAND, 3, flags); QCOMPARE(event.startDateTime(), DateTime(dt)); QCOMPARE(event.cleanText(), text); QVERIFY(event.message().isEmpty()); QVERIFY(event.displayMessage().isEmpty()); QVERIFY(event.fileName().isEmpty()); QCOMPARE(event.command(), text); QVERIFY(event.emailMessage().isEmpty()); QVERIFY(event.audioFile().isEmpty()); QCOMPARE(event.flags(), flags); QCOMPARE(event.actionTypes(), KAEvent::ACT_COMMAND); QCOMPARE(event.actionSubType(), KAEvent::COMMAND); QCOMPARE(event.lateCancel(), 3); QVERIFY(!event.isReadOnly()); } { // Email alarm KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::EMAIL, 3, flags); QCOMPARE(event.startDateTime(), DateTime(dt)); QCOMPARE(event.cleanText(), text); QCOMPARE(event.message(), text); QVERIFY(event.displayMessage().isEmpty()); QVERIFY(event.fileName().isEmpty()); QVERIFY(event.command().isEmpty()); QCOMPARE(event.emailMessage(), text); QVERIFY(event.audioFile().isEmpty()); QCOMPARE(event.flags(), flags); QCOMPARE(event.actionTypes(), KAEvent::ACT_EMAIL); QCOMPARE(event.actionSubType(), KAEvent::EMAIL); QCOMPARE(event.lateCancel(), 3); QVERIFY(!event.isReadOnly()); } { // Audio alarm KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::AUDIO, 3, flags); QCOMPARE(event.startDateTime(), DateTime(dt)); QVERIFY(event.cleanText().isEmpty()); QVERIFY(event.message().isEmpty()); QVERIFY(event.displayMessage().isEmpty()); QVERIFY(event.fileName().isEmpty()); QVERIFY(event.command().isEmpty()); QVERIFY(event.emailMessage().isEmpty()); QCOMPARE(event.audioFile(), text); QCOMPARE(event.flags(), flags); QCOMPARE(event.actionTypes(), KAEvent::ACT_AUDIO); QCOMPARE(event.actionSubType(), KAEvent::AUDIO); QCOMPARE(event.lateCancel(), 3); QVERIFY(!event.isReadOnly()); } // This tests the basic KCalCore::Event properties. // Custom properties are tested later. const QDateTime createdDt(QDate(2009,4,13), QTime(11,14,0), QTimeZone("UTC")); const QString uid(QStringLiteral("fd45-77398a2")); { // Display alarm Event::Ptr kcalevent(new Event); kcalevent->setCreated(createdDt); kcalevent->setDtStart(dt.qDateTime()); kcalevent->setSummary(text); kcalevent->setUid(uid); kcalevent->setRevision(12); kcalevent->setReadOnly(false); Alarm::Ptr kcalalarm = kcalevent->newAlarm(); kcalalarm->setDisplayAlarm(text); KAEvent event(kcalevent); QCOMPARE(event.createdDateTime().qDateTime(), createdDt); QCOMPARE(event.startDateTime(), DateTime(dt)); QCOMPARE(event.cleanText(), text); QCOMPARE(event.message(), text); QCOMPARE(event.displayMessage(), text); QVERIFY(event.fileName().isEmpty()); QVERIFY(event.command().isEmpty()); QVERIFY(event.emailMessage().isEmpty()); QVERIFY(event.audioFile().isEmpty()); QCOMPARE(event.actionTypes(), KAEvent::ACT_DISPLAY); QCOMPARE(event.actionSubType(), KAEvent::MESSAGE); QVERIFY(!event.isReadOnly()); QCOMPARE(event.id(), uid); QCOMPARE(event.revision(), 12); } { // Display file alarm Event::Ptr kcalevent(new Event); kcalevent->setCreated(createdDt); kcalevent->setDtStart(dt.qDateTime()); kcalevent->setSummary(text); kcalevent->setUid(uid); kcalevent->setRevision(12); kcalevent->setReadOnly(false); Alarm::Ptr kcalalarm = kcalevent->newAlarm(); kcalalarm->setDisplayAlarm(text); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("FILE")); KAEvent event(kcalevent); QCOMPARE(event.createdDateTime().qDateTime(), createdDt); QCOMPARE(event.startDateTime(), DateTime(dt)); QCOMPARE(event.cleanText(), text); QVERIFY(event.message().isEmpty()); QVERIFY(event.displayMessage().isEmpty()); QCOMPARE(event.fileName(), text); QVERIFY(event.command().isEmpty()); QVERIFY(event.emailMessage().isEmpty()); QVERIFY(event.audioFile().isEmpty()); QCOMPARE(event.actionTypes(), KAEvent::ACT_DISPLAY); QCOMPARE(event.actionSubType(), KAEvent::FILE); QVERIFY(!event.isReadOnly()); QCOMPARE(event.id(), uid); QCOMPARE(event.revision(), 12); } { // Command alarm const QString args(QStringLiteral("-x anargument")); const QString cmdline(text + QStringLiteral(" ") + args); Event::Ptr kcalevent(new Event); kcalevent->setCreated(createdDt); kcalevent->setDtStart(dt.qDateTime()); kcalevent->setSummary(text); kcalevent->setUid(uid); kcalevent->setRevision(12); kcalevent->setReadOnly(false); Alarm::Ptr kcalalarm = kcalevent->newAlarm(); kcalalarm->setProcedureAlarm(text, args); KAEvent event(kcalevent); QCOMPARE(event.createdDateTime().qDateTime(), createdDt); QCOMPARE(event.startDateTime(), DateTime(dt)); QCOMPARE(event.cleanText(), cmdline); QVERIFY(event.message().isEmpty()); QVERIFY(event.displayMessage().isEmpty()); QVERIFY(event.fileName().isEmpty()); QCOMPARE(event.command(), cmdline); QVERIFY(event.emailMessage().isEmpty()); QVERIFY(event.audioFile().isEmpty()); QCOMPARE(event.actionTypes(), KAEvent::ACT_COMMAND); QCOMPARE(event.actionSubType(), KAEvent::COMMAND); QVERIFY(!event.isReadOnly()); QCOMPARE(event.id(), uid); QCOMPARE(event.revision(), 12); } { // Email alarm const QString subject(QStringLiteral("Subject 1")); const Person addressee{QStringLiteral("Fred"), QStringLiteral("fred@freddy.com")}; const Person::List addressees{addressee}; const QStringList attachments{QStringLiteral("/tmp/xyz"), QStringLiteral("/home/fred/attch.p")}; Event::Ptr kcalevent(new Event); kcalevent->setCreated(createdDt); kcalevent->setDtStart(dt.qDateTime()); kcalevent->setSummary(text); kcalevent->setUid(uid); kcalevent->setRevision(12); kcalevent->setReadOnly(false); Alarm::Ptr kcalalarm = kcalevent->newAlarm(); kcalalarm->setEmailAlarm(subject, text, addressees, attachments); KAEvent event(kcalevent); QCOMPARE(event.createdDateTime().qDateTime(), createdDt); QCOMPARE(event.startDateTime(), DateTime(dt)); QCOMPARE(event.cleanText(), text); QCOMPARE(event.message(), text); QVERIFY(event.displayMessage().isEmpty()); QVERIFY(event.fileName().isEmpty()); QVERIFY(event.command().isEmpty()); QCOMPARE(event.emailMessage(), text); QVERIFY(event.audioFile().isEmpty()); QCOMPARE(event.actionTypes(), KAEvent::ACT_EMAIL); QCOMPARE(event.actionSubType(), KAEvent::EMAIL); QVERIFY(!event.isReadOnly()); QCOMPARE(event.id(), uid); QCOMPARE(event.revision(), 12); QCOMPARE(event.emailAddresses(), QStringList{QStringLiteral("Fred ")}); QCOMPARE(event.emailSubject(), subject); QCOMPARE(event.emailAttachments(), attachments); } { // Audio alarm Event::Ptr kcalevent(new Event); kcalevent->setCreated(createdDt); kcalevent->setDtStart(dt.qDateTime()); kcalevent->setUid(uid); kcalevent->setRevision(12); kcalevent->setReadOnly(true); Alarm::Ptr kcalalarm = kcalevent->newAlarm(); kcalalarm->setAudioAlarm(text); KAEvent event(kcalevent); QCOMPARE(event.createdDateTime().qDateTime(), createdDt); QCOMPARE(event.startDateTime(), DateTime(dt)); QCOMPARE(event.cleanText(), text); QVERIFY(event.message().isEmpty()); QVERIFY(event.displayMessage().isEmpty()); QVERIFY(event.fileName().isEmpty()); QVERIFY(event.command().isEmpty()); QVERIFY(event.emailMessage().isEmpty()); QCOMPARE(event.audioFile(), text); QCOMPARE(event.actionTypes(), KAEvent::ACT_AUDIO); QCOMPARE(event.actionSubType(), KAEvent::AUDIO); QVERIFY(event.isReadOnly()); QCOMPARE(event.id(), uid); QCOMPARE(event.revision(), 12); } } void KAEventTest::flags() { const KADateTime dt(QDate(2010,5,13), QTime(3, 45, 0), QTimeZone("Europe/London")); const QString text(QStringLiteral("message")); const QColor fgColour(130, 110, 240); const QColor bgColour(20, 70, 140); const QFont font(QStringLiteral("Helvetica"), 10, QFont::Bold, true); { const KAEvent::Flags flags(KAEvent::BEEP | KAEvent::DEFAULT_FONT); KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::MESSAGE, 3, flags); QCOMPARE(event.flags(), flags); QVERIFY(!event.startDateTime().isDateOnly()); QVERIFY(!event.repeatAtLogin()); QVERIFY(event.enabled()); QVERIFY(event.beep()); QVERIFY(event.useDefaultFont()); QVERIFY(!event.confirmAck()); QVERIFY(!event.speak()); QVERIFY(!event.autoClose()); QVERIFY(!event.holidaysExcluded()); QVERIFY(!event.repeatSound()); QVERIFY(!event.copyToKOrganizer()); QVERIFY(!event.workTimeOnly()); QVERIFY(!event.commandScript()); QVERIFY(!event.commandXterm()); QVERIFY(!event.commandDisplay()); QVERIFY(!event.reminderOnceOnly()); QVERIFY(!event.emailBcc()); } { const KAEvent::Flags flags(KAEvent::REPEAT_AT_LOGIN | KAEvent::DISABLED); KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::MESSAGE, 3, flags); QVERIFY(!event.startDateTime().isDateOnly()); QCOMPARE(event.flags(), flags); QVERIFY(event.repeatAtLogin()); QVERIFY(!event.enabled()); QVERIFY(!event.beep()); QVERIFY(!event.useDefaultFont()); QVERIFY(!event.confirmAck()); QVERIFY(!event.speak()); QVERIFY(!event.autoClose()); QVERIFY(!event.holidaysExcluded()); QVERIFY(!event.repeatSound()); QVERIFY(!event.copyToKOrganizer()); QVERIFY(!event.workTimeOnly()); QVERIFY(!event.commandScript()); QVERIFY(!event.commandXterm()); QVERIFY(!event.commandDisplay()); QVERIFY(!event.reminderOnceOnly()); QVERIFY(!event.emailBcc()); } { const KADateTime dtDateOnly(QDate(2010,5,13), QTimeZone("Europe/London")); const KAEvent::Flags flags(KAEvent::REPEAT_AT_LOGIN | KAEvent::DISABLED); KAEvent event(dtDateOnly, text, bgColour, fgColour, font, KAEvent::MESSAGE, 3, flags); QVERIFY(event.startDateTime().isDateOnly()); QCOMPARE(event.flags(), flags | KAEvent::ANY_TIME); QVERIFY(event.repeatAtLogin()); QVERIFY(!event.enabled()); QVERIFY(!event.beep()); QVERIFY(!event.useDefaultFont()); QVERIFY(!event.confirmAck()); QVERIFY(!event.speak()); QVERIFY(!event.autoClose()); QVERIFY(!event.holidaysExcluded()); QVERIFY(!event.repeatSound()); QVERIFY(!event.copyToKOrganizer()); QVERIFY(!event.workTimeOnly()); QVERIFY(!event.commandScript()); QVERIFY(!event.commandXterm()); QVERIFY(!event.commandDisplay()); QVERIFY(!event.reminderOnceOnly()); QVERIFY(!event.emailBcc()); } { const KAEvent::Flags flags(KAEvent::REPEAT_AT_LOGIN | KAEvent::DISABLED); KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::MESSAGE, 3, flags | KAEvent::ANY_TIME); QVERIFY(event.startDateTime().isDateOnly()); QCOMPARE(event.flags(), flags | KAEvent::ANY_TIME); QVERIFY(event.repeatAtLogin()); QVERIFY(!event.enabled()); QVERIFY(!event.beep()); QVERIFY(!event.useDefaultFont()); QVERIFY(!event.confirmAck()); QVERIFY(!event.speak()); QVERIFY(!event.autoClose()); QVERIFY(!event.holidaysExcluded()); QVERIFY(!event.repeatSound()); QVERIFY(!event.copyToKOrganizer()); QVERIFY(!event.workTimeOnly()); QVERIFY(!event.commandScript()); QVERIFY(!event.commandXterm()); QVERIFY(!event.commandDisplay()); QVERIFY(!event.reminderOnceOnly()); QVERIFY(!event.emailBcc()); } { const KAEvent::Flags flags(KAEvent::CONFIRM_ACK | KAEvent::SPEAK | KAEvent::EXCL_HOLIDAYS); KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::MESSAGE, 3, flags); QVERIFY(!event.startDateTime().isDateOnly()); QCOMPARE(event.flags(), flags); QVERIFY(!event.repeatAtLogin()); QVERIFY(event.enabled()); QVERIFY(!event.beep()); QVERIFY(!event.useDefaultFont()); QVERIFY(event.confirmAck()); QVERIFY(event.speak()); QVERIFY(!event.autoClose()); QVERIFY(event.holidaysExcluded()); QVERIFY(!event.repeatSound()); QVERIFY(!event.copyToKOrganizer()); QVERIFY(!event.workTimeOnly()); QVERIFY(!event.commandScript()); QVERIFY(!event.commandXterm()); QVERIFY(!event.commandDisplay()); QVERIFY(!event.reminderOnceOnly()); QVERIFY(!event.emailBcc()); } { KAEvent::setHolidays(KHolidays::HolidayRegion()); const KAEvent::Flags flags(KAEvent::AUTO_CLOSE | KAEvent::EXCL_HOLIDAYS | KAEvent::REPEAT_SOUND); KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::MESSAGE, 3, flags); QVERIFY(!event.startDateTime().isDateOnly()); QCOMPARE(event.flags(), flags); QVERIFY(!event.repeatAtLogin()); QVERIFY(event.enabled()); QVERIFY(!event.beep()); QVERIFY(!event.useDefaultFont()); QVERIFY(!event.confirmAck()); QVERIFY(!event.speak()); QVERIFY(event.autoClose()); QVERIFY(event.holidaysExcluded()); QVERIFY(event.repeatSound()); QVERIFY(!event.copyToKOrganizer()); QVERIFY(!event.workTimeOnly()); QVERIFY(!event.commandScript()); QVERIFY(!event.commandXterm()); QVERIFY(!event.commandDisplay()); QVERIFY(!event.reminderOnceOnly()); QVERIFY(!event.emailBcc()); } { const KAEvent::Flags flags(KAEvent::COPY_KORGANIZER | KAEvent::WORK_TIME_ONLY); KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::MESSAGE, 3, flags); QVERIFY(!event.startDateTime().isDateOnly()); QCOMPARE(event.flags(), flags); QVERIFY(!event.repeatAtLogin()); QVERIFY(event.enabled()); QVERIFY(!event.beep()); QVERIFY(!event.useDefaultFont()); QVERIFY(!event.confirmAck()); QVERIFY(!event.speak()); QVERIFY(!event.autoClose()); QVERIFY(!event.holidaysExcluded()); QVERIFY(!event.repeatSound()); QVERIFY(event.copyToKOrganizer()); QVERIFY(event.workTimeOnly()); QVERIFY(!event.commandScript()); QVERIFY(!event.commandXterm()); QVERIFY(!event.commandDisplay()); QVERIFY(!event.reminderOnceOnly()); QVERIFY(!event.emailBcc()); } { const KAEvent::Flags flags(KAEvent::SCRIPT | KAEvent::EXEC_IN_XTERM); KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::COMMAND, 3, flags); QVERIFY(!event.startDateTime().isDateOnly()); QCOMPARE(event.flags(), flags); QVERIFY(!event.repeatAtLogin()); QVERIFY(event.enabled()); QVERIFY(!event.beep()); QVERIFY(!event.useDefaultFont()); QVERIFY(!event.confirmAck()); QVERIFY(!event.speak()); QVERIFY(!event.autoClose()); QVERIFY(!event.holidaysExcluded()); QVERIFY(!event.repeatSound()); QVERIFY(!event.copyToKOrganizer()); QVERIFY(!event.workTimeOnly()); QVERIFY(event.commandScript()); QVERIFY(event.commandXterm()); QVERIFY(!event.commandDisplay()); QVERIFY(!event.reminderOnceOnly()); QVERIFY(!event.emailBcc()); } { const KAEvent::Flags flags(KAEvent::DISPLAY_COMMAND | KAEvent::REMINDER_ONCE); KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::COMMAND, 3, flags); QVERIFY(!event.startDateTime().isDateOnly()); QCOMPARE(event.flags(), flags); QVERIFY(!event.repeatAtLogin()); QVERIFY(event.enabled()); QVERIFY(!event.beep()); QVERIFY(!event.useDefaultFont()); QVERIFY(!event.confirmAck()); QVERIFY(!event.speak()); QVERIFY(!event.autoClose()); QVERIFY(!event.holidaysExcluded()); QVERIFY(!event.repeatSound()); QVERIFY(!event.copyToKOrganizer()); QVERIFY(!event.workTimeOnly()); QVERIFY(!event.commandScript()); QVERIFY(!event.commandXterm()); QVERIFY(event.commandDisplay()); QVERIFY(event.reminderOnceOnly()); QVERIFY(!event.emailBcc()); } { const KAEvent::Flags flags(KAEvent::EMAIL_BCC); KAEvent event(dt, text, bgColour, fgColour, font, KAEvent::EMAIL, 3, flags); QVERIFY(!event.startDateTime().isDateOnly()); QCOMPARE(event.flags(), flags); QVERIFY(!event.repeatAtLogin()); QVERIFY(event.enabled()); QVERIFY(!event.beep()); QVERIFY(!event.useDefaultFont()); QVERIFY(!event.confirmAck()); QVERIFY(!event.speak()); QVERIFY(!event.autoClose()); QVERIFY(!event.holidaysExcluded()); QVERIFY(!event.repeatSound()); QVERIFY(!event.copyToKOrganizer()); QVERIFY(!event.workTimeOnly()); QVERIFY(!event.commandScript()); QVERIFY(!event.commandXterm()); QVERIFY(!event.commandDisplay()); QVERIFY(!event.reminderOnceOnly()); QVERIFY(event.emailBcc()); } } namespace { Event::Ptr createKcalEvent(const QDateTime& start, const QDateTime& created, Alarm::Ptr& kcalalarm, Alarm::Type type = Alarm::Display) { Event::Ptr kcalevent(new Event); kcalevent->setCreated(created); kcalevent->setDtStart(start); kcalalarm = kcalevent->newAlarm(); kcalalarm->setType(type); switch (type) { case Alarm::Display: kcalalarm->setText(QStringLiteral("message")); break; case Alarm::Procedure: kcalalarm->setProgramFile(QStringLiteral("/tmp/cmd.sh")); kcalalarm->setProgramArguments(QStringLiteral("-a PERM")); break; case Alarm::Email: { Person addr(QStringLiteral("Cliff Edge"), QStringLiteral("cliff@edge.com")); kcalalarm->setMailSubject(QStringLiteral("Subject")); kcalalarm->setMailText(QStringLiteral("message")); kcalalarm->setMailAddress(addr); kcalalarm->setMailAttachment(QStringLiteral("/tmp/secret.txt")); break; } case Alarm::Audio: kcalalarm->setAudioFile(QStringLiteral("/tmp/sample.ogg")); break; default: break; } return kcalevent; } Event::Ptr createKcalEvent(const QDateTime& start, const QDateTime& created, Alarm::Type type = Alarm::Display) { Alarm::Ptr kcalalarm; return createKcalEvent(start, created, kcalalarm, type); } Alarm::Ptr copyKcalAlarm(Event::Ptr& kcalevent, Alarm::Ptr& kcalalarm) { Alarm::Ptr newAlarm = kcalevent->newAlarm(); *newAlarm.data() = *kcalalarm.data(); return newAlarm; } } void KAEventTest::fromKCalEvent() { // Check KCalCore::Event custom properties. const KADateTime dt(QDate(2010,5,13), QTime(3, 45, 0), QTimeZone("Europe/London")); const QDateTime createdDt(QDate(2009,4,13), QTime(11,14,0), QTimeZone("UTC")); // Event category, UID, revision, start time, created time { const QString uid = QStringLiteral("fa74ec931"); Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("ACTIVE")); kcalevent->setUid(uid); kcalevent->setRevision(273); KAEvent event(kcalevent); QCOMPARE(event.category(), CalEvent::ACTIVE); QCOMPARE(event.startDateTime().kDateTime(), dt); QCOMPARE(event.createdDateTime().qDateTime(), createdDt); QCOMPARE(event.id(), uid); QCOMPARE(event.revision(), 273); } { // Start time using LocalZone const KADateTime dtLocal(dt.date(), dt.time(), KADateTime::LocalZone); Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("ACTIVE")); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("LOCAL")); KAEvent event(kcalevent); QCOMPARE(event.category(), CalEvent::ACTIVE); QCOMPARE(event.startDateTime().kDateTime(), dtLocal); QCOMPARE(event.createdDateTime().qDateTime(), createdDt); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("TEMPLATE")); KAEvent event(kcalevent); QCOMPARE(event.category(), CalEvent::TEMPLATE); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("ARCHIVED")); KAEvent event(kcalevent); QCOMPARE(event.category(), CalEvent::ARCHIVED); } { bool showEdit = false; bool showDefer = false; Akonadi::Collection::Id collectionId = -1; Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); Alarm::Ptr kcalalarmDisp = copyKcalAlarm(kcalevent, kcalalarm); { kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING")); kcalalarmDisp->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING")); KAEvent event(kcalevent); QCOMPARE(event.category(), CalEvent::DISPLAYING); KAEvent event2; event2.reinstateFromDisplaying(kcalevent, collectionId, showEdit, showDefer); QCOMPARE(event2.category(), CalEvent::ACTIVE); QVERIFY(!event2.repeatAtLogin()); QCOMPARE(collectionId, -1); QVERIFY(!showEdit); QVERIFY(!showDefer); } { kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING;7;EDIT")); KAEvent event(kcalevent); QCOMPARE(event.category(), CalEvent::DISPLAYING); KAEvent event2; event2.reinstateFromDisplaying(kcalevent, collectionId, showEdit, showDefer); QCOMPARE(event2.category(), CalEvent::ACTIVE); QVERIFY(!event2.deferred()); QCOMPARE(collectionId, 7); QVERIFY(showEdit); QVERIFY(!showDefer); } { kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING;-1;DEFER")); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("DEFERRAL")); kcalalarmDisp->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING,DEFERRAL")); KAEvent event(kcalevent); QCOMPARE(event.category(), CalEvent::DISPLAYING); KAEvent event2; event2.reinstateFromDisplaying(kcalevent, collectionId, showEdit, showDefer); QCOMPARE(event2.category(), CalEvent::ACTIVE); QVERIFY(event2.deferred()); QVERIFY(!event2.deferDateTime().isDateOnly()); QCOMPARE(collectionId, -1); QVERIFY(!showEdit); QVERIFY(showDefer); } { kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING;634;DEFER;EDIT")); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("DATE_DEFERRAL")); kcalalarmDisp->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING,DATE_DEFERRAL")); KAEvent event(kcalevent); QCOMPARE(event.category(), CalEvent::DISPLAYING); KAEvent event2; event2.reinstateFromDisplaying(kcalevent, collectionId, showEdit, showDefer); QCOMPARE(event2.category(), CalEvent::ACTIVE); QVERIFY(event2.deferred()); QVERIFY(event2.deferDateTime().isDateOnly()); QCOMPARE(collectionId, 634); QVERIFY(showEdit); QVERIFY(showDefer); } } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING;DEFER")); KAEvent event(kcalevent); QCOMPARE(event.category(), CalEvent::DISPLAYING); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING;EDIT")); KAEvent event(kcalevent); QCOMPARE(event.category(), CalEvent::DISPLAYING); } // Event flags { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("DATE")); KAEvent event(kcalevent); QVERIFY(event.startDateTime().isDateOnly()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("ACKCONF")); KAEvent event(kcalevent); QVERIFY(!event.startDateTime().isDateOnly()); QVERIFY(event.confirmAck()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("KORG")); KAEvent event(kcalevent); QVERIFY(!event.confirmAck()); QVERIFY(event.copyToKOrganizer()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("EXHOLIDAYS")); KAEvent event(kcalevent); QVERIFY(!event.copyToKOrganizer()); QVERIFY(event.holidaysExcluded()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("WORKTIME")); KAEvent event(kcalevent); QVERIFY(!event.holidaysExcluded()); QVERIFY(event.workTimeOnly()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("LATECANCEL;4")); KAEvent event(kcalevent); QVERIFY(!event.reminderOnceOnly()); QCOMPARE(event.lateCancel(), 4); QVERIFY(!event.autoClose()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("LOGIN")); KAEvent event(kcalevent); QCOMPARE(event.lateCancel(), 0); QVERIFY(event.repeatAtLogin(true)); QVERIFY(!event.repeatAtLogin(false)); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("LATECLOSE;16")); KAEvent event(kcalevent); QVERIFY(!event.repeatAtLogin()); QCOMPARE(event.lateCancel(), 16); QVERIFY(event.autoClose()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("KORG")); KAEvent event(kcalevent); QCOMPARE(event.lateCancel(), 0); QVERIFY(!event.autoClose()); QVERIFY(event.copyToKOrganizer()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("ARCHIVE")); KAEvent event(kcalevent); QVERIFY(!event.copyToKOrganizer()); QVERIFY(event.toBeArchived()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("DEFER;7")); KAEvent event(kcalevent); QVERIFY(!event.toBeArchived()); QCOMPARE(event.deferDefaultMinutes(), 7); QVERIFY(!event.deferDefaultDateOnly()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("DEFER;6D")); KAEvent event(kcalevent); QCOMPARE(event.deferDefaultMinutes(), 6); QVERIFY(event.deferDefaultDateOnly()); } { // Reminder after the event, first recurrence only Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("REMINDER;ONCE;27M")); kcalalarm = copyKcalAlarm(kcalevent, kcalalarm); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("REMINDER")); kcalalarm->setStartOffset(-27*60); KAEvent event(kcalevent); QCOMPARE(event.deferDefaultMinutes(), 0); QVERIFY(!event.deferDefaultDateOnly()); QVERIFY(event.reminderOnceOnly()); QCOMPARE(event.reminderMinutes(), -27); } { // Reminder before the event Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("REMINDER;-27H")); kcalalarm = copyKcalAlarm(kcalevent, kcalalarm); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("REMINDER")); kcalalarm->setStartOffset(-27*3600); KAEvent event(kcalevent); QVERIFY(event.reminderActive()); QVERIFY(!event.reminderOnceOnly()); QCOMPARE(event.reminderMinutes(), 27*60); } { // Reminder after the event Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("REMINDER;27D")); kcalalarm = copyKcalAlarm(kcalevent, kcalalarm); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("REMINDER")); kcalalarm->setStartOffset(Duration(27, Duration::Days)); KAEvent event(kcalevent); QVERIFY(event.reminderActive()); QVERIFY(!event.reminderOnceOnly()); QCOMPARE(event.reminderMinutes(), -27*60*24); } { // Reminder before the event Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("REMINDER;10M")); kcalalarm = copyKcalAlarm(kcalevent, kcalalarm); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("REMINDER")); kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("HIDE")); kcalalarm->setStartOffset(10*60); KAEvent event(kcalevent); QVERIFY(!event.reminderActive()); QVERIFY(!event.reminderOnceOnly()); QCOMPARE(event.reminderMinutes(), -10); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("BCC")); KAEvent event(kcalevent); QVERIFY(!event.reminderOnceOnly()); QCOMPARE(event.reminderMinutes(), 0); QVERIFY(event.emailBcc()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("TMPLAFTTIME;31")); KAEvent event(kcalevent); QVERIFY(!event.emailBcc()); QCOMPARE(event.templateAfterTime(), 31); - QCOMPARE(event.kmailSerialNumber(), 0UL); + QCOMPARE(event.akonadiItemId(), Akonadi::Item::Id(-1)); } { - // KMail serial number, with alarm message in email format + // Akonadi item ID, with alarm message in email format Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("KMAIL;759231")); kcalalarm->setText(QStringLiteral("From: a@b.c\nTo: d@e.f\nDate: Sun, 01 Apr 2018 17:36:06 +0100\nSubject: About this")); KAEvent event(kcalevent); QCOMPARE(event.templateAfterTime(), -1); - QCOMPARE(event.kmailSerialNumber(), 759231UL); + QCOMPARE(event.akonadiItemId(), Akonadi::Item::Id(759231)); } { - // KMail serial number, with alarm message in wrong format + // Akonadi item ID, with alarm message in wrong format Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("KMAIL;759231")); KAEvent event(kcalevent); - QCOMPARE(event.kmailSerialNumber(), 0UL); + QCOMPARE(event.akonadiItemId(), Akonadi::Item::Id(-1)); } // Alarm custom properties { Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("FILE")); kcalalarm->setStartOffset(5*60); KAEvent event(kcalevent); QCOMPARE(event.actionSubType(), KAEvent::FILE); } { Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("DEFERRAL")); kcalalarm->setStartOffset(5*60); KAEvent event(kcalevent); QCOMPARE(event.actionSubType(), KAEvent::MESSAGE); QVERIFY(event.mainExpired()); } { QFont font(QStringLiteral("Monospace"), 8); font.setBold(true); Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalalarm->setCustomProperty("KALARM", "FONTCOLOR", QStringLiteral("#27A8F3;#94B0FF;") + font.toString()); KAEvent event(kcalevent); QVERIFY(!event.mainExpired()); QCOMPARE(event.bgColour(), QColor("#27A8F3")); QCOMPARE(event.fgColour(), QColor("#94B0FF")); QCOMPARE(event.font(), font); } { // Non-repeating sound Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalalarm = kcalevent->newAlarm(); kcalalarm->setType(Alarm::Audio); kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("SPEAK")); KAEvent event(kcalevent); QVERIFY(event.speak()); QCOMPARE(event.repeatSoundPause(), -1); } { // Sound volume Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalalarm = kcalevent->newAlarm(); kcalalarm->setAudioAlarm(QStringLiteral("/tmp/next.ogg")); kcalalarm->setCustomProperty("KALARM", "VOLUME", QStringLiteral("0.7;0.3;9")); KAEvent event(kcalevent); QVERIFY(!event.speak()); QCOMPARE(event.soundVolume(), 0.7f); QCOMPARE(event.fadeVolume(), 0.3f); QCOMPARE(event.fadeSeconds(), 9); } { // Sound volume Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm, Alarm::Audio); kcalalarm->setCustomProperty("KALARM", "VOLUME", QStringLiteral("0.7;0.3;9")); KAEvent event(kcalevent); QCOMPARE(event.soundVolume(), 0.7f); QCOMPARE(event.fadeVolume(), 0.3f); QCOMPARE(event.fadeSeconds(), 9); } { // Display alarm with repeating sound, without pause Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalalarm = kcalevent->newAlarm(); kcalalarm->setAudioAlarm(QStringLiteral("/tmp/next.ogg")); kcalalarm->setRepeatCount(-1); kcalalarm->setSnoozeTime(Duration(0)); KAEvent event(kcalevent); QCOMPARE(event.repeatSoundPause(), 0); } { // Display alarm with repeating sound, with pause Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalalarm = kcalevent->newAlarm(); kcalalarm->setAudioAlarm(QStringLiteral("/tmp/next.ogg")); kcalalarm->setRepeatCount(-2); kcalalarm->setSnoozeTime(Duration(6)); KAEvent event(kcalevent); QCOMPARE(event.repeatSoundPause(), 6); } { // Audio alarm with repeating sound, without pause Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm, Alarm::Audio); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("SOUNDREPEAT,0")); KAEvent event(kcalevent); QCOMPARE(event.repeatSoundPause(), 0); } { // Audio alarm with repeating sound, with pause Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm, Alarm::Audio); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("SOUNDREPEAT,4")); KAEvent event(kcalevent); QCOMPARE(event.repeatSoundPause(), 4); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, Alarm::Procedure); kcalevent->setCustomProperty("KALARM", "LOG", QStringLiteral("xterm:")); KAEvent event(kcalevent); QVERIFY(event.commandXterm()); QVERIFY(!event.commandDisplay()); QVERIFY(event.logFile().isEmpty()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "LOG", QStringLiteral("display:")); KAEvent event(kcalevent); QVERIFY(!event.commandXterm()); QVERIFY(event.commandDisplay()); QVERIFY(event.logFile().isEmpty()); } { Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); const QString file(QStringLiteral("/tmp/file")); kcalevent->setCustomProperty("KALARM", "LOG", file); KAEvent event(kcalevent); QVERIFY(!event.commandXterm()); QVERIFY(!event.commandDisplay()); QCOMPARE(event.logFile(), file); QVERIFY(!event.recurs()); } { // Test date/time event with recurrence and sub-repetition Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); Recurrence* recurrence = kcalevent->recurrence(); recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false); recurrence->setHourly(3); { KAEvent event(kcalevent); QCOMPARE(event.repetition().interval().asSeconds(), 0); QCOMPARE(event.repetition().count(), 0); QCOMPARE(event.nextRepetition(), 0); } kcalalarm->setSnoozeTime(17*60); kcalalarm->setRepeatCount(5); kcalalarm->setCustomProperty("KALARM", "NEXTREPEAT", QStringLiteral("2")); { KAEvent event(kcalevent); QCOMPARE(event.repetition().interval().asSeconds(), 17*60); QCOMPARE(event.repetition().count(), 5); QCOMPARE(event.nextRepetition(), 2); } } { // Test deferred event whose main alarm has expired, with sub-repetition Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("DEFERRAL")); kcalevent->setCustomProperty("KALARM", "REPEAT", QStringLiteral("17:5")); Recurrence* recurrence = kcalevent->recurrence(); recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false); recurrence->setHourly(3); KAEvent event(kcalevent); QCOMPARE(event.recurType(), KARecurrence::MINUTELY); QCOMPARE(event.recurInterval(), 3*60); QCOMPARE(event.repetition().interval().asSeconds(), 17*60); QCOMPARE(event.repetition().count(), 5); } { // Test deferred event whose main alarm has not expired, with sub-repetition Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); kcalevent->setCustomProperty("KALARM", "REPEAT", QStringLiteral("17:5")); Recurrence* recurrence = kcalevent->recurrence(); recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false); recurrence->setHourly(3); KAEvent event(kcalevent); QCOMPARE(event.recurType(), KARecurrence::MINUTELY); QCOMPARE(event.recurInterval(), 3*60); QCOMPARE(event.repetition().interval().asSeconds(), 0); QCOMPARE(event.repetition().count(), 0); } { // Test date/time event with next recurrence Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); Recurrence* recurrence = kcalevent->recurrence(); recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false); recurrence->setHourly(3); kcalevent->setCustomProperty("KALARM", "NEXTRECUR", QStringLiteral("20100514T051700")); KAEvent event(kcalevent); QVERIFY(event.recurs()); QCOMPARE(event.recurType(), KARecurrence::MINUTELY); QCOMPARE(event.recurInterval(), 3*60); QCOMPARE(event.repetition().interval().asSeconds(), 0); QCOMPARE(event.repetition().count(), 0); QVERIFY(event.mainDateTime() > event.startDateTime()); } { // Test date/time event with date-only next recurrence Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); Recurrence* recurrence = kcalevent->recurrence(); recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false); recurrence->setHourly(3); kcalevent->setCustomProperty("KALARM", "NEXTRECUR", QStringLiteral("20100514")); KAEvent event(kcalevent); QVERIFY(event.recurs()); QCOMPARE(event.recurType(), KARecurrence::MINUTELY); QCOMPARE(event.recurInterval(), 3*60); QCOMPARE(event.repetition().interval().asSeconds(), 0); QCOMPARE(event.repetition().count(), 0); QCOMPARE(event.mainDateTime(), event.startDateTime()); } { // Test date-only event with next recurrence Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); Recurrence* recurrence = kcalevent->recurrence(); recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false); recurrence->setDaily(3); kcalevent->setCustomProperty("KALARM", "NEXTRECUR", QStringLiteral("20100516")); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("DATE")); KAEvent event(kcalevent); QVERIFY(event.recurs()); QCOMPARE(event.recurType(), KARecurrence::DAILY); QCOMPARE(event.recurInterval(), 3); QCOMPARE(event.repetition().interval().asSeconds(), 0); QCOMPARE(event.repetition().count(), 0); QVERIFY(event.mainDateTime() > event.startDateTime()); } { // Test date-only event with date/time next recurrence Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); Recurrence* recurrence = kcalevent->recurrence(); recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false); recurrence->setDaily(3); kcalevent->setCustomProperty("KALARM", "NEXTRECUR", QStringLiteral("20100516T051700")); kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("DATE")); KAEvent event(kcalevent); QVERIFY(event.recurs()); QCOMPARE(event.recurType(), KARecurrence::DAILY); QCOMPARE(event.recurInterval(), 3); QCOMPARE(event.repetition().interval().asSeconds(), 0); QCOMPARE(event.repetition().count(), 0); QCOMPARE(event.mainDateTime(), event.startDateTime()); } { // Pre-action alarm Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); Alarm::Ptr kcalalarm = kcalevent->newAlarm(); kcalalarm->setProcedureAlarm(QStringLiteral("/tmp/action.sh"), QStringLiteral("-h")); kcalalarm->setStartOffset(0); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("PRE")); KAEvent event(kcalevent); QCOMPARE(event.preAction(), QStringLiteral("/tmp/action.sh -h")); QCOMPARE(event.extraActionOptions(), 0); } { // Pre-action alarm Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); Alarm::Ptr kcalalarm = kcalevent->newAlarm(); kcalalarm->setProcedureAlarm(QStringLiteral("/tmp/action.sh"), QStringLiteral("-h")); kcalalarm->setStartOffset(0); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("PRE")); kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("EXECDEFER")); KAEvent event(kcalevent); QCOMPARE(event.preAction(), QStringLiteral("/tmp/action.sh -h")); QCOMPARE(event.extraActionOptions(), KAEvent::ExecPreActOnDeferral); } { // Pre-action alarm Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); Alarm::Ptr kcalalarm = kcalevent->newAlarm(); kcalalarm->setProcedureAlarm(QStringLiteral("/tmp/action.sh"), QStringLiteral("-h")); kcalalarm->setStartOffset(0); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("PRE")); kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("ERRCANCEL")); KAEvent event(kcalevent); QCOMPARE(event.preAction(), QStringLiteral("/tmp/action.sh -h")); QCOMPARE(event.extraActionOptions(), KAEvent::CancelOnPreActError); } { // Pre-action alarm Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); Alarm::Ptr kcalalarm = kcalevent->newAlarm(); kcalalarm->setProcedureAlarm(QStringLiteral("/tmp/action.sh"), QStringLiteral("-h")); kcalalarm->setStartOffset(0); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("PRE")); kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("ERRNOSHOW")); KAEvent event(kcalevent); QCOMPARE(event.preAction(), QStringLiteral("/tmp/action.sh -h")); QCOMPARE(event.extraActionOptions(), KAEvent::DontShowPreActError); } { // Pre-action alarm Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); Alarm::Ptr kcalalarm = kcalevent->newAlarm(); kcalalarm->setProcedureAlarm(QStringLiteral("/tmp/action.sh"), QStringLiteral("-h")); kcalalarm->setStartOffset(0); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("PRE")); kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("ERRNOSHOW;ERRCANCEL;EXECDEFER")); KAEvent event(kcalevent); QCOMPARE(event.preAction(), QStringLiteral("/tmp/action.sh -h")); QCOMPARE(event.extraActionOptions(), KAEvent::DontShowPreActError | KAEvent::CancelOnPreActError | KAEvent::ExecPreActOnDeferral); } { // Post-action alarm Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt); Alarm::Ptr kcalalarm = kcalevent->newAlarm(); kcalalarm->setProcedureAlarm(QStringLiteral("/tmp/action.sh"), QStringLiteral("-h")); kcalalarm->setStartOffset(0); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("POST")); KAEvent event(kcalevent); QCOMPARE(event.postAction(), QStringLiteral("/tmp/action.sh -h")); QCOMPARE(event.extraActionOptions(), 0); } { // Email-from ID Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm, Alarm::Email); kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("EMAILID;2589")); KAEvent event(kcalevent); QCOMPARE(event.emailFromId(), 2589U); } { // Archived repeat-at-login Alarm::Ptr kcalalarm; Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm); kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("LOGIN")); KAEvent event(kcalevent); QVERIFY(event.repeatAtLogin(false)); } } void KAEventTest::toKCalEvent() { // Check KCalCore::Event custom properties. const KADateTime dt(QDate(2010,5,13), QTime(3, 45, 0), QTimeZone("Europe/London")); const KADateTime createdDt(QDate(2009,4,13), QTime(11,14,0), QTimeZone("UTC")); const QString text = QStringLiteral("message"); const QColor fgColour(0x82, 0x6e, 0xf0); const QColor bgColour(0x14, 0x46, 0x8c); const QFont font(QStringLiteral("Helvetica"), 10, QFont::Bold, true); const QString uid = QStringLiteral("fa74ec931"); { // Event category, UID, revision, start time using time zone, created time KAEvent event; event.set(dt, text, bgColour, fgColour, font, KAEvent::MESSAGE, 3, KAEvent::CONFIRM_ACK); event.setEventId(uid); event.incrementRevision(); event.incrementRevision(); event.setCategory(CalEvent::ACTIVE); event.setCreatedDateTime(createdDt); Event::Ptr kcalevent(new Event); QVERIFY(event.updateKCalEvent(kcalevent, KAEvent::UID_SET, true)); QCOMPARE(kcalevent->uid(), uid); QCOMPARE(kcalevent->revision(), 2); QCOMPARE(kcalevent->customProperty("KALARM", "TYPE"), QStringLiteral("ACTIVE")); QStringList flags = kcalevent->customProperty("KALARM", "FLAGS").split(SC); QCOMPARE(flags.size(), 3); // must contain LATECANCEL;3 and ACKCONF QCOMPARE(flags.removeAll(QStringLiteral("ACKCONF")), 1); QCOMPARE(flags.at(0), QStringLiteral("LATECANCEL")); QCOMPARE(flags.at(1), QStringLiteral("3")); QCOMPARE(kcalevent->dtStart(), dt.qDateTime()); QCOMPARE(kcalevent->created(), createdDt.qDateTime()); const Alarm::List kcalalarms = kcalevent->alarms(); QCOMPARE(kcalalarms.size(), 1); Alarm::Ptr kcalalarm(kcalalarms[0]); QCOMPARE(kcalalarm->type(), Alarm::Display); QCOMPARE(kcalalarm->text(), text); QCOMPARE(kcalalarm->customProperty("KALARM", "FONTCOLOR").toUpper(), (QStringLiteral("#14468C;#826EF0;") + font.toString()).toUpper()); } { // Start time using LocalZone const KADateTime dt(QDate(2010,5,13), QTime(3, 45, 0), KADateTime::LocalZone); KAEvent event; event.set(dt, text, bgColour, fgColour, font, KAEvent::MESSAGE, 3, KAEvent::CONFIRM_ACK); event.setEventId(uid); event.incrementRevision(); event.setCategory(CalEvent::ACTIVE); event.setCreatedDateTime(createdDt); Event::Ptr kcalevent(new Event); QVERIFY(event.updateKCalEvent(kcalevent, KAEvent::UID_SET, true)); QCOMPARE(kcalevent->uid(), uid); QCOMPARE(kcalevent->revision(), 1); QCOMPARE(kcalevent->customProperty("KALARM", "TYPE"), QStringLiteral("ACTIVE")); QStringList flags = kcalevent->customProperty("KALARM", "FLAGS").split(SC); QCOMPARE(flags.size(), 4); // must contain LOCAL, LATECANCEL;3 and ACKCONF QVERIFY(flags.contains(QStringLiteral("LOCAL"))); const QDateTime dtCurrentTz(dt.date(), dt.time(), QTimeZone::systemTimeZone()); QCOMPARE(kcalevent->dtStart(), dtCurrentTz); QCOMPARE(kcalevent->created(), createdDt.qDateTime()); } } // vim: et sw=4: diff --git a/serializers/akonadi_serializer_kalarm.cpp b/serializers/akonadi_serializer_kalarm.cpp index bb065ca..05aa127 100644 --- a/serializers/akonadi_serializer_kalarm.cpp +++ b/serializers/akonadi_serializer_kalarm.cpp @@ -1,315 +1,315 @@ /* * akonadi_serializer_kalarm.cpp - Akonadi resource serializer for KAlarm - * Copyright © 2009-2012,2018 by David Jarvie + * Copyright © 2009-2019 David Jarvie * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Library General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public * License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "akonadi_serializer_kalarm.h" #include "kaeventformatter.h" #include "akonadi_serializer_kalarm_debug.h" #include "eventattribute.h" #include "kacalendar.h" #include "kaevent.h" #include #include #include #include #include #include using namespace Akonadi; using namespace KAlarmCal; // Convert from backend data stream to a KAEvent, and set it into the item's payload. bool SerializerPluginKAlarm::deserialize(Item &item, const QByteArray &label, QIODevice &data, int version) { Q_UNUSED(version); if (label != Item::FullPayload) { return false; } KCalCore::Incidence::Ptr i = mFormat.fromString(QString::fromUtf8(data.readAll())); if (!i) { qCWarning(AKONADI_SERIALIZER_KALARM_LOG) << "Failed to parse incidence!"; data.seek(0); qCWarning(AKONADI_SERIALIZER_KALARM_LOG) << QString::fromUtf8(data.readAll()); return false; } if (i->type() != KCalCore::Incidence::TypeEvent) { qCWarning(AKONADI_SERIALIZER_KALARM_LOG) << "Incidence with uid" << i->uid() << "is not an Event!"; data.seek(0); return false; } KAEvent event(i.staticCast()); const QString mime = CalEvent::mimeType(event.category()); if (mime.isEmpty() || !event.isValid()) { qCWarning(AKONADI_SERIALIZER_KALARM_LOG) << "Event with uid" << event.id() << "contains no usable alarms!"; data.seek(0); return false; } event.setItemId(item.id()); // Set additional event data contained in attributes if (mRegistered.isEmpty()) { AttributeFactory::registerAttribute(); mRegistered = QStringLiteral("x"); // set to any non-null string } const EventAttribute dummy; if (item.hasAttribute(dummy.type())) { Attribute *a = item.attribute(dummy.type()); if (!a) { qCCritical(AKONADI_SERIALIZER_KALARM_LOG) << "deserialize(): Event with uid" << event.id() << "contains null attribute"; } else { EventAttribute *evAttr = dynamic_cast(a); if (!evAttr) { // Registering EventAttribute doesn't work in the serializer // unless the application also registers it. This doesn't // matter unless the application uses KAEvent class. qCCritical(AKONADI_SERIALIZER_KALARM_LOG) << "deserialize(): Event with uid" << event.id() << "contains unknown type EventAttribute (application must call AttributeFactory::registerAttribute())"; } else { KAEvent::CmdErrType err = evAttr->commandError(); event.setCommandError(err); } } } item.setMimeType(mime); item.setPayload(event); return true; } // Convert an item's KAEvent payload to backend data stream. void SerializerPluginKAlarm::serialize(const Item &item, const QByteArray &label, QIODevice &data, int &version) { Q_UNUSED(version); if (label != Item::FullPayload || !item.hasPayload()) { return; } const KAEvent e = item.payload(); KCalCore::Event::Ptr kcalEvent(new KCalCore::Event); e.updateKCalEvent(kcalEvent, KAEvent::UID_SET); QByteArray head = "BEGIN:VCALENDAR\nPRODID:"; head += KACalendar::icalProductId(); head += "\nVERSION:2.0\nX-KDE-KALARM-VERSION:"; head += KAEvent::currentCalendarVersionString(); head += '\n'; data.write(head); data.write(mFormat.toString(kcalEvent.staticCast()).toUtf8()); data.write("\nEND:VCALENDAR"); } void SerializerPluginKAlarm::compare(AbstractDifferencesReporter *reporter, const Item &left, const Item &right) { Q_ASSERT(reporter); Q_ASSERT(left.hasPayload()); Q_ASSERT(right.hasPayload()); KAEvent eventL = left.payload(); KAEvent eventR = right.payload(); // Note that event attributes are not included, since they are not part of the payload mValueL = KAEventFormatter(eventL, false); mValueR = KAEventFormatter(eventR, false); reporter->setLeftPropertyValueTitle(i18nc("@title:column", "Changed Alarm")); reporter->setRightPropertyValueTitle(i18nc("@title:column", "Conflicting Alarm")); reportDifference(reporter, KAEventFormatter::Id); if (eventL.revision() != eventR.revision()) { reportDifference(reporter, KAEventFormatter::Revision); } if (eventL.actionSubType() != eventR.actionSubType()) { reportDifference(reporter, KAEventFormatter::AlarmType); } if (eventL.category() != eventR.category()) { reportDifference(reporter, KAEventFormatter::AlarmCategory); } if (eventL.templateName() != eventR.templateName()) { reportDifference(reporter, KAEventFormatter::TemplateName); } if (eventL.createdDateTime() != eventR.createdDateTime()) { reportDifference(reporter, KAEventFormatter::CreatedTime); } if (eventL.startDateTime() != eventR.startDateTime()) { reportDifference(reporter, KAEventFormatter::StartTime); } if (eventL.templateAfterTime() != eventR.templateAfterTime()) { reportDifference(reporter, KAEventFormatter::TemplateAfterTime); } if (*eventL.recurrence() != *eventR.recurrence()) { reportDifference(reporter, KAEventFormatter::Recurrence); } if (eventL.mainDateTime(true) != eventR.mainDateTime(true)) { reportDifference(reporter, KAEventFormatter::NextRecurrence); } if (eventL.repetition() != eventR.repetition()) { reportDifference(reporter, KAEventFormatter::SubRepetition); } if (eventL.repetition().interval() != eventR.repetition().interval()) { reportDifference(reporter, KAEventFormatter::RepeatInterval); } if (eventL.repetition().count() != eventR.repetition().count()) { reportDifference(reporter, KAEventFormatter::RepeatCount); } if (eventL.nextRepetition() != eventR.nextRepetition()) { reportDifference(reporter, KAEventFormatter::NextRepetition); } if (eventL.holidaysExcluded() != eventR.holidaysExcluded()) { reportDifference(reporter, KAEventFormatter::HolidaysExcluded); } if (eventL.workTimeOnly() != eventR.workTimeOnly()) { reportDifference(reporter, KAEventFormatter::WorkTimeOnly); } if (eventL.lateCancel() != eventR.lateCancel()) { reportDifference(reporter, KAEventFormatter::LateCancel); } if (eventL.autoClose() != eventR.autoClose()) { reportDifference(reporter, KAEventFormatter::AutoClose); } if (eventL.copyToKOrganizer() != eventR.copyToKOrganizer()) { reportDifference(reporter, KAEventFormatter::CopyKOrganizer); } if (eventL.enabled() != eventR.enabled()) { reportDifference(reporter, KAEventFormatter::Enabled); } if (eventL.isReadOnly() != eventR.isReadOnly()) { reportDifference(reporter, KAEventFormatter::ReadOnly); } if (eventL.toBeArchived() != eventR.toBeArchived()) { reportDifference(reporter, KAEventFormatter::Archive); } if (eventL.customProperties() != eventR.customProperties()) { reportDifference(reporter, KAEventFormatter::CustomProperties); } if (eventL.message() != eventR.message()) { reportDifference(reporter, KAEventFormatter::MessageText); } if (eventL.fileName() != eventR.fileName()) { reportDifference(reporter, KAEventFormatter::MessageFile); } if (eventL.fgColour() != eventR.fgColour()) { reportDifference(reporter, KAEventFormatter::FgColour); } if (eventL.bgColour() != eventR.bgColour()) { reportDifference(reporter, KAEventFormatter::BgColour); } if (eventL.font() != eventR.font()) { reportDifference(reporter, KAEventFormatter::Font); } if (eventL.preAction() != eventR.preAction()) { reportDifference(reporter, KAEventFormatter::PreAction); } if ((eventL.extraActionOptions() & KAEvent::CancelOnPreActError) != (eventR.extraActionOptions() & KAEvent::CancelOnPreActError)) { reportDifference(reporter, KAEventFormatter::PreActionCancel); } if ((eventL.extraActionOptions() & KAEvent::DontShowPreActError) != (eventR.extraActionOptions() & KAEvent::DontShowPreActError)) { reportDifference(reporter, KAEventFormatter::PreActionNoError); } if (eventL.postAction() != eventR.postAction()) { reportDifference(reporter, KAEventFormatter::PostAction); } if (eventL.confirmAck() != eventR.confirmAck()) { reportDifference(reporter, KAEventFormatter::ConfirmAck); } - if (eventL.kmailSerialNumber() != eventR.kmailSerialNumber()) { - reportDifference(reporter, KAEventFormatter::KMailSerial); + if (eventL.akonadiItemId() != eventR.akonadiItemId()) { + reportDifference(reporter, KAEventFormatter::AkonadiItem); } if (eventL.beep() != eventR.beep() || eventL.speak() != eventR.speak() || eventL.audioFile() != eventR.audioFile()) { reportDifference(reporter, KAEventFormatter::Sound); } if (eventL.repeatSound() != eventR.repeatSound()) { reportDifference(reporter, KAEventFormatter::SoundRepeat); } if (eventL.soundVolume() != eventR.soundVolume()) { reportDifference(reporter, KAEventFormatter::SoundVolume); } if (eventL.fadeVolume() != eventR.fadeVolume()) { reportDifference(reporter, KAEventFormatter::SoundFadeVolume); } if (eventL.fadeSeconds() != eventR.fadeSeconds()) { reportDifference(reporter, KAEventFormatter::SoundFadeTime); } if (eventL.reminderMinutes() != eventR.reminderMinutes()) { reportDifference(reporter, KAEventFormatter::Reminder); } if (eventL.reminderOnceOnly() != eventR.reminderOnceOnly()) { reportDifference(reporter, KAEventFormatter::ReminderOnce); } if (eventL.deferred() != eventR.deferred()) { reportDifference(reporter, KAEventFormatter::DeferralType); } if (eventL.deferDateTime() != eventR.deferDateTime()) { reportDifference(reporter, KAEventFormatter::DeferralTime); } if (eventL.deferDefaultMinutes() != eventR.deferDefaultMinutes()) { reportDifference(reporter, KAEventFormatter::DeferDefault); } if (eventL.deferDefaultDateOnly() != eventR.deferDefaultDateOnly()) { reportDifference(reporter, KAEventFormatter::DeferDefaultDate); } if (eventL.command() != eventR.command()) { reportDifference(reporter, KAEventFormatter::Command); } if (eventL.logFile() != eventR.logFile()) { reportDifference(reporter, KAEventFormatter::LogFile); } if (eventL.commandXterm() != eventR.commandXterm()) { reportDifference(reporter, KAEventFormatter::CommandXTerm); } if (eventL.emailSubject() != eventR.emailSubject()) { reportDifference(reporter, KAEventFormatter::EmailSubject); } if (eventL.emailFromId() != eventR.emailFromId()) { reportDifference(reporter, KAEventFormatter::EmailFromId); } if (eventL.emailAddresses() != eventR.emailAddresses()) { reportDifference(reporter, KAEventFormatter::EmailTo); } if (eventL.emailBcc() != eventR.emailBcc()) { reportDifference(reporter, KAEventFormatter::EmailBcc); } if (eventL.emailMessage() != eventR.emailMessage()) { reportDifference(reporter, KAEventFormatter::EmailBody); } if (eventL.emailAttachments() != eventR.emailAttachments()) { reportDifference(reporter, KAEventFormatter::EmailAttachments); } QLocale locale; reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18nc("@label", "Item revision"), locale.toString(left.revision()), locale.toString(right.revision())); } void SerializerPluginKAlarm::reportDifference(AbstractDifferencesReporter *reporter, KAEventFormatter::Parameter id) { if (mValueL.isApplicable(id) || mValueR.isApplicable(id)) { reporter->addProperty(AbstractDifferencesReporter::ConflictMode, KAEventFormatter::label(id), mValueL.value(id), mValueR.value(id)); } } QString SerializerPluginKAlarm::extractGid(const Item &item) const { return item.hasPayload() ? item.payload().id() : QString(); } diff --git a/serializers/kaeventformatter.cpp b/serializers/kaeventformatter.cpp index 71161a4..b5658f2 100644 --- a/serializers/kaeventformatter.cpp +++ b/serializers/kaeventformatter.cpp @@ -1,447 +1,447 @@ /* * kaeventformatter.cpp - converts KAlarmCal::KAEvent properties to text - * Copyright © 2010,2011,2018 by David Jarvie + * Copyright © 2010-2019 David Jarvie * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Library General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public * License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "kaeventformatter.h" #include "kacalendar.h" #include "kaevent.h" #include "datetime.h" #include #include #include static QString trueFalse(bool value); static QString minutes(int n); static QString minutesHoursDays(int minutes); static QString dateTime(const KAlarmCal::KADateTime &); KAEventFormatter::KAEventFormatter(const KAEvent &e, bool falseForUnspecified) : mEvent(e) { if (falseForUnspecified) { mUnspecifiedValue = trueFalse(false); } } QString KAEventFormatter::label(Parameter param) { switch (param) { case Id: return i18nc("@label Unique identifier", "UID"); case AlarmType: return i18nc("@label", "Alarm type"); case AlarmCategory: return i18nc("@label", "Alarm status"); case TemplateName: return i18nc("@label", "Template name"); case CreatedTime: return i18nc("@label", "Creation time"); case StartTime: return i18nc("@label", "Start time"); case TemplateAfterTime: return i18nc("@label Start delay configured in an alarm template", "Template after time"); case Recurs: return i18nc("@label", "Recurs"); case Recurrence: return i18nc("@label", "Recurrence"); case SubRepetition: return i18nc("@label", "Sub-repetition"); case RepeatInterval: return i18nc("@label", "Sub-repetition interval"); case RepeatCount: return i18nc("@label", "Sub-repetition count"); case NextRepetition: return i18nc("@label", "Next sub-repetition"); case WorkTimeOnly: return i18nc("@label", "Work time only"); case HolidaysExcluded: return i18nc("@label", "Holidays excluded"); case NextRecurrence: return i18nc("@label", "Next recurrence"); case LateCancel: return i18nc("@label", "Late cancel"); case AutoClose: return i18nc("@label Automatically close window", "Auto close"); case CopyKOrganizer: return i18nc("@label", "Copy to KOrganizer"); case Enabled: return i18nc("@label", "Enabled"); case ReadOnly: return i18nc("@label", "Read-only"); case Archive: return i18nc("@label Whether alarm should be archived", "Archive"); case Revision: return i18nc("@label", "Revision"); case CustomProperties: return i18nc("@label", "Custom properties"); case MessageText: return i18nc("@label", "Message text"); case MessageFile: return i18nc("@label File to provide text for message", "Message file"); case FgColour: return i18nc("@label", "Foreground color"); case BgColour: return i18nc("@label", "Background color"); case Font: return i18nc("@label", "Font"); case PreAction: return i18nc("@label Shell command to execute before alarm", "Pre-alarm action"); case PreActionCancel: return i18nc("@label", "Pre-alarm action cancel"); case PreActionNoError: return i18nc("@label", "Pre-alarm action no error"); case PostAction: return i18nc("@label Shell command to execute after alarm", "Post-alarm action"); case ConfirmAck: return i18nc("@label", "Confirm acknowledgement"); - case KMailSerial: - return i18nc("@label", "KMail serial number"); + case AkonadiItem: + return i18nc("@label", "Akonadi Item ID"); case Sound: return i18nc("@label Audio method", "Sound"); case SoundRepeat: return i18nc("@label Whether audio should repeat", "Sound repeat"); case SoundVolume: return i18nc("@label", "Sound volume"); case SoundFadeVolume: return i18nc("@label", "Sound fade volume"); case SoundFadeTime: return i18nc("@label", "Sound fade time"); case Reminder: return i18nc("@label Whether the alarm has a reminder", "Reminder"); case ReminderOnce: return i18nc("@label Whether reminder is on first recurrence only", "Reminder once only"); case DeferralType: return i18nc("@label Deferral type", "Deferral"); case DeferralTime: return i18nc("@label", "Deferral time"); case DeferDefault: return i18nc("@label Default deferral delay", "Deferral default"); case DeferDefaultDate: return i18nc("@label Whether deferral time is date-only by default", "Deferral default date only"); case Command: return i18nc("@label A shell command", "Command"); case LogFile: return i18nc("@label", "Log file"); case CommandXTerm: return i18nc("@label Execute in terminal window", "Execute in terminal"); case EmailSubject: return i18nc("@label", "Email subject"); case EmailFromId: return i18nc("@label Email address", "Email sender ID"); case EmailTo: return i18nc("@label Email address", "Email to"); case EmailBcc: return i18nc("@label true/false", "Email bcc"); case EmailBody: return i18nc("@label", "Email body"); case EmailAttachments: return i18nc("@label", "Email attachments"); } return QString(); } bool KAEventFormatter::isApplicable(Parameter param) const { switch (param) { case Id: case AlarmType: case AlarmCategory: case CreatedTime: case StartTime: case Recurs: case LateCancel: case Enabled: case ReadOnly: case Archive: case Revision: case CustomProperties: case CopyKOrganizer: return true; case TemplateName: case TemplateAfterTime: return mEvent.isTemplate(); case Recurrence: case RepeatCount: case SubRepetition: case WorkTimeOnly: case HolidaysExcluded: case NextRecurrence: return mEvent.recurs(); case RepeatInterval: case NextRepetition: return mEvent.repetition(); case AutoClose: return mEvent.lateCancel(); case MessageText: return mEvent.actionSubType() == KAEvent::MESSAGE; case MessageFile: return mEvent.actionSubType() == KAEvent::FILE; case FgColour: case BgColour: case Font: case PreAction: case PostAction: case ConfirmAck: - case KMailSerial: + case AkonadiItem: case Reminder: case DeferralType: case DeferDefault: return mEvent.actionTypes() & KAEvent::ACT_DISPLAY; case ReminderOnce: return mEvent.reminderMinutes() && mEvent.recurs(); case DeferralTime: return mEvent.deferred(); case DeferDefaultDate: return mEvent.deferDefaultMinutes() > 0; case PreActionCancel: case PreActionNoError: return !mEvent.preAction().isEmpty(); case Sound: return mEvent.actionSubType() == KAEvent::MESSAGE || mEvent.actionSubType() == KAEvent::AUDIO; case SoundRepeat: return !mEvent.audioFile().isEmpty(); case SoundVolume: return mEvent.soundVolume() >= 0; case SoundFadeVolume: case SoundFadeTime: return mEvent.fadeVolume() >= 0; case Command: case LogFile: case CommandXTerm: return mEvent.actionSubType() == KAEvent::COMMAND; case EmailSubject: case EmailFromId: case EmailTo: case EmailBcc: case EmailBody: case EmailAttachments: return mEvent.actionSubType() == KAEvent::EMAIL; } return false; } QString KAEventFormatter::value(Parameter param) const { switch (param) { case Id: return mEvent.id(); case AlarmType: switch (mEvent.actionSubType()) { case KAEvent::MESSAGE: return i18nc("@info Alarm type", "Display (text)"); case KAEvent::FILE: return i18nc("@info Alarm type", "Display (file)"); case KAEvent::COMMAND: return mEvent.commandDisplay() ? i18nc("@info Alarm type", "Display (command)") : i18nc("@info Alarm type", "Command"); case KAEvent::EMAIL: return i18nc("@info Alarm type", "Email"); case KAEvent::AUDIO: return i18nc("@info Alarm type", "Audio"); } break; case AlarmCategory: switch (mEvent.category()) { case CalEvent::ACTIVE: return i18nc("@info Alarm type", "Active"); case CalEvent::ARCHIVED: return i18nc("@info Alarm type", "Archived"); case CalEvent::TEMPLATE: return i18nc("@info Alarm type", "Template"); default: break; } break; case TemplateName: return mEvent.templateName(); case CreatedTime: return mEvent.createdDateTime().toUtc().toString(QStringLiteral("%Y-%m-%d %H:%M:%SZ")); case StartTime: return dateTime(mEvent.startDateTime().kDateTime()); case TemplateAfterTime: return (mEvent.templateAfterTime() >= 0) ? QLocale().toString(mEvent.templateAfterTime()) : trueFalse(false); case Recurs: return trueFalse(mEvent.recurs()); case Recurrence: { if (mEvent.repeatAtLogin(true)) { return i18nc("@info Repeat at login", "At login until %1", dateTime(mEvent.mainDateTime().kDateTime())); } KCalCore::Event::Ptr eptr(new KCalCore::Event); mEvent.updateKCalEvent(eptr, KAEvent::UID_SET); return KCalUtils::IncidenceFormatter::recurrenceString(eptr); } case NextRecurrence: return dateTime(mEvent.mainDateTime().kDateTime()); case SubRepetition: return trueFalse(mEvent.repetition()); case RepeatInterval: return mEvent.repetitionText(true); case RepeatCount: return mEvent.repetition() ? QLocale().toString(mEvent.repetition().count()) : QString(); case NextRepetition: return mEvent.repetition() ? QLocale().toString(mEvent.nextRepetition()) : QString(); case WorkTimeOnly: return trueFalse(mEvent.workTimeOnly()); case HolidaysExcluded: return trueFalse(mEvent.holidaysExcluded()); case LateCancel: return mEvent.lateCancel() ? minutesHoursDays(mEvent.lateCancel()) : trueFalse(false); case AutoClose: return trueFalse(mEvent.lateCancel() ? mEvent.autoClose() : false); case CopyKOrganizer: return trueFalse(mEvent.copyToKOrganizer()); case Enabled: return trueFalse(mEvent.enabled()); case ReadOnly: return trueFalse(mEvent.isReadOnly()); case Archive: return trueFalse(mEvent.toBeArchived()); case Revision: return QLocale().toString(mEvent.revision()); case CustomProperties: { if (mEvent.customProperties().isEmpty()) { return QString(); } QString value; const auto customProperties = mEvent.customProperties(); for (auto it = customProperties.cbegin(), end = customProperties.cend(); it != end; ++it) { value += QString::fromLatin1(it.key()) + QLatin1String(":") + it.value() + QLatin1String(""); } return i18nc("@info", "%1", value); } case MessageText: return (mEvent.actionSubType() == KAEvent::MESSAGE) ? mEvent.cleanText() : QString(); case MessageFile: return (mEvent.actionSubType() == KAEvent::FILE) ? mEvent.cleanText() : QString(); case FgColour: return mEvent.fgColour().name(); case BgColour: return mEvent.bgColour().name(); case Font: return mEvent.useDefaultFont() ? i18nc("@info Using default font", "Default") : mEvent.font().toString(); case PreActionCancel: return trueFalse(mEvent.extraActionOptions() & KAEvent::CancelOnPreActError); case PreActionNoError: return trueFalse(mEvent.extraActionOptions() & KAEvent::CancelOnPreActError); case PreAction: return mEvent.preAction(); case PostAction: return mEvent.postAction(); case Reminder: return mEvent.reminderMinutes() ? minutesHoursDays(mEvent.reminderMinutes()) : trueFalse(false); case ReminderOnce: return trueFalse(mEvent.reminderOnceOnly()); case DeferralType: return mEvent.reminderDeferral() ? i18nc("@info", "Reminder") : trueFalse(mEvent.deferred()); case DeferralTime: return mEvent.deferred() ? dateTime(mEvent.deferDateTime().kDateTime()) : trueFalse(false); case DeferDefault: return (mEvent.deferDefaultMinutes() > 0) ? minutes(mEvent.deferDefaultMinutes()) : trueFalse(false); case DeferDefaultDate: return trueFalse(mEvent.deferDefaultDateOnly()); case ConfirmAck: return trueFalse(mEvent.confirmAck()); - case KMailSerial: - return mEvent.kmailSerialNumber() ? QLocale().toString(qulonglong(mEvent.kmailSerialNumber())) : trueFalse(false); + case AkonadiItem: + return (mEvent.akonadiItemId() >= 0) ? QLocale().toString(mEvent.akonadiItemId()) : trueFalse(false); case Sound: return !mEvent.audioFile().isEmpty() ? mEvent.audioFile() : mEvent.speak() ? i18nc("@info", "Speak") : mEvent.beep() ? i18nc("@info", "Beep") : trueFalse(false); case SoundRepeat: return trueFalse(mEvent.repeatSound()); case SoundVolume: return mEvent.soundVolume() >= 0 ? i18nc("@info Percentage", "%1%%", static_cast(mEvent.soundVolume() * 100)) : mUnspecifiedValue; case SoundFadeVolume: return mEvent.fadeVolume() >= 0 ? i18nc("@info Percentage", "%1%%", static_cast(mEvent.fadeVolume() * 100)) : mUnspecifiedValue; case SoundFadeTime: return mEvent.fadeSeconds() ? i18ncp("@info", "1 Second", "%1 Seconds", mEvent.fadeSeconds()) : mUnspecifiedValue; case Command: return (mEvent.actionSubType() == KAEvent::COMMAND) ? mEvent.cleanText() : QString(); case LogFile: return mEvent.logFile(); case CommandXTerm: return trueFalse(mEvent.commandXterm()); case EmailSubject: return mEvent.emailSubject(); case EmailFromId: return (mEvent.actionSubType() == KAEvent::EMAIL) ? QLocale().toString(mEvent.emailFromId()) : QString(); case EmailTo: return mEvent.emailAddresses(QStringLiteral(", ")); case EmailBcc: return trueFalse(mEvent.emailBcc()); case EmailBody: return mEvent.emailMessage(); case EmailAttachments: return mEvent.emailAttachments(QStringLiteral(", ")); } return i18nc("@info Error indication", "error!"); } QString trueFalse(bool value) { return value ? i18nc("@info General purpose status indication: yes or no", "Yes") : i18nc("@info General purpose status indication: yes or no", "No"); } QString minutes(int n) { return i18ncp("@info", "1 Minute", "%1 Minutes", n); } QString dateTime(const KAlarmCal::KADateTime &dt) { if (dt.isDateOnly()) { return dt.toString(QStringLiteral("%Y-%m-%d %:Z")); } else { return dt.toString(QStringLiteral("%Y-%m-%d %H:%M %:Z")); } } QString minutesHoursDays(int minutes) { if (minutes % 60) { return i18ncp("@info", "1 Minute", "%1 Minutes", minutes); } else if (minutes % 1440) { return i18ncp("@info", "1 Hour", "%1 Hours", minutes / 60); } else { return i18ncp("@info", "1 Day", "%1 Days", minutes / 1440); } } diff --git a/serializers/kaeventformatter.h b/serializers/kaeventformatter.h index 70aa593..d25f72e 100644 --- a/serializers/kaeventformatter.h +++ b/serializers/kaeventformatter.h @@ -1,115 +1,115 @@ /* * kaeventformatter.h - converts KAlarmCal::KAEvent properties to text - * Copyright © 2010-2011 by David Jarvie + * Copyright © 2010-2019 David Jarvie * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Library General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public * License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef KAEVENTFORMATTER_H #define KAEVENTFORMATTER_H #include #include using namespace KAlarmCal; class KAEventFormatter { public: // KAEvent parameter identifiers. // Note that parameters stored in Akonadi attributes are not included. enum Parameter { Id, AlarmType, AlarmCategory, TemplateName, CreatedTime, StartTime, TemplateAfterTime, Recurs, // does the event recur? Recurrence, NextRecurrence, // next alarm time excluding repetitions, including reminder/deferral SubRepetition, // is there a sub-repetition? RepeatInterval, RepeatCount, NextRepetition, // next repetition count LateCancel, AutoClose, WorkTimeOnly, HolidaysExcluded, CopyKOrganizer, Enabled, ReadOnly, Archive, Revision, CustomProperties, MessageText, MessageFile, FgColour, BgColour, Font, PreAction, PreActionCancel, PreActionNoError, PostAction, ConfirmAck, - KMailSerial, + AkonadiItem, Sound, SoundRepeat, SoundVolume, SoundFadeVolume, SoundFadeTime, Reminder, ReminderOnce, DeferralType, DeferralTime, DeferDefault, DeferDefaultDate, Command, LogFile, CommandXTerm, EmailSubject, EmailFromId, EmailTo, EmailBcc, EmailBody, EmailAttachments }; KAEventFormatter() { } KAEventFormatter(const KAEvent &e, bool falseForUnspecified); bool isApplicable(Parameter) const; QString value(Parameter) const; const KAEvent &event() const { return mEvent; } static QString label(Parameter); private: KAEvent mEvent; QString mUnspecifiedValue; }; #endif // KAEVENTFORMATTER_H diff --git a/src/alarmtext.cpp b/src/alarmtext.cpp index 1ca3875..6fb2dc3 100644 --- a/src/alarmtext.cpp +++ b/src/alarmtext.cpp @@ -1,593 +1,598 @@ /* * alarmtext.cpp - text/email alarm text conversion * This file is part of kalarmcal library, which provides access to KAlarm * calendar data. - * Copyright © 2004,2005,2007-2016 by David Jarvie + * Copyright © 2004-2019 David Jarvie * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as published * by the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public * License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "alarmtext.h" #include "kaevent.h" #include #include #include #include namespace { const int MAIL_FROM_LINE = 0; // line number containing From in email text const int MAIL_TO_LINE = 1; // line number containing To in email text const int MAIL_CC_LINE = 2; // line number containing CC in email text const int MAIL_MIN_LINES = 4; // allow for From, To, no CC, Date, Subject } namespace KAlarmCal { class Q_DECL_HIDDEN AlarmText::Private { public: enum Type { None, Email, Script, Todo }; QString displayText() const; void clear(); static void initialise(); static void setUpTranslations(); static int emailHeaderCount(const QStringList &); static QString todoTitle(const QString &text); static QString mFromPrefix; // translated header prefixes static QString mToPrefix; static QString mCcPrefix; static QString mDatePrefix; static QString mSubjectPrefix; static QString mTitlePrefix; static QString mLocnPrefix; static QString mDuePrefix; static QString mFromPrefixEn; // untranslated header prefixes static QString mToPrefixEn; static QString mCcPrefixEn; static QString mDatePrefixEn; static QString mSubjectPrefixEn; static bool mInitialised; - QString mBody, mFrom, mTo, mCc, mTime, mSubject; - unsigned long mKMailSerialNum; // if email, message's KMail serial number, else 0 - Type mType; - bool mIsEmail; + QString mBody, mFrom, mTo, mCc, mTime, mSubject; + Akonadi::Item::Id mAkonadiItemId; // if email, message's Akonadi item ID, else -1 + Type mType; + bool mIsEmail; }; QString AlarmText::Private::mFromPrefix; QString AlarmText::Private::mToPrefix; QString AlarmText::Private::mCcPrefix; QString AlarmText::Private::mDatePrefix; QString AlarmText::Private::mSubjectPrefix; QString AlarmText::Private::mTitlePrefix; QString AlarmText::Private::mLocnPrefix; QString AlarmText::Private::mDuePrefix; QString AlarmText::Private::mFromPrefixEn; QString AlarmText::Private::mToPrefixEn; QString AlarmText::Private::mCcPrefixEn; QString AlarmText::Private::mDatePrefixEn; QString AlarmText::Private::mSubjectPrefixEn; bool AlarmText::Private::mInitialised = false; void AlarmText::Private::initialise() { if (!mInitialised) { mInitialised = true; mFromPrefixEn = QStringLiteral("From:"); mToPrefixEn = QStringLiteral("To:"); mCcPrefixEn = QStringLiteral("Cc:"); mDatePrefixEn = QStringLiteral("Date:"); mSubjectPrefixEn = QStringLiteral("Subject:"); } } AlarmText::AlarmText(const QString &text) : d(new Private) { Private::initialise(); setText(text); } AlarmText::AlarmText(const AlarmText &other) : d(new Private(*other.d)) { } AlarmText::~AlarmText() { delete d; } AlarmText &AlarmText::operator=(const AlarmText &other) { if (&other != this) { *d = *other.d; } return *this; } void AlarmText::setText(const QString &text) { d->clear(); d->mBody = text; if (text.startsWith(QLatin1String("#!"))) { d->mType = Private::Script; } } void AlarmText::setScript(const QString &text) { setText(text); d->mType = Private::Script; } void AlarmText::setEmail(const QString &to, const QString &from, const QString &cc, const QString &time, - const QString &subject, const QString &body, unsigned long kmailSerialNumber) + const QString &subject, const QString &body, Akonadi::Item::Id itemId) { d->clear(); - d->mType = Private::Email; - d->mTo = to; - d->mFrom = from; - d->mCc = cc; - d->mTime = time; - d->mSubject = subject; - d->mBody = body; - d->mKMailSerialNum = kmailSerialNumber; + d->mType = Private::Email; + d->mTo = to; + d->mFrom = from; + d->mCc = cc; + d->mTime = time; + d->mSubject = subject; + d->mBody = body; + d->mAkonadiItemId = itemId; } void AlarmText::setTodo(const KCalCore::Todo::Ptr &todo) { d->clear(); d->mType = Private::Todo; d->mSubject = todo->summary(); d->mBody = todo->description(); d->mTo = todo->location(); if (todo->hasDueDate()) { QDateTime due = todo->dtDue(false); // fetch the next due date if (todo->hasStartDate() && todo->dtStart() != due) { d->mTime = todo->allDay() ? QLocale().toString(due.date(), QLocale::ShortFormat) : QLocale().toString(due, QLocale::ShortFormat); } } } /****************************************************************************** * Return the text for a text message alarm, in display format. */ QString AlarmText::displayText() const { return d->displayText(); } QString AlarmText::Private::displayText() const { QString text; switch (mType) { case Email: // Format the email into a text alarm setUpTranslations(); text = mFromPrefix + QLatin1Char('\t') + mFrom + QLatin1Char('\n'); text += mToPrefix + QLatin1Char('\t') + mTo + QLatin1Char('\n'); if (!mCc.isEmpty()) { text += mCcPrefix + QLatin1Char('\t') + mCc + QLatin1Char('\n'); } if (!mTime.isEmpty()) { text += mDatePrefix + QLatin1Char('\t') + mTime + QLatin1Char('\n'); } text += mSubjectPrefix + QLatin1Char('\t') + mSubject; if (!mBody.isEmpty()) { text += QLatin1String("\n\n"); text += mBody; } break; case Todo: // Format the todo into a text alarm setUpTranslations(); if (!mSubject.isEmpty()) { text = mTitlePrefix + QLatin1Char('\t') + mSubject + QLatin1Char('\n'); } if (!mTo.isEmpty()) { text += mLocnPrefix + QLatin1Char('\t') + mTo + QLatin1Char('\n'); } if (!mTime.isEmpty()) { text += mDuePrefix + QLatin1Char('\t') + mTime + QLatin1Char('\n'); } if (!mBody.isEmpty()) { if (!text.isEmpty()) { text += QLatin1Char('\n'); } text += mBody; } break; default: break; } return !text.isEmpty() ? text : mBody; } QString AlarmText::to() const { return (d->mType == Private::Email) ? d->mTo : QString(); } QString AlarmText::from() const { return (d->mType == Private::Email) ? d->mFrom : QString(); } QString AlarmText::cc() const { return (d->mType == Private::Email) ? d->mCc : QString(); } QString AlarmText::time() const { return (d->mType == Private::Email) ? d->mTime : QString(); } QString AlarmText::subject() const { return (d->mType == Private::Email) ? d->mSubject : QString(); } QString AlarmText::body() const { return (d->mType == Private::Email) ? d->mBody : QString(); } QString AlarmText::summary() const { return (d->mType == Private::Todo) ? d->mSubject : QString(); } QString AlarmText::location() const { return (d->mType == Private::Todo) ? d->mTo : QString(); } QString AlarmText::due() const { return (d->mType == Private::Todo) ? d->mTime : QString(); } QString AlarmText::description() const { return (d->mType == Private::Todo) ? d->mBody : QString(); } /****************************************************************************** * Return whether there is any text. */ bool AlarmText::isEmpty() const { if (!d->mBody.isEmpty()) { return false; } if (d->mType != Private::Email) { return true; } return d->mFrom.isEmpty() && d->mTo.isEmpty() && d->mCc.isEmpty() && d->mTime.isEmpty() && d->mSubject.isEmpty(); } bool AlarmText::isEmail() const { return d->mType == Private::Email; } bool AlarmText::isScript() const { return d->mType == Private::Script; } bool AlarmText::isTodo() const { return d->mType == Private::Todo; } unsigned long AlarmText::kmailSerialNumber() const { - return d->mKMailSerialNum; + return static_cast(akonadiItemId()); +} + +Akonadi::Item::Id AlarmText::akonadiItemId() const +{ + return d->mAkonadiItemId; } /****************************************************************************** * Return the alarm summary text for either single line or tooltip display. * The maximum number of line returned is determined by 'maxLines'. * If 'truncated' is non-null, it will be set true if the text returned has been * truncated, other than to strip a trailing newline. */ QString AlarmText::summary(const KAEvent &event, int maxLines, bool *truncated) { static const QRegExp localfile(QStringLiteral("^file:/+")); QString text; switch (event.actionSubType()) { case KAEvent::AUDIO: text = event.audioFile(); if (localfile.indexIn(text) >= 0) { text = text.mid(localfile.matchedLength() - 1); } break; case KAEvent::EMAIL: text = event.emailSubject(); break; case KAEvent::COMMAND: text = event.cleanText(); if (localfile.indexIn(text) >= 0) { text = text.mid(localfile.matchedLength() - 1); } break; case KAEvent::FILE: text = event.cleanText(); break; case KAEvent::MESSAGE: { text = event.cleanText(); // If the message is the text of an email, return its headers or just subject line QString subject = emailHeaders(text, (maxLines <= 1)); if (!subject.isNull()) { if (truncated) { *truncated = true; } return subject; } if (maxLines == 1) { // If the message is the text of a todo, return either the // title/description or the whole text. subject = Private::todoTitle(text); if (!subject.isEmpty()) { if (truncated) { *truncated = true; } return subject; } } break; } } if (truncated) { *truncated = false; } if (text.count(QLatin1Char('\n')) < maxLines) { return text; } int newline = -1; for (int i = 0; i < maxLines; ++i) { newline = text.indexOf(QLatin1Char('\n'), newline + 1); if (newline < 0) { return text; // not truncated after all !?! } } if (newline == static_cast(text.length()) - 1) { return text.left(newline); // text ends in newline } if (truncated) { *truncated = true; } return text.left(newline + (maxLines <= 1 ? 0 : 1)) + QLatin1String("..."); } /****************************************************************************** * Check whether a text is an email. */ bool AlarmText::checkIfEmail(const QString &text) { const QStringList lines = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); return Private::emailHeaderCount(lines); } /****************************************************************************** * Check whether a text is an email, and if so return its headers or optionally * only its subject line. * Reply = headers/subject line, or QString() if not the text of an email. */ QString AlarmText::emailHeaders(const QString &text, bool subjectOnly) { const QStringList lines = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); const int n = Private::emailHeaderCount(lines); if (!n) { return QString(); } if (subjectOnly) { return lines[n - 1].mid(Private::mSubjectPrefix.length()).trimmed(); } QString h = lines[0]; for (int i = 1; i < n; ++i) { h += QLatin1Char('\n'); h += lines[i]; } return h; } /****************************************************************************** * Translate an alarm calendar text to a display text. * Translation is needed for email texts, since the alarm calendar stores * untranslated email prefixes. * 'email' is set to indicate whether it is an email text. */ QString AlarmText::fromCalendarText(const QString &text, bool &email) { Private::initialise(); const QStringList lines = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); const int maxn = lines.count(); if (maxn >= MAIL_MIN_LINES && lines[MAIL_FROM_LINE].startsWith(Private::mFromPrefixEn) && lines[MAIL_TO_LINE].startsWith(Private::mToPrefixEn)) { int n = MAIL_CC_LINE; if (lines[MAIL_CC_LINE].startsWith(Private::mCcPrefixEn)) { ++n; } if (maxn > n + 1 && lines[n].startsWith(Private::mDatePrefixEn) && lines[n + 1].startsWith(Private::mSubjectPrefixEn)) { Private::setUpTranslations(); QString dispText; dispText = Private::mFromPrefix + lines[MAIL_FROM_LINE].mid(Private::mFromPrefixEn.length()) + QLatin1Char('\n'); dispText += Private::mToPrefix + lines[MAIL_TO_LINE].mid(Private::mToPrefixEn.length()) + QLatin1Char('\n'); if (n > MAIL_CC_LINE) { dispText += Private::mCcPrefix + lines[MAIL_CC_LINE].mid(Private::mCcPrefixEn.length()) + QLatin1Char('\n'); } dispText += Private::mDatePrefix + lines[n].mid(Private::mDatePrefixEn.length()) + QLatin1Char('\n'); dispText += Private::mSubjectPrefix + lines[n + 1].mid(Private::mSubjectPrefixEn.length()); int i = text.indexOf(Private::mSubjectPrefixEn); i = text.indexOf(QLatin1Char('\n'), i); if (i > 0) { dispText += text.midRef(i); } email = true; return dispText; } } email = false; return text; } /****************************************************************************** * Return the text for a text message alarm, in alarm calendar format. * (The prefix strings are untranslated in the calendar.) */ QString AlarmText::toCalendarText(const QString &text) { Private::setUpTranslations(); const QStringList lines = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); const int maxn = lines.count(); if (maxn >= MAIL_MIN_LINES && lines[MAIL_FROM_LINE].startsWith(Private::mFromPrefix) && lines[MAIL_TO_LINE].startsWith(Private::mToPrefix)) { int n = MAIL_CC_LINE; if (lines[MAIL_CC_LINE].startsWith(Private::mCcPrefix)) { ++n; } if (maxn > n + 1 && lines[n].startsWith(Private::mDatePrefix) && lines[n + 1].startsWith(Private::mSubjectPrefix)) { // Format the email into a text alarm QString calText; calText = Private::mFromPrefixEn + lines[MAIL_FROM_LINE].mid(Private::mFromPrefix.length()) + QLatin1Char('\n'); calText += Private::mToPrefixEn + lines[MAIL_TO_LINE].mid(Private::mToPrefix.length()) + QLatin1Char('\n'); if (n > MAIL_CC_LINE) { calText += Private::mCcPrefixEn + lines[MAIL_CC_LINE].mid(Private::mCcPrefix.length()) + QLatin1Char('\n'); } calText += Private::mDatePrefixEn + lines[n].mid(Private::mDatePrefix.length()) + QLatin1Char('\n'); calText += Private::mSubjectPrefixEn + lines[n + 1].mid(Private::mSubjectPrefix.length()); int i = text.indexOf(Private::mSubjectPrefix); i = text.indexOf(QLatin1Char('\n'), i); if (i > 0) { calText += text.midRef(i); } return calText; } } return text; } void AlarmText::Private::clear() { mType = None; mBody.clear(); mTo.clear(); mFrom.clear(); mCc.clear(); mTime.clear(); mSubject.clear(); - mKMailSerialNum = 0; + mAkonadiItemId = -1; } /****************************************************************************** * Set up messages used by executeDropEvent() and emailHeaders(). */ void AlarmText::Private::setUpTranslations() { initialise(); if (mFromPrefix.isNull()) { mFromPrefix = i18nc("@info 'From' email address", "From:"); mToPrefix = i18nc("@info Email addressee", "To:"); mCcPrefix = i18nc("@info Copy-to in email headers", "Cc:"); mDatePrefix = i18nc("@info", "Date:"); mSubjectPrefix = i18nc("@info Email subject", "Subject:"); // Todo prefixes mTitlePrefix = i18nc("@info Todo calendar item's title field", "To-do:"); mLocnPrefix = i18nc("@info Todo calendar item's location field", "Location:"); mDuePrefix = i18nc("@info Todo calendar item's due date/time", "Due:"); } } /****************************************************************************** * Check whether a text is an email. * Reply = number of email header lines, or 0 if not an email. */ int AlarmText::Private::emailHeaderCount(const QStringList &lines) { setUpTranslations(); const int maxn = lines.count(); if (maxn >= MAIL_MIN_LINES && lines[MAIL_FROM_LINE].startsWith(mFromPrefix) && lines[MAIL_TO_LINE].startsWith(mToPrefix)) { int n = MAIL_CC_LINE; if (lines[MAIL_CC_LINE].startsWith(mCcPrefix)) { ++n; } if (maxn > n + 1 && lines[n].startsWith(mDatePrefix) && lines[n + 1].startsWith(mSubjectPrefix)) { return n + 2; } } return 0; } /****************************************************************************** * Return the Todo title line, if the text is for a Todo. */ QString AlarmText::Private::todoTitle(const QString &text) { setUpTranslations(); const QStringList lines = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); int n; for (n = 0; n < lines.count() && lines[n].contains(QLatin1Char('\t')); ++n) ; if (!n || n > 3) { return QString(); } QString title; int i = 0; if (lines[i].startsWith(mTitlePrefix + QLatin1Char('\t'))) { title = lines[i].mid(mTitlePrefix.length()).trimmed(); ++i; } if (i < n && lines[i].startsWith(mLocnPrefix + QLatin1Char('\t'))) { ++i; } if (i < n && lines[i].startsWith(mDuePrefix + QLatin1Char('\t'))) { ++i; } if (i == n) { // It's a Todo text if (!title.isEmpty()) { return title; } if (n < lines.count()) { return lines[n]; } } return QString(); } } // namespace KAlarmCal // vim: et sw=4: diff --git a/src/alarmtext.h b/src/alarmtext.h index 9e87e6b..7c353b4 100644 --- a/src/alarmtext.h +++ b/src/alarmtext.h @@ -1,212 +1,220 @@ /* * alarmtext.h - text/email alarm text conversion * This file is part of kalarmcal library, which provides access to KAlarm * calendar data. - * Copyright © 2004,2005,2008-2012 by David Jarvie + * Copyright © 2004-2019 David Jarvie * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as published * by the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public * License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifndef KALARM_ALARMTEXT_H #define KALARM_ALARMTEXT_H #include "kalarmcal_export.h" #include +#include #include namespace KAlarmCal { class KAEvent; /** * @short Parses email, todo and script alarm texts. * * This class parses email, todo and script texts, enabling drag and drop of * these items to be recognised and interpreted. It also holds plain alarm * texts. * * - Email texts must contain headers (To, From, etc.) in normal RFC format. * - Todos should be in iCalendar format. * - Scripts are assumed if the alarm text starts with '#!'. * * @author David Jarvie */ class KALARMCAL_EXPORT AlarmText { public: /** Constructor which sets the alarm text. * If @p text starts with '#!', it is flagged as a script, else plain text. * @param text alarm text to set */ explicit AlarmText(const QString &text = QString()); AlarmText(const AlarmText &other); ~AlarmText(); AlarmText &operator=(const AlarmText &other); /** Set the alarm text. * If @p text starts with '#!', it is flagged as a script, else plain text. * @param text alarm text to set */ void setText(const QString &text); /** Set the instance contents to be a script. * @param text text of script to set */ void setScript(const QString &text); /** Set the instance contents to be an email. - * @param to 'To' header parameter - * @param from 'From' header parameter - * @param cc 'Cc' header parameter - * @param time 'Date' header parameter - * @param subject 'Subject' header parameter - * @param body email body text + * @param to 'To' header parameter + * @param from 'From' header parameter + * @param cc 'Cc' header parameter + * @param time 'Date' header parameter + * @param subject 'Subject' header parameter + * @param body email body text + * @param itemId Akonadi item ID of the email. */ void setEmail(const QString &to, const QString &from, const QString &cc, const QString &time, - const QString &subject, const QString &body, unsigned long kmailSerialNumber = 0); + const QString &subject, const QString &body, Akonadi::Item::Id itemId = -1); /** Set the instance contents to be a todo. * @param todo Todo instance to set as the text */ void setTodo(const KCalCore::Todo::Ptr &todo); /** Return the text for a text message alarm, in display format. * - An email is returned as a sequence of headers followed by the message body. * - A todo is returned as a subject, location and due date followed by any text. * - A script or plain text is returned without interpretation. */ QString displayText() const; /** Return the 'To' header parameter for an email alarm. * @return 'from' value, or empty if not an email text. */ QString to() const; /** Return the 'From' header parameter for an email alarm. * @return 'from' value, or empty if not an email text. */ QString from() const; /** Return the 'Cc' header parameter for an email alarm. * @return 'cc' value, or empty if not an email text. */ QString cc() const; /** Return the 'Date' header parameter for an email alarm. * @return 'date' value, or empty if not an email text. */ QString time() const; /** Return the 'Subject' header parameter for an email alarm. * @return 'subject' value, or empty if not an email text. */ QString subject() const; /** Return the email message body. * @return message body, or empty if not an email text. */ QString body() const; /** Return the summary text for a todo. * @return summary text, or empty if not a todo. */ QString summary() const; /** Return the location text for a todo. * @return location text, or empty if not a todo. */ QString location() const; /** Return the due date text for a todo. * @return due date text, or empty if not a todo. */ QString due() const; /** Return the description text for a todo. * @return description text, or empty if not a todo. */ QString description() const; - /** Return whether there is any text. */ + /** Return whether the instance has any contents. */ bool isEmpty() const; /** Return whether the instance contains the text of an email. */ bool isEmail() const; /** Return whether the instance contains the text of a script. */ bool isScript() const; /** Return whether the instance contains the text of a todo. */ bool isTodo() const; /** Return the kmail serial number of an email. * @return serial number, or 0 if none. + * @deprecated Use akonadiItemId() instead */ - unsigned long kmailSerialNumber() const; + KALARMCAL_DEPRECATED unsigned long kmailSerialNumber() const; + + /** Return the Akonadi item ID of an email. + * @return serial number, or 0 if none. + */ + Akonadi::Item::Id akonadiItemId() const; /** Return the alarm summary text for either single line or tooltip display. * @param event event whose summary text is to be returned * @param maxLines the maximum number of lines returned * @param truncated if non-null, points to a variable which will be set true * if the text returned has been truncated, other than to * strip a trailing newline, or false otherwise */ static QString summary(const KAEvent &event, int maxLines = 1, bool *truncated = nullptr); /** Return whether a text is an email, with at least To and From headers. * @param text text to check */ static bool checkIfEmail(const QString &text); /** Check whether a text is an email (with at least To and From headers), * and if so return its headers or optionally only its subject line. * @param text text to check * @param subjectOnly true to only return the subject line, * false to return all headers * @return headers/subject line, or QString() if not the text of an email. */ static QString emailHeaders(const QString &text, bool subjectOnly); /** Translate an alarm calendar text to a display text. * Translation is needed for email texts, since the alarm calendar stores * untranslated email prefixes. * @param text text to translate * @param email updated to indicate whether it is an email text */ static QString fromCalendarText(const QString &text, bool &email); /** Return the text for an alarm message text, in alarm calendar format. * (The prefix strings are untranslated in the calendar.) * @param text alarm message text */ static QString toCalendarText(const QString &text); private: //@cond PRIVATE class Private; Private *const d; //@endcond }; } // namespace KAlarmCal #endif // KALARM_ALARMTEXT_H // vim: et sw=4: diff --git a/src/kaevent.cpp b/src/kaevent.cpp index e8ab742..e366a86 100644 --- a/src/kaevent.cpp +++ b/src/kaevent.cpp @@ -1,5961 +1,5961 @@ /* * kaevent.cpp - represents calendar events * This file is part of kalarmcal library, which provides access to KAlarm * calendar data. - * Copyright © 2001-2016 by David Jarvie + * Copyright © 2001-2019 David Jarvie * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as published * by the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public * License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "kaevent.h" #include "alarmtext.h" #include "identities.h" #include "version.h" #include #include #include #include #include "kalarmcal_debug.h" using namespace KCalCore; using namespace KHolidays; namespace KAlarmCal { //============================================================================= typedef KCalCore::Person EmailAddress; class EmailAddressList : public KCalCore::Person::List { public: EmailAddressList() : KCalCore::Person::List() { } EmailAddressList(const KCalCore::Person::List &list) { operator=(list); } EmailAddressList &operator=(const KCalCore::Person::List &); operator QStringList() const; QString join(const QString &separator) const; QStringList pureAddresses() const; QString pureAddresses(const QString &separator) const; private: QString address(int index) const; }; //============================================================================= class Q_DECL_HIDDEN KAAlarm::Private { public: Private(); Action mActionType; // alarm action type Type mType; // alarm type DateTime mNextMainDateTime; // next time to display the alarm, excluding repetitions Repetition mRepetition; // sub-repetition count and interval int mNextRepeat; // repetition count of next due sub-repetition bool mRepeatAtLogin; // whether to repeat the alarm at every login bool mRecurs; // there is a recurrence rule for the alarm bool mDeferred; // whether the alarm is an extra deferred/deferred-reminder alarm bool mTimedDeferral; // if mDeferred = true: true if the deferral is timed, false if date-only }; //============================================================================= class Q_DECL_HIDDEN KAEventPrivate : public QSharedData { public: // Read-only internal flags additional to KAEvent::Flags enum values. // NOTE: If any values are added to those in KAEvent::Flags, ensure // that these values don't overlap them. enum { REMINDER = 0x100000, DEFERRAL = 0x200000, TIMED_FLAG = 0x400000, DATE_DEFERRAL = DEFERRAL, TIME_DEFERRAL = DEFERRAL | TIMED_FLAG, DISPLAYING_ = 0x800000, READ_ONLY_FLAGS = 0xF00000 //!< mask for all read-only internal values }; enum ReminderType { // current active state of reminder NO_REMINDER, // reminder is not due ACTIVE_REMINDER, // reminder is due HIDDEN_REMINDER // reminder-after is disabled due to main alarm being deferred past it }; enum DeferType { NO_DEFERRAL = 0, // there is no deferred alarm NORMAL_DEFERRAL, // the main alarm, a recurrence or a repeat is deferred REMINDER_DEFERRAL // a reminder alarm is deferred }; // Alarm types. // This uses the same scheme as KAAlarm::Type, with some extra values. // Note that the actual enum values need not be the same as in KAAlarm::Type. enum AlarmType { INVALID_ALARM = 0, // Not an alarm MAIN_ALARM = 1, // THE real alarm. Must be the first in the enumeration. REMINDER_ALARM = 0x02, // Reminder in advance of/after the main alarm DEFERRED_ALARM = 0x04, // Deferred alarm DEFERRED_REMINDER_ALARM = REMINDER_ALARM | DEFERRED_ALARM, // Deferred reminder alarm // The following values must be greater than the preceding ones, to // ensure that in ordered processing they are processed afterwards. AT_LOGIN_ALARM = 0x10, // Additional repeat-at-login trigger DISPLAYING_ALARM = 0x20, // Copy of the alarm currently being displayed // The following are extra internal KAEvent values AUDIO_ALARM = 0x30, // sound to play when displaying the alarm PRE_ACTION_ALARM = 0x40, // command to execute before displaying the alarm POST_ACTION_ALARM = 0x50 // command to execute after the alarm window is closed }; struct AlarmData { Alarm::Ptr alarm; QString cleanText; // text or audio file name QFont font; QColor bgColour, fgColour; float soundVolume; float fadeVolume; int fadeSeconds; int repeatSoundPause; int nextRepeat; uint emailFromId; KAEventPrivate::AlarmType type; KAAlarm::Action action; int displayingFlags; KAEvent::ExtraActionOptions extraActionOptions; bool speak; bool defaultFont; bool isEmailText; bool commandScript; bool timedDeferral; bool hiddenReminder; }; typedef QMap AlarmMap; KAEventPrivate(); KAEventPrivate(const KADateTime &, const QString &message, const QColor &bg, const QColor &fg, const QFont &f, KAEvent::SubAction, int lateCancel, KAEvent::Flags flags, bool changesPending = false); explicit KAEventPrivate(const KCalCore::Event::Ptr &); KAEventPrivate(const KAEventPrivate &); ~KAEventPrivate() { delete mRecurrence; } KAEventPrivate &operator=(const KAEventPrivate &e) { if (&e != this) { copy(e); } return *this; } void set(const KCalCore::Event::Ptr &); void set(const KADateTime &, const QString &message, const QColor &bg, const QColor &fg, const QFont &, KAEvent::SubAction, int lateCancel, KAEvent::Flags flags, bool changesPending = false); void setAudioFile(const QString &filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile); KAEvent::OccurType setNextOccurrence(const KADateTime &preDateTime); void setFirstRecurrence(); void setCategory(CalEvent::Type); void setRepeatAtLogin(bool); void setRepeatAtLoginTrue(bool clearReminder); void setReminder(int minutes, bool onceOnly); void activateReminderAfter(const DateTime &mainAlarmTime); void defer(const DateTime &, bool reminder, bool adjustRecurrence = false); void cancelDefer(); bool setDisplaying(const KAEventPrivate &, KAAlarm::Type, Akonadi::Collection::Id, const KADateTime &dt, bool showEdit, bool showDefer); void reinstateFromDisplaying(const KCalCore::Event::Ptr &, Akonadi::Collection::Id &, bool &showEdit, bool &showDefer); void startChanges() { ++mChangeCount; } void endChanges(); void removeExpiredAlarm(KAAlarm::Type); KAAlarm alarm(KAAlarm::Type) const; KAAlarm firstAlarm() const; KAAlarm nextAlarm(KAAlarm::Type) const; bool updateKCalEvent(const KCalCore::Event::Ptr &, KAEvent::UidAction, bool setCustomProperties = true) const; DateTime mainDateTime(bool withRepeats = false) const { return (withRepeats && mNextRepeat && mRepetition) ? DateTime(mRepetition.duration(mNextRepeat).end(mNextMainDateTime.qDateTime())) : mNextMainDateTime; } DateTime mainEndRepeatTime() const { return mRepetition ? DateTime(mRepetition.duration().end(mNextMainDateTime.qDateTime())) : mNextMainDateTime; } DateTime deferralLimit(KAEvent::DeferLimitType * = nullptr) const; KAEvent::Flags flags() const; bool isWorkingTime(const KADateTime &) const; bool setRepetition(const Repetition &); bool occursAfter(const KADateTime &preDateTime, bool includeRepetitions) const; KAEvent::OccurType nextOccurrence(const KADateTime &preDateTime, DateTime &result, KAEvent::OccurOption = KAEvent::IGNORE_REPETITION) const; KAEvent::OccurType previousOccurrence(const KADateTime &afterDateTime, DateTime &result, bool includeRepetitions = false) const; void setRecurrence(const KARecurrence &); bool setRecur(KCalCore::RecurrenceRule::PeriodType, int freq, int count, const QDate &end, KARecurrence::Feb29Type = KARecurrence::Feb29_None); bool setRecur(KCalCore::RecurrenceRule::PeriodType, int freq, int count, const KADateTime &end, KARecurrence::Feb29Type = KARecurrence::Feb29_None); KARecurrence::Type checkRecur() const; void clearRecur(); void calcTriggerTimes() const; #ifdef KDE_NO_DEBUG_OUTPUT void dumpDebug() const { } #else void dumpDebug() const; #endif static bool convertRepetition(const KCalCore::Event::Ptr &); static bool convertStartOfDay(const KCalCore::Event::Ptr &); static DateTime readDateTime(const KCalCore::Event::Ptr &, bool localZone, bool dateOnly, DateTime &start); static void readAlarms(const KCalCore::Event::Ptr &, AlarmMap *, bool cmdDisplay = false); static void readAlarm(const KCalCore::Alarm::Ptr &, AlarmData &, bool audioMain, bool cmdDisplay = false); static QSharedPointer holidays(); private: void copy(const KAEventPrivate &); bool mayOccurDailyDuringWork(const KADateTime &) const; int nextWorkRepetition(const KADateTime &pre) const; void calcNextWorkingTime(const DateTime &nextTrigger) const; DateTime nextWorkingTime() const; KAEvent::OccurType nextRecurrence(const KADateTime &preDateTime, DateTime &result) const; void setAudioAlarm(const KCalCore::Alarm::Ptr &) const; KCalCore::Alarm::Ptr initKCalAlarm(const KCalCore::Event::Ptr &, const DateTime &, const QStringList &types, AlarmType = INVALID_ALARM) const; KCalCore::Alarm::Ptr initKCalAlarm(const KCalCore::Event::Ptr &, int startOffsetSecs, const QStringList &types, AlarmType = INVALID_ALARM) const; inline void set_deferral(DeferType); inline void activate_reminder(bool activate); static int transitionIndex(const QDateTime &utc, const QTimeZone::OffsetDataList& transitions); public: static QFont mDefaultFont; // default alarm message font static QSharedPointer mHolidays; // holiday region to use static QBitArray mWorkDays; // working days of the week static QTime mWorkDayStart; // start time of the working day static QTime mWorkDayEnd; // end time of the working day static int mWorkTimeIndex; // incremented every time working days/times are changed mutable DateTime mAllTrigger; // next trigger time, including reminders, ignoring working hours mutable DateTime mMainTrigger; // next trigger time, ignoring reminders and working hours mutable DateTime mAllWorkTrigger; // next trigger time, taking account of reminders and working hours mutable DateTime mMainWorkTrigger; // next trigger time, ignoring reminders but taking account of working hours mutable KAEvent::CmdErrType mCommandError; // command execution error last time the alarm triggered QString mEventID; // UID: KCal::Event unique ID QString mTemplateName; // alarm template's name, or null if normal event QMap mCustomProperties; // KCal::Event's non-KAlarm custom properties Akonadi::Item::Id mItemId; // Akonadi::Item ID for this event mutable Akonadi::Collection::Id mCollectionId; // ID of collection containing the event, or for a displaying event, // saved collection ID (not the collection the event is in) QString mText; // message text, file URL, command, email body [or audio file for KAAlarm] QString mAudioFile; // ATTACH: audio file to play QString mPreAction; // command to execute before alarm is displayed QString mPostAction; // command to execute after alarm window is closed DateTime mStartDateTime; // DTSTART and DTEND: start and end time for event KADateTime mCreatedDateTime; // CREATED: date event was created, or saved in archive calendar DateTime mNextMainDateTime; // next time to display the alarm, excluding repetitions KADateTime mAtLoginDateTime; // repeat-at-login end time DateTime mDeferralTime; // extra time to trigger alarm (if alarm or reminder deferred) DateTime mDisplayingTime; // date/time shown in the alarm currently being displayed int mDisplayingFlags; // type of alarm which is currently being displayed (for display alarm) int mReminderMinutes; // how long in advance reminder is to be, or 0 if none (<0 for reminder AFTER the alarm) DateTime mReminderAfterTime; // if mReminderActive true, time to trigger reminder AFTER the main alarm, or invalid if not pending ReminderType mReminderActive; // whether a reminder is due (before next, or after last, main alarm/recurrence) int mDeferDefaultMinutes; // default number of minutes for deferral dialog, or 0 to select time control bool mDeferDefaultDateOnly;// select date-only by default in deferral dialog int mRevision; // SEQUENCE: revision number of the original alarm, or 0 KARecurrence *mRecurrence; // RECUR: recurrence specification, or 0 if none Repetition mRepetition; // sub-repetition count and interval int mNextRepeat; // repetition count of next due sub-repetition int mAlarmCount; // number of alarms: count of !mMainExpired, mRepeatAtLogin, mDeferral, mReminderActive, mDisplaying DeferType mDeferral; // whether the alarm is an extra deferred/deferred-reminder alarm - unsigned long mKMailSerialNumber; // if email text, message's KMail serial number + Akonadi::Item::Id mAkonadiItemId; // if email text, message's Akonadi item ID int mTemplateAfterTime; // time not specified: use n minutes after default time, or -1 (applies to templates only) QColor mBgColour; // background colour of alarm message QColor mFgColour; // foreground colour of alarm message, or invalid for default QFont mFont; // font of alarm message (ignored if mUseDefaultFont true) uint mEmailFromIdentity; // standard email identity uoid for 'From' field, or empty EmailAddressList mEmailAddresses; // ATTENDEE: addresses to send email to QString mEmailSubject; // SUMMARY: subject line of email QStringList mEmailAttachments; // ATTACH: email attachment file names mutable int mChangeCount; // >0 = inhibit calling calcTriggerTimes() mutable bool mTriggerChanged; // true if need to recalculate trigger times QString mLogFile; // alarm output is to be logged to this URL float mSoundVolume; // volume for sound file (range 0 - 1), or < 0 for unspecified float mFadeVolume; // initial volume for sound file (range 0 - 1), or < 0 for no fade int mFadeSeconds; // fade time (seconds) for sound file, or 0 if none int mRepeatSoundPause; // seconds to pause between sound file repetitions, or -1 if no repetition int mLateCancel; // how many minutes late will cancel the alarm, or 0 for no cancellation bool mExcludeHolidays; // don't trigger alarms on holidays mutable QSharedPointer mExcludeHolidayRegion; // holiday region used to exclude alarms on holidays (= mHolidays when trigger calculated) mutable int mWorkTimeOnly; // non-zero to trigger alarm only during working hours (= mWorkTimeIndex when trigger calculated) KAEvent::SubAction mActionSubType; // sub-action type for the event's main alarm CalEvent::Type mCategory; // event category (active, archived, template, ...) KAEvent::ExtraActionOptions mExtraActionOptions;// options for pre- or post-alarm actions KACalendar::Compat mCompatibility; // event's storage format compatibility bool mReadOnly; // event is read-only in its original calendar file bool mConfirmAck; // alarm acknowledgement requires confirmation by user bool mUseDefaultFont; // use default message font, not mFont bool mCommandScript; // the command text is a script, not a shell command line bool mCommandXterm; // command alarm is to be executed in a terminal window bool mCommandDisplay; // command output is to be displayed in an alarm window bool mEmailBcc; // blind copy the email to the user bool mBeep; // whether to beep when the alarm is displayed bool mSpeak; // whether to speak the message when the alarm is displayed bool mCopyToKOrganizer; // KOrganizer should hold a copy of the event bool mReminderOnceOnly; // the reminder is output only for the first recurrence bool mAutoClose; // whether to close the alarm window after the late-cancel period bool mMainExpired; // main alarm has expired (in which case a deferral alarm will exist) bool mRepeatAtLogin; // whether to repeat the alarm at every login bool mArchiveRepeatAtLogin; // if now archived, original event was repeat-at-login bool mArchive; // event has triggered in the past, so archive it when closed bool mDisplaying; // whether the alarm is currently being displayed (i.e. in displaying calendar) bool mDisplayingDefer; // show Defer button (applies to displaying calendar only) bool mDisplayingEdit; // show Edit button (applies to displaying calendar only) bool mEnabled; // false if event is disabled public: static const QByteArray FLAGS_PROPERTY; static const QString DATE_ONLY_FLAG; static const QString LOCAL_ZONE_FLAG; static const QString EMAIL_BCC_FLAG; static const QString CONFIRM_ACK_FLAG; static const QString KORGANIZER_FLAG; static const QString EXCLUDE_HOLIDAYS_FLAG; static const QString WORK_TIME_ONLY_FLAG; static const QString REMINDER_ONCE_FLAG; static const QString DEFER_FLAG; static const QString LATE_CANCEL_FLAG; static const QString AUTO_CLOSE_FLAG; static const QString TEMPL_AFTER_TIME_FLAG; - static const QString KMAIL_SERNUM_FLAG; + static const QString KMAIL_ITEM_FLAG; static const QString ARCHIVE_FLAG; static const QByteArray NEXT_RECUR_PROPERTY; static const QByteArray REPEAT_PROPERTY; static const QByteArray LOG_PROPERTY; static const QString xtermURL; static const QString displayURL; static const QByteArray TYPE_PROPERTY; static const QString FILE_TYPE; static const QString AT_LOGIN_TYPE; static const QString REMINDER_TYPE; static const QString REMINDER_ONCE_TYPE; static const QString TIME_DEFERRAL_TYPE; static const QString DATE_DEFERRAL_TYPE; static const QString DISPLAYING_TYPE; static const QString PRE_ACTION_TYPE; static const QString POST_ACTION_TYPE; static const QString SOUND_REPEAT_TYPE; static const QByteArray NEXT_REPEAT_PROPERTY; static const QString HIDDEN_REMINDER_FLAG; static const QByteArray FONT_COLOUR_PROPERTY; static const QByteArray VOLUME_PROPERTY; static const QString EMAIL_ID_FLAG; static const QString SPEAK_FLAG; static const QString EXEC_ON_DEFERRAL_FLAG; static const QString CANCEL_ON_ERROR_FLAG; static const QString DONT_SHOW_ERROR_FLAG; static const QString DISABLED_STATUS; static const QString DISP_DEFER; static const QString DISP_EDIT; static const QString CMD_ERROR_VALUE; static const QString CMD_ERROR_PRE_VALUE; static const QString CMD_ERROR_POST_VALUE; static const QString SC; }; //============================================================================= // KAlarm version which first used the current calendar/event format. // If this changes, KAEvent::convertKCalEvents() must be changed correspondingly. // The string version is the KAlarm version string used in the calendar file. QByteArray KAEvent::currentCalendarVersionString() { return QByteArray("2.7.0"); // This is NOT the KAlarmCal library .so version! } int KAEvent::currentCalendarVersion() { return Version(2, 7, 0); // This is NOT the KAlarmCal library .so version! } // Custom calendar properties. // Note that all custom property names are prefixed with X-KDE-KALARM- in the calendar file. // Event properties const QByteArray KAEventPrivate::FLAGS_PROPERTY("FLAGS"); // X-KDE-KALARM-FLAGS property const QString KAEventPrivate::DATE_ONLY_FLAG = QStringLiteral("DATE"); const QString KAEventPrivate::LOCAL_ZONE_FLAG = QStringLiteral("LOCAL"); const QString KAEventPrivate::EMAIL_BCC_FLAG = QStringLiteral("BCC"); const QString KAEventPrivate::CONFIRM_ACK_FLAG = QStringLiteral("ACKCONF"); const QString KAEventPrivate::KORGANIZER_FLAG = QStringLiteral("KORG"); const QString KAEventPrivate::EXCLUDE_HOLIDAYS_FLAG = QStringLiteral("EXHOLIDAYS"); const QString KAEventPrivate::WORK_TIME_ONLY_FLAG = QStringLiteral("WORKTIME"); const QString KAEventPrivate::REMINDER_ONCE_FLAG = QStringLiteral("ONCE"); const QString KAEventPrivate::DEFER_FLAG = QStringLiteral("DEFER"); // default defer interval for this alarm const QString KAEventPrivate::LATE_CANCEL_FLAG = QStringLiteral("LATECANCEL"); const QString KAEventPrivate::AUTO_CLOSE_FLAG = QStringLiteral("LATECLOSE"); const QString KAEventPrivate::TEMPL_AFTER_TIME_FLAG = QStringLiteral("TMPLAFTTIME"); -const QString KAEventPrivate::KMAIL_SERNUM_FLAG = QStringLiteral("KMAIL"); +const QString KAEventPrivate::KMAIL_ITEM_FLAG = QStringLiteral("KMAIL"); const QString KAEventPrivate::ARCHIVE_FLAG = QStringLiteral("ARCHIVE"); const QByteArray KAEventPrivate::NEXT_RECUR_PROPERTY("NEXTRECUR"); // X-KDE-KALARM-NEXTRECUR property const QByteArray KAEventPrivate::REPEAT_PROPERTY("REPEAT"); // X-KDE-KALARM-REPEAT property const QByteArray KAEventPrivate::LOG_PROPERTY("LOG"); // X-KDE-KALARM-LOG property const QString KAEventPrivate::xtermURL = QStringLiteral("xterm:"); const QString KAEventPrivate::displayURL = QStringLiteral("display:"); // - General alarm properties const QByteArray KAEventPrivate::TYPE_PROPERTY("TYPE"); // X-KDE-KALARM-TYPE property const QString KAEventPrivate::FILE_TYPE = QStringLiteral("FILE"); const QString KAEventPrivate::AT_LOGIN_TYPE = QStringLiteral("LOGIN"); const QString KAEventPrivate::REMINDER_TYPE = QStringLiteral("REMINDER"); const QString KAEventPrivate::TIME_DEFERRAL_TYPE = QStringLiteral("DEFERRAL"); const QString KAEventPrivate::DATE_DEFERRAL_TYPE = QStringLiteral("DATE_DEFERRAL"); const QString KAEventPrivate::DISPLAYING_TYPE = QStringLiteral("DISPLAYING"); // used only in displaying calendar const QString KAEventPrivate::PRE_ACTION_TYPE = QStringLiteral("PRE"); const QString KAEventPrivate::POST_ACTION_TYPE = QStringLiteral("POST"); const QString KAEventPrivate::SOUND_REPEAT_TYPE = QStringLiteral("SOUNDREPEAT"); const QByteArray KAEventPrivate::NEXT_REPEAT_PROPERTY("NEXTREPEAT"); // X-KDE-KALARM-NEXTREPEAT property const QString KAEventPrivate::HIDDEN_REMINDER_FLAG = QStringLiteral("HIDE"); // - Display alarm properties const QByteArray KAEventPrivate::FONT_COLOUR_PROPERTY("FONTCOLOR"); // X-KDE-KALARM-FONTCOLOR property // - Email alarm properties const QString KAEventPrivate::EMAIL_ID_FLAG = QStringLiteral("EMAILID"); // - Audio alarm properties const QByteArray KAEventPrivate::VOLUME_PROPERTY("VOLUME"); // X-KDE-KALARM-VOLUME property const QString KAEventPrivate::SPEAK_FLAG = QStringLiteral("SPEAK"); // - Command alarm properties const QString KAEventPrivate::EXEC_ON_DEFERRAL_FLAG = QStringLiteral("EXECDEFER"); const QString KAEventPrivate::CANCEL_ON_ERROR_FLAG = QStringLiteral("ERRCANCEL"); const QString KAEventPrivate::DONT_SHOW_ERROR_FLAG = QStringLiteral("ERRNOSHOW"); // Event status strings const QString KAEventPrivate::DISABLED_STATUS = QStringLiteral("DISABLED"); // Displaying event ID identifier const QString KAEventPrivate::DISP_DEFER = QStringLiteral("DEFER"); const QString KAEventPrivate::DISP_EDIT = QStringLiteral("EDIT"); // Command error strings const QString KAEventPrivate::CMD_ERROR_VALUE = QStringLiteral("MAIN"); const QString KAEventPrivate::CMD_ERROR_PRE_VALUE = QStringLiteral("PRE"); const QString KAEventPrivate::CMD_ERROR_POST_VALUE = QStringLiteral("POST"); const QString KAEventPrivate::SC = QStringLiteral(";"); QFont KAEventPrivate::mDefaultFont; QSharedPointer KAEventPrivate::mHolidays; QBitArray KAEventPrivate::mWorkDays(7); QTime KAEventPrivate::mWorkDayStart(9, 0, 0); QTime KAEventPrivate::mWorkDayEnd(17, 0, 0); int KAEventPrivate::mWorkTimeIndex = 1; static void setProcedureAlarm(const Alarm::Ptr &, const QString &commandLine); static QString reminderToString(int minutes); /*============================================================================= = Class KAEvent = Corresponds to a KCal::Event instance. =============================================================================*/ inline void KAEventPrivate::activate_reminder(bool activate) { if (activate && mReminderActive != ACTIVE_REMINDER && mReminderMinutes) { if (mReminderActive == NO_REMINDER) { ++mAlarmCount; } mReminderActive = ACTIVE_REMINDER; } else if (!activate && mReminderActive != NO_REMINDER) { mReminderActive = NO_REMINDER; mReminderAfterTime = DateTime(); --mAlarmCount; } } Q_GLOBAL_STATIC_WITH_ARGS(QSharedDataPointer, emptyKAEventPrivate, (new KAEventPrivate)) KAEvent::KAEvent() : d(*emptyKAEventPrivate) { } KAEventPrivate::KAEventPrivate() : mCommandError(KAEvent::CMD_NO_ERROR), mItemId(-1), mCollectionId(-1), mReminderMinutes(0), mReminderActive(NO_REMINDER), mRevision(0), mRecurrence(nullptr), mNextRepeat(0), mAlarmCount(0), mDeferral(NO_DEFERRAL), mChangeCount(0), mTriggerChanged(false), mLateCancel(0), mWorkTimeOnly(0), mCategory(CalEvent::EMPTY), mCompatibility(KACalendar::Current), mReadOnly(false), mConfirmAck(false), mEmailBcc(false), mBeep(false), mAutoClose(false), mRepeatAtLogin(false), mDisplaying(false) { } KAEvent::KAEvent(const KADateTime &dt, const QString &message, const QColor &bg, const QColor &fg, const QFont &f, SubAction action, int lateCancel, Flags flags, bool changesPending) : d(new KAEventPrivate(dt, message, bg, fg, f, action, lateCancel, flags, changesPending)) { } KAEventPrivate::KAEventPrivate(const KADateTime &dt, const QString &message, const QColor &bg, const QColor &fg, const QFont &f, KAEvent::SubAction action, int lateCancel, KAEvent::Flags flags, bool changesPending) : mRevision(0), mRecurrence(nullptr) { set(dt, message, bg, fg, f, action, lateCancel, flags, changesPending); } KAEvent::KAEvent(const KCalCore::Event::Ptr &e) : d(new KAEventPrivate(e)) { } KAEventPrivate::KAEventPrivate(const KCalCore::Event::Ptr &e) : mRecurrence(nullptr) { set(e); } KAEventPrivate::KAEventPrivate(const KAEventPrivate &e) : QSharedData(e), mRecurrence(nullptr) { copy(e); } KAEvent::KAEvent(const KAEvent &other) : d(other.d) { } KAEvent::~KAEvent() { } KAEvent &KAEvent::operator=(const KAEvent &other) { if (&other != this) { d = other.d; } return *this; } /****************************************************************************** * Copies the data from another instance. */ void KAEventPrivate::copy(const KAEventPrivate &event) { mAllTrigger = event.mAllTrigger; mMainTrigger = event.mMainTrigger; mAllWorkTrigger = event.mAllWorkTrigger; mMainWorkTrigger = event.mMainWorkTrigger; mCommandError = event.mCommandError; mEventID = event.mEventID; mTemplateName = event.mTemplateName; mCustomProperties = event.mCustomProperties; mItemId = event.mItemId; mCollectionId = event.mCollectionId; mText = event.mText; mAudioFile = event.mAudioFile; mPreAction = event.mPreAction; mPostAction = event.mPostAction; mStartDateTime = event.mStartDateTime; mCreatedDateTime = event.mCreatedDateTime; mNextMainDateTime = event.mNextMainDateTime; mAtLoginDateTime = event.mAtLoginDateTime; mDeferralTime = event.mDeferralTime; mDisplayingTime = event.mDisplayingTime; mDisplayingFlags = event.mDisplayingFlags; mReminderMinutes = event.mReminderMinutes; mReminderAfterTime = event.mReminderAfterTime; mReminderActive = event.mReminderActive; mDeferDefaultMinutes = event.mDeferDefaultMinutes; mDeferDefaultDateOnly = event.mDeferDefaultDateOnly; mRevision = event.mRevision; mRepetition = event.mRepetition; mNextRepeat = event.mNextRepeat; mAlarmCount = event.mAlarmCount; mDeferral = event.mDeferral; - mKMailSerialNumber = event.mKMailSerialNumber; + mAkonadiItemId = event.mAkonadiItemId; mTemplateAfterTime = event.mTemplateAfterTime; mBgColour = event.mBgColour; mFgColour = event.mFgColour; mFont = event.mFont; mEmailFromIdentity = event.mEmailFromIdentity; mEmailAddresses = event.mEmailAddresses; mEmailSubject = event.mEmailSubject; mEmailAttachments = event.mEmailAttachments; mLogFile = event.mLogFile; mSoundVolume = event.mSoundVolume; mFadeVolume = event.mFadeVolume; mFadeSeconds = event.mFadeSeconds; mRepeatSoundPause = event.mRepeatSoundPause; mLateCancel = event.mLateCancel; mExcludeHolidays = event.mExcludeHolidays; mExcludeHolidayRegion = event.mExcludeHolidayRegion; mWorkTimeOnly = event.mWorkTimeOnly; mActionSubType = event.mActionSubType; mCategory = event.mCategory; mExtraActionOptions = event.mExtraActionOptions; mCompatibility = event.mCompatibility; mReadOnly = event.mReadOnly; mConfirmAck = event.mConfirmAck; mUseDefaultFont = event.mUseDefaultFont; mCommandScript = event.mCommandScript; mCommandXterm = event.mCommandXterm; mCommandDisplay = event.mCommandDisplay; mEmailBcc = event.mEmailBcc; mBeep = event.mBeep; mSpeak = event.mSpeak; mCopyToKOrganizer = event.mCopyToKOrganizer; mReminderOnceOnly = event.mReminderOnceOnly; mAutoClose = event.mAutoClose; mMainExpired = event.mMainExpired; mRepeatAtLogin = event.mRepeatAtLogin; mArchiveRepeatAtLogin = event.mArchiveRepeatAtLogin; mArchive = event.mArchive; mDisplaying = event.mDisplaying; mDisplayingDefer = event.mDisplayingDefer; mDisplayingEdit = event.mDisplayingEdit; mEnabled = event.mEnabled; mChangeCount = 0; mTriggerChanged = event.mTriggerChanged; delete mRecurrence; if (event.mRecurrence) { mRecurrence = new KARecurrence(*event.mRecurrence); } else { mRecurrence = nullptr; } } void KAEvent::set(const KCalCore::Event::Ptr &e) { d->set(e); } /****************************************************************************** * Initialise the KAEventPrivate from a KCalCore::Event. */ void KAEventPrivate::set(const KCalCore::Event::Ptr &event) { startChanges(); // Extract status from the event mCommandError = KAEvent::CMD_NO_ERROR; mEventID = event->uid(); mRevision = event->revision(); mTemplateName.clear(); mLogFile.clear(); mItemId = -1; mCollectionId = -1; mTemplateAfterTime = -1; mBeep = false; mSpeak = false; mEmailBcc = false; mCommandXterm = false; mCommandDisplay = false; mCopyToKOrganizer = false; mConfirmAck = false; mArchive = false; mReminderOnceOnly = false; mAutoClose = false; mArchiveRepeatAtLogin = false; mDisplayingDefer = false; mDisplayingEdit = false; mDeferDefaultDateOnly = false; mReminderActive = NO_REMINDER; mReminderMinutes = 0; mDeferDefaultMinutes = 0; mLateCancel = 0; - mKMailSerialNumber = 0; + mAkonadiItemId = -1; mExcludeHolidays = false; mWorkTimeOnly = 0; mChangeCount = 0; mBgColour = QColor(255, 255, 255); // missing/invalid colour - return white background mFgColour = QColor(0, 0, 0); // and black foreground mCompatibility = KACalendar::Current; mReadOnly = event->isReadOnly(); mUseDefaultFont = true; mEnabled = true; clearRecur(); QString param; bool ok; mCategory = CalEvent::status(event, ¶m); if (mCategory == CalEvent::DISPLAYING) { // It's a displaying calendar event - set values specific to displaying alarms const QStringList params = param.split(SC, QString::KeepEmptyParts); int n = params.count(); if (n) { const qlonglong id = params[0].toLongLong(&ok); if (ok) { mCollectionId = id; // original collection ID which contained the event } for (int i = 1; i < n; ++i) { if (params[i] == DISP_DEFER) { mDisplayingDefer = true; } if (params[i] == DISP_EDIT) { mDisplayingEdit = true; } } } } // Store the non-KAlarm custom properties of the event const QByteArray kalarmKey = "X-KDE-" + KACalendar::APPNAME + '-'; mCustomProperties = event->customProperties(); for (QMap::Iterator it = mCustomProperties.begin(); it != mCustomProperties.end();) { if (it.key().startsWith(kalarmKey)) { it = mCustomProperties.erase(it); } else { ++it; } } bool dateOnly = false; bool localZone = false; QStringList flags = event->customProperty(KACalendar::APPNAME, FLAGS_PROPERTY).split(SC, QString::SkipEmptyParts); flags << QString() << QString(); // to avoid having to check for end of list for (int i = 0, end = flags.count() - 1; i < end; ++i) { QString flag = flags.at(i); if (flag == DATE_ONLY_FLAG) { dateOnly = true; } else if (flag == LOCAL_ZONE_FLAG) { localZone = true; } else if (flag == CONFIRM_ACK_FLAG) { mConfirmAck = true; } else if (flag == EMAIL_BCC_FLAG) { mEmailBcc = true; } else if (flag == KORGANIZER_FLAG) { mCopyToKOrganizer = true; } else if (flag == EXCLUDE_HOLIDAYS_FLAG) { mExcludeHolidays = true; mExcludeHolidayRegion = holidays(); } else if (flag == WORK_TIME_ONLY_FLAG) { mWorkTimeOnly = 1; - } else if (flag == KMAIL_SERNUM_FLAG) { - const unsigned long n = flags.at(i + 1).toULong(&ok); + } else if (flag == KMAIL_ITEM_FLAG) { + const Akonadi::Item::Id id = flags.at(i + 1).toLongLong(&ok); if (!ok) { continue; } - mKMailSerialNumber = n; + mAkonadiItemId = id; ++i; } else if (flag == KAEventPrivate::ARCHIVE_FLAG) { mArchive = true; } else if (flag == KAEventPrivate::AT_LOGIN_TYPE) { mArchiveRepeatAtLogin = true; } else if (flag == KAEventPrivate::REMINDER_TYPE) { flag = flags.at(++i); if (flag == KAEventPrivate::REMINDER_ONCE_FLAG) { mReminderOnceOnly = true; flag = flags.at(++i); } const int len = flag.length() - 1; mReminderMinutes = -flag.leftRef(len).toInt(); // -> 0 if conversion fails switch (flag.at(len).toLatin1()) { case 'M': break; case 'H': mReminderMinutes *= 60; break; case 'D': mReminderMinutes *= 1440; break; default: mReminderMinutes = 0; break; } } else if (flag == DEFER_FLAG) { QString mins = flags.at(i + 1); if (mins.endsWith(QLatin1Char('D'))) { mDeferDefaultDateOnly = true; mins.truncate(mins.length() - 1); } const int n = static_cast(mins.toUInt(&ok)); if (!ok) { continue; } mDeferDefaultMinutes = n; ++i; } else if (flag == TEMPL_AFTER_TIME_FLAG) { const int n = static_cast(flags.at(i + 1).toUInt(&ok)); if (!ok) { continue; } mTemplateAfterTime = n; ++i; } else if (flag == LATE_CANCEL_FLAG) { mLateCancel = static_cast(flags.at(i + 1).toUInt(&ok)); if (ok) { ++i; } if (!ok || !mLateCancel) { mLateCancel = 1; // invalid parameter defaults to 1 minute } } else if (flag == AUTO_CLOSE_FLAG) { mLateCancel = static_cast(flags.at(i + 1).toUInt(&ok)); if (ok) { ++i; } if (!ok || !mLateCancel) { mLateCancel = 1; // invalid parameter defaults to 1 minute } mAutoClose = true; } } QString prop = event->customProperty(KACalendar::APPNAME, LOG_PROPERTY); if (!prop.isEmpty()) { if (prop == xtermURL) { mCommandXterm = true; } else if (prop == displayURL) { mCommandDisplay = true; } else { mLogFile = prop; } } prop = event->customProperty(KACalendar::APPNAME, REPEAT_PROPERTY); if (!prop.isEmpty()) { // This property is used only when the main alarm has expired. // If a main alarm is found, this property is ignored (see below). const QStringList list = prop.split(QLatin1Char(':')); if (list.count() >= 2) { const int interval = static_cast(list[0].toUInt()); const int count = static_cast(list[1].toUInt()); if (interval && count) { if (interval % (24 * 60)) { mRepetition.set(Duration(interval * 60, Duration::Seconds), count); } else { mRepetition.set(Duration(interval / (24 * 60), Duration::Days), count); } } } } mNextMainDateTime = readDateTime(event, localZone, dateOnly, mStartDateTime); mCreatedDateTime = KADateTime(event->created()); if (dateOnly && !mRepetition.isDaily()) { mRepetition.set(Duration(mRepetition.intervalDays(), Duration::Days)); } if (mCategory == CalEvent::TEMPLATE) { mTemplateName = event->summary(); } if (event->customStatus() == DISABLED_STATUS) { mEnabled = false; } // Extract status from the event's alarms. // First set up defaults. mActionSubType = KAEvent::MESSAGE; mMainExpired = true; mRepeatAtLogin = false; mDisplaying = false; mCommandScript = false; mExtraActionOptions = {}; mDeferral = NO_DEFERRAL; mSoundVolume = -1; mFadeVolume = -1; mRepeatSoundPause = -1; mFadeSeconds = 0; mEmailFromIdentity = 0; mReminderAfterTime = DateTime(); mText.clear(); mAudioFile.clear(); mPreAction.clear(); mPostAction.clear(); mEmailSubject.clear(); mEmailAddresses.clear(); mEmailAttachments.clear(); // Extract data from all the event's alarms and index the alarms by sequence number AlarmMap alarmMap; readAlarms(event, &alarmMap, mCommandDisplay); // Incorporate the alarms' details into the overall event mAlarmCount = 0; // initialise as invalid DateTime alTime; bool set = false; bool isEmailText = false; bool setDeferralTime = false; Duration deferralOffset; for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it) { const AlarmData &data = it.value(); const DateTime dateTime(data.alarm->hasStartOffset() ? data.alarm->startOffset().end(mNextMainDateTime.effectiveDateTime()) : data.alarm->time()); switch (data.type) { case MAIN_ALARM: mMainExpired = false; alTime = dateTime; alTime.setDateOnly(mStartDateTime.isDateOnly()); mRepetition.set(0, 0); // ignore X-KDE-KALARM-REPEAT if main alarm exists if (data.alarm->repeatCount() && !data.alarm->snoozeTime().isNull()) { mRepetition.set(data.alarm->snoozeTime(), data.alarm->repeatCount()); // values may be adjusted in setRecurrence() mNextRepeat = data.nextRepeat; } if (data.action != KAAlarm::AUDIO) { break; } // Fall through to AUDIO_ALARM Q_FALLTHROUGH(); case AUDIO_ALARM: mAudioFile = data.cleanText; mSpeak = data.speak && mAudioFile.isEmpty(); mBeep = !mSpeak && mAudioFile.isEmpty(); mSoundVolume = (!mBeep && !mSpeak) ? data.soundVolume : -1; mFadeVolume = (mSoundVolume >= 0 && data.fadeSeconds > 0) ? data.fadeVolume : -1; mFadeSeconds = (mFadeVolume >= 0) ? data.fadeSeconds : 0; mRepeatSoundPause = (!mBeep && !mSpeak) ? data.repeatSoundPause : -1; break; case AT_LOGIN_ALARM: mRepeatAtLogin = true; mAtLoginDateTime = dateTime.kDateTime(); alTime = mAtLoginDateTime; break; case REMINDER_ALARM: // N.B. there can be a start offset but no valid date/time (e.g. in template) if (data.alarm->startOffset().asSeconds() / 60) { mReminderActive = ACTIVE_REMINDER; if (mReminderMinutes < 0) { mReminderAfterTime = dateTime; // the reminder is AFTER the main alarm mReminderAfterTime.setDateOnly(dateOnly); if (data.hiddenReminder) { mReminderActive = HIDDEN_REMINDER; } } } break; case DEFERRED_REMINDER_ALARM: case DEFERRED_ALARM: mDeferral = (data.type == DEFERRED_REMINDER_ALARM) ? REMINDER_DEFERRAL : NORMAL_DEFERRAL; if (data.timedDeferral) { // Don't use start-of-day time for applying timed deferral alarm offset mDeferralTime = DateTime(data.alarm->hasStartOffset() ? data.alarm->startOffset().end(mNextMainDateTime.calendarDateTime()) : data.alarm->time()); } else { mDeferralTime = dateTime; mDeferralTime.setDateOnly(true); } if (data.alarm->hasStartOffset()) { deferralOffset = data.alarm->startOffset(); } break; case DISPLAYING_ALARM: { mDisplaying = true; mDisplayingFlags = data.displayingFlags; const bool dateOnly = (mDisplayingFlags & DEFERRAL) ? !(mDisplayingFlags & TIMED_FLAG) : mStartDateTime.isDateOnly(); mDisplayingTime = dateTime; mDisplayingTime.setDateOnly(dateOnly); alTime = mDisplayingTime; break; } case PRE_ACTION_ALARM: mPreAction = data.cleanText; mExtraActionOptions = data.extraActionOptions; break; case POST_ACTION_ALARM: mPostAction = data.cleanText; break; case INVALID_ALARM: default: break; } bool noSetNextTime = false; switch (data.type) { case DEFERRED_REMINDER_ALARM: case DEFERRED_ALARM: if (!set) { // The recurrence has to be evaluated before we can // calculate the time of a deferral alarm. setDeferralTime = true; noSetNextTime = true; } // fall through to REMINDER_ALARM Q_FALLTHROUGH(); case REMINDER_ALARM: case AT_LOGIN_ALARM: case DISPLAYING_ALARM: if (!set && !noSetNextTime) { mNextMainDateTime = alTime; } // fall through to MAIN_ALARM Q_FALLTHROUGH(); case MAIN_ALARM: // Ensure that the basic fields are set up even if there is no main // alarm in the event (if it has expired and then been deferred) if (!set) { mActionSubType = static_cast(data.action); mText = (mActionSubType == KAEvent::COMMAND) ? data.cleanText.trimmed() : data.cleanText; switch (data.action) { case KAAlarm::COMMAND: mCommandScript = data.commandScript; if (!mCommandDisplay) { break; } // fall through to MESSAGE Q_FALLTHROUGH(); case KAAlarm::MESSAGE: mFont = data.font; mUseDefaultFont = data.defaultFont; if (data.isEmailText) { isEmailText = true; } // fall through to FILE Q_FALLTHROUGH(); case KAAlarm::FILE: mBgColour = data.bgColour; mFgColour = data.fgColour; break; case KAAlarm::EMAIL: mEmailFromIdentity = data.emailFromId; mEmailAddresses = data.alarm->mailAddresses(); mEmailSubject = data.alarm->mailSubject(); mEmailAttachments = data.alarm->mailAttachments(); break; case KAAlarm::AUDIO: // Already mostly handled above mRepeatSoundPause = data.repeatSoundPause; break; default: break; } set = true; } if (data.action == KAAlarm::FILE && mActionSubType == KAEvent::MESSAGE) { mActionSubType = KAEvent::FILE; } ++mAlarmCount; break; case AUDIO_ALARM: case PRE_ACTION_ALARM: case POST_ACTION_ALARM: case INVALID_ALARM: default: break; } } if (!isEmailText) { - mKMailSerialNumber = 0; + mAkonadiItemId = -1; } Recurrence *recur = event->recurrence(); if (recur && recur->recurs()) { const int nextRepeat = mNextRepeat; // setRecurrence() clears mNextRepeat setRecurrence(*recur); if (nextRepeat <= mRepetition.count()) { mNextRepeat = nextRepeat; } } else if (mRepetition) { // Convert a repetition with no recurrence into a recurrence if (mRepetition.isDaily()) { setRecur(RecurrenceRule::rDaily, mRepetition.intervalDays(), mRepetition.count() + 1, QDate()); } else { setRecur(RecurrenceRule::rMinutely, mRepetition.intervalMinutes(), mRepetition.count() + 1, KADateTime()); } mRepetition.set(0, 0); mTriggerChanged = true; } if (mRepeatAtLogin) { mArchiveRepeatAtLogin = false; if (mReminderMinutes > 0) { mReminderMinutes = 0; // pre-alarm reminder not allowed for at-login alarm mReminderActive = NO_REMINDER; } setRepeatAtLoginTrue(false); // clear other incompatible statuses } if (mMainExpired && !deferralOffset.isNull() && checkRecur() != KARecurrence::NO_RECUR) { // Adjust the deferral time for an expired recurrence, since the // offset is relative to the first actual occurrence. DateTime dt = mRecurrence->getNextDateTime(mStartDateTime.addDays(-1).kDateTime()); dt.setDateOnly(mStartDateTime.isDateOnly()); if (mDeferralTime.isDateOnly()) { mDeferralTime = DateTime(deferralOffset.end(dt.qDateTime())); mDeferralTime.setDateOnly(true); } else { mDeferralTime = DateTime(deferralOffset.end(dt.effectiveDateTime())); } } if (mDeferral != NO_DEFERRAL) { if (setDeferralTime) { mNextMainDateTime = mDeferralTime; } } mTriggerChanged = true; endChanges(); } void KAEvent::set(const KADateTime &dt, const QString &message, const QColor &bg, const QColor &fg, const QFont &f, SubAction act, int lateCancel, Flags flags, bool changesPending) { d->set(dt, message, bg, fg, f, act, lateCancel, flags, changesPending); } /****************************************************************************** * Initialise the instance with the specified parameters. */ void KAEventPrivate::set(const KADateTime &dateTime, const QString &text, const QColor &bg, const QColor &fg, const QFont &font, KAEvent::SubAction action, int lateCancel, KAEvent::Flags flags, bool changesPending) { clearRecur(); mStartDateTime = dateTime; if (flags & KAEvent::ANY_TIME) mStartDateTime.setDateOnly(true); mNextMainDateTime = mStartDateTime; switch (action) { case KAEvent::MESSAGE: case KAEvent::FILE: case KAEvent::COMMAND: case KAEvent::EMAIL: case KAEvent::AUDIO: mActionSubType = static_cast(action); break; default: mActionSubType = KAEvent::MESSAGE; break; } mEventID.clear(); mTemplateName.clear(); mItemId = -1; mCollectionId = -1; mPreAction.clear(); mPostAction.clear(); mText = (mActionSubType == KAEvent::COMMAND) ? text.trimmed() : (mActionSubType == KAEvent::AUDIO) ? QString() : text; mCategory = CalEvent::ACTIVE; mAudioFile = (mActionSubType == KAEvent::AUDIO) ? text : QString(); mSoundVolume = -1; mFadeVolume = -1; mTemplateAfterTime = -1; mFadeSeconds = 0; mBgColour = bg; mFgColour = fg; mFont = font; mAlarmCount = 1; mLateCancel = lateCancel; // do this before setting flags mDeferral = NO_DEFERRAL; // do this before setting flags set_deferral((flags & DEFERRAL) ? NORMAL_DEFERRAL : NO_DEFERRAL); mRepeatAtLogin = flags & KAEvent::REPEAT_AT_LOGIN; mConfirmAck = flags & KAEvent::CONFIRM_ACK; mUseDefaultFont = flags & KAEvent::DEFAULT_FONT; mCommandScript = flags & KAEvent::SCRIPT; mCommandXterm = flags & KAEvent::EXEC_IN_XTERM; mCommandDisplay = flags & KAEvent::DISPLAY_COMMAND; mCopyToKOrganizer = flags & KAEvent::COPY_KORGANIZER; mExcludeHolidays = flags & KAEvent::EXCL_HOLIDAYS; mExcludeHolidayRegion = holidays(); mWorkTimeOnly = flags & KAEvent::WORK_TIME_ONLY; mEmailBcc = flags & KAEvent::EMAIL_BCC; mEnabled = !(flags & KAEvent::DISABLED); mDisplaying = flags & DISPLAYING_; mReminderOnceOnly = flags & KAEvent::REMINDER_ONCE; mAutoClose = (flags & KAEvent::AUTO_CLOSE) && mLateCancel; mRepeatSoundPause = (flags & KAEvent::REPEAT_SOUND) ? 0 : -1; mSpeak = (flags & KAEvent::SPEAK) && action != KAEvent::AUDIO; mBeep = (flags & KAEvent::BEEP) && action != KAEvent::AUDIO && !mSpeak; if (mRepeatAtLogin) { // do this after setting other flags ++mAlarmCount; setRepeatAtLoginTrue(false); } - mKMailSerialNumber = 0; + mAkonadiItemId = -1; mReminderMinutes = 0; mDeferDefaultMinutes = 0; mDeferDefaultDateOnly = false; mArchiveRepeatAtLogin = false; mReminderActive = NO_REMINDER; mDisplaying = false; mMainExpired = false; mDisplayingDefer = false; mDisplayingEdit = false; mArchive = false; mReminderAfterTime = DateTime(); mExtraActionOptions = {}; mCompatibility = KACalendar::Current; mReadOnly = false; mCommandError = KAEvent::CMD_NO_ERROR; mChangeCount = changesPending ? 1 : 0; mTriggerChanged = true; } /****************************************************************************** * Update an existing KCal::Event with the KAEventPrivate data. * If 'setCustomProperties' is true, all the KCal::Event's existing custom * properties are cleared and replaced with the KAEvent's custom properties. If * false, the KCal::Event's non-KAlarm custom properties are left untouched. */ bool KAEvent::updateKCalEvent(const KCalCore::Event::Ptr &e, UidAction u, bool setCustomProperties) const { return d->updateKCalEvent(e, u, setCustomProperties); } bool KAEventPrivate::updateKCalEvent(const Event::Ptr &ev, KAEvent::UidAction uidact, bool setCustomProperties) const { // If it's an archived event, the event start date/time will be adjusted to its original // value instead of its next occurrence, and the expired main alarm will be reinstated. const bool archived = (mCategory == CalEvent::ARCHIVED); if (!ev || (uidact == KAEvent::UID_CHECK && !mEventID.isEmpty() && mEventID != ev->uid()) || (!mAlarmCount && (!archived || !mMainExpired))) { return false; } ev->startUpdates(); // prevent multiple update notifications checkRecur(); // ensure recurrence/repetition data is consistent const bool readOnly = ev->isReadOnly(); if (uidact == KAEvent::UID_SET) { ev->setUid(mEventID); } ev->setReadOnly(mReadOnly); ev->setTransparency(Event::Transparent); // Set up event-specific data // Set up custom properties. if (setCustomProperties) { ev->setCustomProperties(mCustomProperties); } ev->removeCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY); ev->removeCustomProperty(KACalendar::APPNAME, NEXT_RECUR_PROPERTY); ev->removeCustomProperty(KACalendar::APPNAME, REPEAT_PROPERTY); ev->removeCustomProperty(KACalendar::APPNAME, LOG_PROPERTY); QString param; if (mCategory == CalEvent::DISPLAYING) { param = QString::number(mCollectionId); // original collection ID which contained the event if (mDisplayingDefer) { param += SC + DISP_DEFER; } if (mDisplayingEdit) { param += SC + DISP_EDIT; } } CalEvent::setStatus(ev, mCategory, param); QStringList flags; if (mStartDateTime.isDateOnly()) { flags += DATE_ONLY_FLAG; } if (mStartDateTime.timeType() == KADateTime::LocalZone) { flags += LOCAL_ZONE_FLAG; } if (mConfirmAck) { flags += CONFIRM_ACK_FLAG; } if (mEmailBcc) { flags += EMAIL_BCC_FLAG; } if (mCopyToKOrganizer) { flags += KORGANIZER_FLAG; } if (mExcludeHolidays) { flags += EXCLUDE_HOLIDAYS_FLAG; } if (mWorkTimeOnly) { flags += WORK_TIME_ONLY_FLAG; } if (mLateCancel) { (flags += (mAutoClose ? AUTO_CLOSE_FLAG : LATE_CANCEL_FLAG)) += QString::number(mLateCancel); } if (mReminderMinutes) { flags += REMINDER_TYPE; if (mReminderOnceOnly) { flags += REMINDER_ONCE_FLAG; } flags += reminderToString(-mReminderMinutes); } if (mDeferDefaultMinutes) { QString param = QString::number(mDeferDefaultMinutes); if (mDeferDefaultDateOnly) { param += QLatin1Char('D'); } (flags += DEFER_FLAG) += param; } if (!mTemplateName.isEmpty() && mTemplateAfterTime >= 0) { (flags += TEMPL_AFTER_TIME_FLAG) += QString::number(mTemplateAfterTime); } - if (mKMailSerialNumber) { - (flags += KMAIL_SERNUM_FLAG) += QString::number(mKMailSerialNumber); + if (mAkonadiItemId) { + (flags += KMAIL_ITEM_FLAG) += QString::number(mAkonadiItemId); } if (mArchive && !archived) { flags += ARCHIVE_FLAG; if (mArchiveRepeatAtLogin) { flags += AT_LOGIN_TYPE; } } if (!flags.isEmpty()) { ev->setCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY, flags.join(SC)); } if (mCommandXterm) { ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, xtermURL); } else if (mCommandDisplay) { ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, displayURL); } else if (!mLogFile.isEmpty()) { ev->setCustomProperty(KACalendar::APPNAME, LOG_PROPERTY, mLogFile); } ev->setCustomStatus(mEnabled ? QString() : DISABLED_STATUS); ev->setRevision(mRevision); ev->clearAlarms(); /* Always set DTSTART as date/time, and use the category "DATE" to indicate * a date-only event, instead of calling setAllDay(). This is necessary to * allow a time zone to be specified for a date-only event. Also, KAlarm * allows the alarm to float within the 24-hour period defined by the * start-of-day time (which is user-dependent and therefore can't be * written into the calendar) rather than midnight to midnight, and there * is no RFC2445 conformant way to specify this. * RFC2445 states that alarm trigger times specified in absolute terms * (rather than relative to DTSTART or DTEND) can only be specified as a * UTC DATE-TIME value. So always use a time relative to DTSTART instead of * an absolute time. */ ev->setDtStart(mStartDateTime.calendarDateTime()); ev->setAllDay(false); ev->setDtEnd(QDateTime()); const DateTime dtMain = archived ? mStartDateTime : mNextMainDateTime; int ancillaryType = 0; // 0 = invalid, 1 = time, 2 = offset DateTime ancillaryTime; // time for ancillary alarms (pre-action, extra audio, etc) int ancillaryOffset = 0; // start offset for ancillary alarms if (!mMainExpired || archived) { /* The alarm offset must always be zero for the main alarm. To determine * which recurrence is due, the property X-KDE-KALARM_NEXTRECUR is used. * If the alarm offset was non-zero, exception dates and rules would not * work since they apply to the event time, not the alarm time. */ if (!archived && checkRecur() != KARecurrence::NO_RECUR) { QDateTime dt = mNextMainDateTime.kDateTime().toTimeSpec(mStartDateTime.timeSpec()).qDateTime(); ev->setCustomProperty(KACalendar::APPNAME, NEXT_RECUR_PROPERTY, dt.toString(mNextMainDateTime.isDateOnly() ? QStringLiteral("yyyyMMdd") : QStringLiteral("yyyyMMddThhmmss"))); } // Add the main alarm initKCalAlarm(ev, 0, QStringList(), MAIN_ALARM); ancillaryOffset = 0; ancillaryType = dtMain.isValid() ? 2 : 0; } else if (mRepetition) { // Alarm repetition is normally held in the main alarm, but since // the main alarm has expired, store in a custom property. const QString param = QStringLiteral("%1:%2").arg(mRepetition.intervalMinutes()).arg(mRepetition.count()); ev->setCustomProperty(KACalendar::APPNAME, REPEAT_PROPERTY, param); } // Add subsidiary alarms if (mRepeatAtLogin || (mArchiveRepeatAtLogin && archived)) { DateTime dtl; if (mArchiveRepeatAtLogin) { dtl = mStartDateTime.calendarKDateTime().addDays(-1); } else if (mAtLoginDateTime.isValid()) { dtl = mAtLoginDateTime; } else if (mStartDateTime.isDateOnly()) { dtl = DateTime(KADateTime::currentLocalDate().addDays(-1), mStartDateTime.timeSpec()); } else { dtl = KADateTime::currentUtcDateTime(); } initKCalAlarm(ev, dtl, QStringList(AT_LOGIN_TYPE)); if (!ancillaryType && dtl.isValid()) { ancillaryTime = dtl; ancillaryType = 1; } } // Find the base date/time for calculating alarm offsets DateTime nextDateTime = mNextMainDateTime; if (mMainExpired) { if (checkRecur() == KARecurrence::NO_RECUR) { nextDateTime = mStartDateTime; } else if (!archived) { // It's a deferral of an expired recurrence. // Need to ensure that the alarm offset is to an occurrence // which isn't excluded by an exception - otherwise, it will // never be triggered. So choose the first recurrence which // isn't an exception. KADateTime dt = mRecurrence->getNextDateTime(mStartDateTime.addDays(-1).kDateTime()); dt.setDateOnly(mStartDateTime.isDateOnly()); nextDateTime = dt; } } if (mReminderMinutes && (mReminderActive != NO_REMINDER || archived)) { int startOffset; if (mReminderMinutes < 0 && mReminderActive != NO_REMINDER) { // A reminder AFTER the main alarm is active or disabled startOffset = nextDateTime.calendarKDateTime().secsTo(mReminderAfterTime.calendarKDateTime()); } else { // A reminder BEFORE the main alarm is active startOffset = -mReminderMinutes * 60; } initKCalAlarm(ev, startOffset, QStringList(REMINDER_TYPE)); // Don't set ancillary time if the reminder AFTER is hidden by a deferral if (!ancillaryType && (mReminderActive == ACTIVE_REMINDER || archived)) { ancillaryOffset = startOffset; ancillaryType = 2; } } if (mDeferral != NO_DEFERRAL) { int startOffset; QStringList list; if (mDeferralTime.isDateOnly()) { startOffset = nextDateTime.secsTo(mDeferralTime.calendarKDateTime()); list += DATE_DEFERRAL_TYPE; } else { startOffset = nextDateTime.calendarKDateTime().secsTo(mDeferralTime.calendarKDateTime()); list += TIME_DEFERRAL_TYPE; } if (mDeferral == REMINDER_DEFERRAL) { list += REMINDER_TYPE; } initKCalAlarm(ev, startOffset, list); if (!ancillaryType && mDeferralTime.isValid()) { ancillaryOffset = startOffset; ancillaryType = 2; } } if (!mTemplateName.isEmpty()) { ev->setSummary(mTemplateName); } else if (mDisplaying) { QStringList list(DISPLAYING_TYPE); if (mDisplayingFlags & KAEvent::REPEAT_AT_LOGIN) { list += AT_LOGIN_TYPE; } else if (mDisplayingFlags & DEFERRAL) { if (mDisplayingFlags & TIMED_FLAG) { list += TIME_DEFERRAL_TYPE; } else { list += DATE_DEFERRAL_TYPE; } } if (mDisplayingFlags & REMINDER) { list += REMINDER_TYPE; } initKCalAlarm(ev, mDisplayingTime, list); if (!ancillaryType && mDisplayingTime.isValid()) { ancillaryTime = mDisplayingTime; ancillaryType = 1; } } if ((mBeep || mSpeak || !mAudioFile.isEmpty()) && mActionSubType != KAEvent::AUDIO) { // A sound is specified if (ancillaryType == 2) { initKCalAlarm(ev, ancillaryOffset, QStringList(), AUDIO_ALARM); } else { initKCalAlarm(ev, ancillaryTime, QStringList(), AUDIO_ALARM); } } if (!mPreAction.isEmpty()) { // A pre-display action is specified if (ancillaryType == 2) { initKCalAlarm(ev, ancillaryOffset, QStringList(PRE_ACTION_TYPE), PRE_ACTION_ALARM); } else { initKCalAlarm(ev, ancillaryTime, QStringList(PRE_ACTION_TYPE), PRE_ACTION_ALARM); } } if (!mPostAction.isEmpty()) { // A post-display action is specified if (ancillaryType == 2) { initKCalAlarm(ev, ancillaryOffset, QStringList(POST_ACTION_TYPE), POST_ACTION_ALARM); } else { initKCalAlarm(ev, ancillaryTime, QStringList(POST_ACTION_TYPE), POST_ACTION_ALARM); } } if (mRecurrence) { mRecurrence->writeRecurrence(*ev->recurrence()); } else { ev->clearRecurrence(); } if (mCreatedDateTime.isValid()) { ev->setCreated(mCreatedDateTime.qDateTime()); } ev->setReadOnly(readOnly); ev->endUpdates(); // finally issue an update notification return true; } /****************************************************************************** * Create a new alarm for a libkcal event, and initialise it according to the * alarm action. If 'types' is non-null, it is appended to the X-KDE-KALARM-TYPE * property value list. * NOTE: The variant taking a DateTime calculates the offset from mStartDateTime, * which is not suitable for an alarm in a recurring event. */ Alarm::Ptr KAEventPrivate::initKCalAlarm(const KCalCore::Event::Ptr &event, const DateTime &dt, const QStringList &types, AlarmType type) const { const int startOffset = dt.isDateOnly() ? mStartDateTime.secsTo(dt) : mStartDateTime.calendarKDateTime().secsTo(dt.calendarKDateTime()); return initKCalAlarm(event, startOffset, types, type); } Alarm::Ptr KAEventPrivate::initKCalAlarm(const KCalCore::Event::Ptr &event, int startOffsetSecs, const QStringList &types, AlarmType type) const { QStringList alltypes; QStringList flags; Alarm::Ptr alarm = event->newAlarm(); alarm->setEnabled(true); if (type != MAIN_ALARM) { // RFC2445 specifies that absolute alarm times must be stored as a UTC DATE-TIME value. // Set the alarm time as an offset to DTSTART for the reasons described in updateKCalEvent(). alarm->setStartOffset(startOffsetSecs); } switch (type) { case AUDIO_ALARM: setAudioAlarm(alarm); if (mSpeak) { flags << KAEventPrivate::SPEAK_FLAG; } if (mRepeatSoundPause >= 0) { // Alarm::setSnoozeTime() sets 5 seconds if duration parameter is zero, // so repeat count = -1 represents 0 pause, -2 represents non-zero pause. alarm->setRepeatCount(mRepeatSoundPause ? -2 : -1); alarm->setSnoozeTime(Duration(mRepeatSoundPause, Duration::Seconds)); } break; case PRE_ACTION_ALARM: setProcedureAlarm(alarm, mPreAction); if (mExtraActionOptions & KAEvent::ExecPreActOnDeferral) { flags << KAEventPrivate::EXEC_ON_DEFERRAL_FLAG; } if (mExtraActionOptions & KAEvent::CancelOnPreActError) { flags << KAEventPrivate::CANCEL_ON_ERROR_FLAG; } if (mExtraActionOptions & KAEvent::DontShowPreActError) { flags << KAEventPrivate::DONT_SHOW_ERROR_FLAG; } break; case POST_ACTION_ALARM: setProcedureAlarm(alarm, mPostAction); break; case MAIN_ALARM: alarm->setSnoozeTime(mRepetition.interval()); alarm->setRepeatCount(mRepetition.count()); if (mRepetition) alarm->setCustomProperty(KACalendar::APPNAME, NEXT_REPEAT_PROPERTY, QString::number(mNextRepeat)); // fall through to INVALID_ALARM Q_FALLTHROUGH(); case REMINDER_ALARM: case INVALID_ALARM: { if (types == QStringList(REMINDER_TYPE) && mReminderMinutes < 0 && mReminderActive == HIDDEN_REMINDER) { // It's a reminder AFTER the alarm which is currently disabled // due to the main alarm being deferred past it. flags << HIDDEN_REMINDER_FLAG; } bool display = false; switch (mActionSubType) { case KAEvent::FILE: alltypes += FILE_TYPE; // fall through to MESSAGE Q_FALLTHROUGH(); case KAEvent::MESSAGE: alarm->setDisplayAlarm(AlarmText::toCalendarText(mText)); display = true; break; case KAEvent::COMMAND: if (mCommandScript) { alarm->setProcedureAlarm(QString(), mText); } else { setProcedureAlarm(alarm, mText); } display = mCommandDisplay; break; case KAEvent::EMAIL: alarm->setEmailAlarm(mEmailSubject, mText, mEmailAddresses, mEmailAttachments); if (mEmailFromIdentity) { flags << KAEventPrivate::EMAIL_ID_FLAG << QString::number(mEmailFromIdentity); } break; case KAEvent::AUDIO: setAudioAlarm(alarm); if (mRepeatSoundPause >= 0 && type == MAIN_ALARM) { // Indicate repeating sound in the main alarm by a non-standard // method, since it might have a sub-repetition too. alltypes << SOUND_REPEAT_TYPE << QString::number(mRepeatSoundPause); } break; } if (display) alarm->setCustomProperty(KACalendar::APPNAME, FONT_COLOUR_PROPERTY, QStringLiteral("%1;%2;%3").arg(mBgColour.name(), mFgColour.name(), mUseDefaultFont ? QString() : mFont.toString())); break; } case DEFERRED_ALARM: case DEFERRED_REMINDER_ALARM: case AT_LOGIN_ALARM: case DISPLAYING_ALARM: break; } alltypes += types; if (!alltypes.isEmpty()) { alarm->setCustomProperty(KACalendar::APPNAME, TYPE_PROPERTY, alltypes.join(QStringLiteral(","))); } if (!flags.isEmpty()) { alarm->setCustomProperty(KACalendar::APPNAME, FLAGS_PROPERTY, flags.join(SC)); } return alarm; } /****************************************************************************** * Find the index to the last daylight savings time transition at or before a * given UTC time. * Returns index, or -1 if before the first transition. */ int KAEventPrivate::transitionIndex(const QDateTime &utc, const QTimeZone::OffsetDataList& transitions) { if (utc.timeSpec() != Qt::UTC || transitions.isEmpty()) return -1; int start = 0; int end = transitions.size() - 1; while (start != end) { int i = (start + end + 1) / 2; if (transitions[i].atUtc == utc) return i; if (transitions[i].atUtc > utc) { end = i - 1; if (end < 0) return -1; } else start = i; } return start; } bool KAEvent::isValid() const { return d->mAlarmCount && (d->mAlarmCount != 1 || !d->mRepeatAtLogin); } void KAEvent::setEnabled(bool enable) { d->mEnabled = enable; } bool KAEvent::enabled() const { return d->mEnabled; } void KAEvent::setReadOnly(bool ro) { d->mReadOnly = ro; } bool KAEvent::isReadOnly() const { return d->mReadOnly; } void KAEvent::setArchive() { d->mArchive = true; } bool KAEvent::toBeArchived() const { return d->mArchive; } bool KAEvent::mainExpired() const { return d->mMainExpired; } bool KAEvent::expired() const { return (d->mDisplaying && d->mMainExpired) || d->mCategory == CalEvent::ARCHIVED; } KAEvent::Flags KAEvent::flags() const { return d->flags(); } KAEvent::Flags KAEventPrivate::flags() const { KAEvent::Flags result{}; if (mBeep) { result |= KAEvent::BEEP; } if (mRepeatSoundPause >= 0) { result |= KAEvent::REPEAT_SOUND; } if (mEmailBcc) { result |= KAEvent::EMAIL_BCC; } if (mStartDateTime.isDateOnly()) { result |= KAEvent::ANY_TIME; } if (mSpeak) { result |= KAEvent::SPEAK; } if (mRepeatAtLogin) { result |= KAEvent::REPEAT_AT_LOGIN; } if (mConfirmAck) { result |= KAEvent::CONFIRM_ACK; } if (mUseDefaultFont) { result |= KAEvent::DEFAULT_FONT; } if (mCommandScript) { result |= KAEvent::SCRIPT; } if (mCommandXterm) { result |= KAEvent::EXEC_IN_XTERM; } if (mCommandDisplay) { result |= KAEvent::DISPLAY_COMMAND; } if (mCopyToKOrganizer) { result |= KAEvent::COPY_KORGANIZER; } if (mExcludeHolidays) { result |= KAEvent::EXCL_HOLIDAYS; } if (mWorkTimeOnly) { result |= KAEvent::WORK_TIME_ONLY; } if (mReminderOnceOnly) { result |= KAEvent::REMINDER_ONCE; } if (mAutoClose) { result |= KAEvent::AUTO_CLOSE; } if (!mEnabled) { result |= KAEvent::DISABLED; } return result; } /****************************************************************************** * Change the type of an event. * If it is being set to archived, set the archived indication in the event ID; * otherwise, remove the archived indication from the event ID. */ void KAEvent::setCategory(CalEvent::Type s) { d->setCategory(s); } void KAEventPrivate::setCategory(CalEvent::Type s) { if (s == mCategory) { return; } mEventID = CalEvent::uid(mEventID, s); mCategory = s; mTriggerChanged = true; // templates and archived don't have trigger times } CalEvent::Type KAEvent::category() const { return d->mCategory; } void KAEvent::setEventId(const QString &id) { d->mEventID = id; } QString KAEvent::id() const { return d->mEventID; } void KAEvent::incrementRevision() { ++d->mRevision; } int KAEvent::revision() const { return d->mRevision; } void KAEvent::setCollectionId(Akonadi::Collection::Id id) { d->mCollectionId = id; } void KAEvent::setCollectionId_const(Akonadi::Collection::Id id) const { d->mCollectionId = id; } Akonadi::Collection::Id KAEvent::collectionId() const { // A displaying alarm contains the event's original collection ID return d->mDisplaying ? -1 : d->mCollectionId; } void KAEvent::setItemId(Akonadi::Item::Id id) { d->mItemId = id; } Akonadi::Item::Id KAEvent::itemId() const { return d->mItemId; } /****************************************************************************** * Initialise an Item with the event. * Note that the event is not updated with the Item ID. * Reply = true if successful, * false if event's category does not match collection's mime types. */ bool KAEvent::setItemPayload(Akonadi::Item &item, const QStringList &collectionMimeTypes) const { QString mimetype; switch (d->mCategory) { case CalEvent::ACTIVE: mimetype = MIME_ACTIVE; break; case CalEvent::ARCHIVED: mimetype = MIME_ARCHIVED; break; case CalEvent::TEMPLATE: mimetype = MIME_TEMPLATE; break; default: Q_ASSERT(0); return false; } if (!collectionMimeTypes.contains(mimetype)) { return false; } item.setMimeType(mimetype); item.setPayload(*this); return true; } void KAEvent::setCompatibility(KACalendar::Compat c) { d->mCompatibility = c; } KACalendar::Compat KAEvent::compatibility() const { return d->mCompatibility; } QMap KAEvent::customProperties() const { return d->mCustomProperties; } KAEvent::SubAction KAEvent::actionSubType() const { return d->mActionSubType; } KAEvent::Actions KAEvent::actionTypes() const { switch (d->mActionSubType) { case MESSAGE: case FILE: return ACT_DISPLAY; case COMMAND: return d->mCommandDisplay ? ACT_DISPLAY_COMMAND : ACT_COMMAND; case EMAIL: return ACT_EMAIL; case AUDIO: return ACT_AUDIO; default: return ACT_NONE; } } void KAEvent::setLateCancel(int minutes) { if (d->mRepeatAtLogin) { minutes = 0; } d->mLateCancel = minutes; if (!minutes) { d->mAutoClose = false; } } int KAEvent::lateCancel() const { return d->mLateCancel; } void KAEvent::setAutoClose(bool ac) { d->mAutoClose = ac; } bool KAEvent::autoClose() const { return d->mAutoClose; } -void KAEvent::setKMailSerialNumber(unsigned long n) +void KAEvent::setAkonadiItemId(Akonadi::Item::Id id) { - d->mKMailSerialNumber = n; + d->mAkonadiItemId = id; } -unsigned long KAEvent::kmailSerialNumber() const +Akonadi::Item::Id KAEvent::akonadiItemId() const { - return d->mKMailSerialNumber; + return d->mAkonadiItemId; } QString KAEvent::cleanText() const { return d->mText; } QString KAEvent::message() const { return (d->mActionSubType == MESSAGE || d->mActionSubType == EMAIL) ? d->mText : QString(); } QString KAEvent::displayMessage() const { return (d->mActionSubType == MESSAGE) ? d->mText : QString(); } QString KAEvent::fileName() const { return (d->mActionSubType == FILE) ? d->mText : QString(); } QColor KAEvent::bgColour() const { return d->mBgColour; } QColor KAEvent::fgColour() const { return d->mFgColour; } void KAEvent::setDefaultFont(const QFont &f) { KAEventPrivate::mDefaultFont = f; } bool KAEvent::useDefaultFont() const { return d->mUseDefaultFont; } QFont KAEvent::font() const { return d->mUseDefaultFont ? KAEventPrivate::mDefaultFont : d->mFont; } QString KAEvent::command() const { return (d->mActionSubType == COMMAND) ? d->mText : QString(); } bool KAEvent::commandScript() const { return d->mCommandScript; } bool KAEvent::commandXterm() const { return d->mCommandXterm; } bool KAEvent::commandDisplay() const { return d->mCommandDisplay; } void KAEvent::setCommandError(CmdErrType t) const { d->mCommandError = t; } KAEvent::CmdErrType KAEvent::commandError() const { return d->mCommandError; } void KAEvent::setLogFile(const QString &logfile) { d->mLogFile = logfile; if (!logfile.isEmpty()) { d->mCommandDisplay = d->mCommandXterm = false; } } QString KAEvent::logFile() const { return d->mLogFile; } bool KAEvent::confirmAck() const { return d->mConfirmAck; } bool KAEvent::copyToKOrganizer() const { return d->mCopyToKOrganizer; } void KAEvent::setEmail(uint from, const KCalCore::Person::List &addresses, const QString &subject, const QStringList &attachments) { d->mEmailFromIdentity = from; d->mEmailAddresses = addresses; d->mEmailSubject = subject; d->mEmailAttachments = attachments; } QString KAEvent::emailMessage() const { return (d->mActionSubType == EMAIL) ? d->mText : QString(); } uint KAEvent::emailFromId() const { return d->mEmailFromIdentity; } KCalCore::Person::List KAEvent::emailAddressees() const { return d->mEmailAddresses; } QStringList KAEvent::emailAddresses() const { return static_cast(d->mEmailAddresses); } QString KAEvent::emailAddresses(const QString &sep) const { return d->mEmailAddresses.join(sep); } QString KAEvent::joinEmailAddresses(const KCalCore::Person::List &addresses, const QString &separator) { return EmailAddressList(addresses).join(separator); } QStringList KAEvent::emailPureAddresses() const { return d->mEmailAddresses.pureAddresses(); } QString KAEvent::emailPureAddresses(const QString &sep) const { return d->mEmailAddresses.pureAddresses(sep); } QString KAEvent::emailSubject() const { return d->mEmailSubject; } QStringList KAEvent::emailAttachments() const { return d->mEmailAttachments; } QString KAEvent::emailAttachments(const QString &sep) const { return d->mEmailAttachments.join(sep); } bool KAEvent::emailBcc() const { return d->mEmailBcc; } void KAEvent::setAudioFile(const QString &filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile) { d->setAudioFile(filename, volume, fadeVolume, fadeSeconds, repeatPause, allowEmptyFile); } void KAEventPrivate::setAudioFile(const QString &filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause, bool allowEmptyFile) { mAudioFile = filename; mSoundVolume = (!allowEmptyFile && filename.isEmpty()) ? -1 : volume; if (mSoundVolume >= 0) { mFadeVolume = (fadeSeconds > 0) ? fadeVolume : -1; mFadeSeconds = (mFadeVolume >= 0) ? fadeSeconds : 0; } else { mFadeVolume = -1; mFadeSeconds = 0; } mRepeatSoundPause = repeatPause; } QString KAEvent::audioFile() const { return d->mAudioFile; } float KAEvent::soundVolume() const { return d->mSoundVolume; } float KAEvent::fadeVolume() const { return d->mSoundVolume >= 0 && d->mFadeSeconds ? d->mFadeVolume : -1; } int KAEvent::fadeSeconds() const { return d->mSoundVolume >= 0 && d->mFadeVolume >= 0 ? d->mFadeSeconds : 0; } bool KAEvent::repeatSound() const { return d->mRepeatSoundPause >= 0; } int KAEvent::repeatSoundPause() const { return d->mRepeatSoundPause; } bool KAEvent::beep() const { return d->mBeep; } bool KAEvent::speak() const { return (d->mActionSubType == MESSAGE || (d->mActionSubType == COMMAND && d->mCommandDisplay)) && d->mSpeak; } /****************************************************************************** * Set the event to be an alarm template. */ void KAEvent::setTemplate(const QString &name, int afterTime) { d->setCategory(CalEvent::TEMPLATE); d->mTemplateName = name; d->mTemplateAfterTime = afterTime; d->mTriggerChanged = true; // templates and archived don't have trigger times } bool KAEvent::isTemplate() const { return !d->mTemplateName.isEmpty(); } QString KAEvent::templateName() const { return d->mTemplateName; } bool KAEvent::usingDefaultTime() const { return d->mTemplateAfterTime == 0; } int KAEvent::templateAfterTime() const { return d->mTemplateAfterTime; } void KAEvent::setActions(const QString &pre, const QString &post, ExtraActionOptions options) { d->mPreAction = pre; d->mPostAction = post; d->mExtraActionOptions = options; } QString KAEvent::preAction() const { return d->mPreAction; } QString KAEvent::postAction() const { return d->mPostAction; } KAEvent::ExtraActionOptions KAEvent::extraActionOptions() const { return d->mExtraActionOptions; } /****************************************************************************** * Set a reminder. * 'minutes' = number of minutes BEFORE the main alarm. */ void KAEvent::setReminder(int minutes, bool onceOnly) { d->setReminder(minutes, onceOnly); } void KAEventPrivate::setReminder(int minutes, bool onceOnly) { if (minutes > 0 && mRepeatAtLogin) { minutes = 0; } if (minutes != mReminderMinutes || (minutes && mReminderActive != ACTIVE_REMINDER)) { if (minutes && mReminderActive == NO_REMINDER) { ++mAlarmCount; } else if (!minutes && mReminderActive != NO_REMINDER) { --mAlarmCount; } mReminderMinutes = minutes; mReminderActive = minutes ? ACTIVE_REMINDER : NO_REMINDER; mReminderOnceOnly = onceOnly; mReminderAfterTime = DateTime(); mTriggerChanged = true; } } /****************************************************************************** * Activate the event's reminder which occurs AFTER the given main alarm time. * Reply = true if successful (i.e. reminder falls before the next main alarm). */ void KAEvent::activateReminderAfter(const DateTime &mainAlarmTime) { d->activateReminderAfter(mainAlarmTime); } void KAEventPrivate::activateReminderAfter(const DateTime &mainAlarmTime) { if (mReminderMinutes >= 0 || mReminderActive == ACTIVE_REMINDER || !mainAlarmTime.isValid()) { return; } // There is a reminder AFTER the main alarm. if (checkRecur() != KARecurrence::NO_RECUR) { // For a recurring alarm, the given alarm time must be a recurrence, not a sub-repetition. DateTime next; //???? For some unknown reason, addSecs(-1) returns the recurrence after the next, //???? so addSecs(-60) is used instead. if (nextRecurrence(mainAlarmTime.addSecs(-60).effectiveKDateTime(), next) == KAEvent::NO_OCCURRENCE || mainAlarmTime != next) { return; } } else if (!mRepeatAtLogin) { // For a non-recurring alarm, the given alarm time must be the main alarm time. if (mainAlarmTime != mStartDateTime) { return; } } const DateTime reminderTime = mainAlarmTime.addMins(-mReminderMinutes); DateTime next; if (nextOccurrence(mainAlarmTime.effectiveKDateTime(), next, KAEvent::RETURN_REPETITION) != KAEvent::NO_OCCURRENCE && reminderTime >= next) { return; // the reminder time is after the next occurrence of the main alarm } qCDebug(KALARMCAL_LOG) << "Setting reminder at" << reminderTime.effectiveKDateTime().toString(QStringLiteral("%Y-%m-%d %H:%M")); activate_reminder(true); mReminderAfterTime = reminderTime; } int KAEvent::reminderMinutes() const { return d->mReminderMinutes; } bool KAEvent::reminderActive() const { return d->mReminderActive == KAEventPrivate::ACTIVE_REMINDER; } bool KAEvent::reminderOnceOnly() const { return d->mReminderOnceOnly; } bool KAEvent::reminderDeferral() const { return d->mDeferral == KAEventPrivate::REMINDER_DEFERRAL; } /****************************************************************************** * Defer the event to the specified time. * If the main alarm time has passed, the main alarm is marked as expired. * If 'adjustRecurrence' is true, ensure that the next scheduled recurrence is * after the current time. */ void KAEvent::defer(const DateTime &dt, bool reminder, bool adjustRecurrence) { return d->defer(dt, reminder, adjustRecurrence); } void KAEventPrivate::defer(const DateTime &dateTime, bool reminder, bool adjustRecurrence) { startChanges(); // prevent multiple trigger time evaluation here bool setNextRepetition = false; bool checkRepetition = false; bool checkReminderAfter = false; if (checkRecur() == KARecurrence::NO_RECUR) { // Deferring a non-recurring alarm if (mReminderMinutes) { bool deferReminder = false; if (mReminderMinutes > 0) { // There's a reminder BEFORE the main alarm if (dateTime < mNextMainDateTime.effectiveKDateTime()) { deferReminder = true; } else if (mReminderActive == ACTIVE_REMINDER || mDeferral == REMINDER_DEFERRAL) { // Deferring past the main alarm time, so adjust any existing deferral set_deferral(NO_DEFERRAL); mTriggerChanged = true; } } else if (mReminderMinutes < 0 && reminder) { deferReminder = true; // deferring a reminder AFTER the main alarm } if (deferReminder) { set_deferral(REMINDER_DEFERRAL); // defer reminder alarm mDeferralTime = dateTime; mTriggerChanged = true; } if (mReminderActive == ACTIVE_REMINDER) { activate_reminder(false); mTriggerChanged = true; } } if (mDeferral != REMINDER_DEFERRAL) { // We're deferring the main alarm. // Main alarm has now expired. mNextMainDateTime = mDeferralTime = dateTime; set_deferral(NORMAL_DEFERRAL); mTriggerChanged = true; checkReminderAfter = true; if (!mMainExpired) { // Mark the alarm as expired now mMainExpired = true; --mAlarmCount; if (mRepeatAtLogin) { // Remove the repeat-at-login alarm, but keep a note of it for archiving purposes mArchiveRepeatAtLogin = true; mRepeatAtLogin = false; --mAlarmCount; } } } } else if (reminder) { // Deferring a reminder for a recurring alarm if (dateTime >= mNextMainDateTime.effectiveKDateTime()) { // Trying to defer it past the next main alarm (regardless of whether // the reminder triggered before or after the main alarm). set_deferral(NO_DEFERRAL); // (error) } else { set_deferral(REMINDER_DEFERRAL); mDeferralTime = dateTime; checkRepetition = true; } mTriggerChanged = true; } else { // Deferring a recurring alarm mDeferralTime = dateTime; if (mDeferral == NO_DEFERRAL) { set_deferral(NORMAL_DEFERRAL); } mTriggerChanged = true; checkReminderAfter = true; if (adjustRecurrence) { const KADateTime now = KADateTime::currentUtcDateTime(); if (mainEndRepeatTime() < now) { // The last repetition (if any) of the current recurrence has already passed. // Adjust to the next scheduled recurrence after now. if (!mMainExpired && setNextOccurrence(now) == KAEvent::NO_OCCURRENCE) { mMainExpired = true; --mAlarmCount; } } else { setNextRepetition = mRepetition; } } else { checkRepetition = true; } } if (checkReminderAfter && mReminderMinutes < 0 && mReminderActive != NO_REMINDER) { // Enable/disable the active reminder AFTER the main alarm, // depending on whether the deferral is before or after the reminder. mReminderActive = (mDeferralTime < mReminderAfterTime) ? ACTIVE_REMINDER : HIDDEN_REMINDER; } if (checkRepetition) { setNextRepetition = (mRepetition && mDeferralTime < mainEndRepeatTime()); } if (setNextRepetition) { // The alarm is repeated, and we're deferring to a time before the last repetition. // Set the next scheduled repetition to the one after the deferral. if (mNextMainDateTime >= mDeferralTime) { mNextRepeat = 0; } else { mNextRepeat = mRepetition.nextRepeatCount(mNextMainDateTime.kDateTime(), mDeferralTime.kDateTime()); } mTriggerChanged = true; } endChanges(); } /****************************************************************************** * Cancel any deferral alarm. */ void KAEvent::cancelDefer() { d->cancelDefer(); } void KAEventPrivate::cancelDefer() { if (mDeferral != NO_DEFERRAL) { mDeferralTime = DateTime(); set_deferral(NO_DEFERRAL); mTriggerChanged = true; } } void KAEvent::setDeferDefaultMinutes(int minutes, bool dateOnly) { d->mDeferDefaultMinutes = minutes; d->mDeferDefaultDateOnly = dateOnly; } bool KAEvent::deferred() const { return d->mDeferral > 0; } DateTime KAEvent::deferDateTime() const { return d->mDeferralTime; } /****************************************************************************** * Find the latest time which the alarm can currently be deferred to. */ DateTime KAEvent::deferralLimit(DeferLimitType *limitType) const { return d->deferralLimit(limitType); } DateTime KAEventPrivate::deferralLimit(KAEvent::DeferLimitType *limitType) const { KAEvent::DeferLimitType ltype = KAEvent::LIMIT_NONE; DateTime endTime; if (checkRecur() != KARecurrence::NO_RECUR) { // It's a recurring alarm. Find the latest time it can be deferred to: // it cannot be deferred past its next occurrence or sub-repetition, // or any advance reminder before that. DateTime reminderTime; const KADateTime now = KADateTime::currentUtcDateTime(); const KAEvent::OccurType type = nextOccurrence(now, endTime, KAEvent::RETURN_REPETITION); if (type & KAEvent::OCCURRENCE_REPEAT) { ltype = KAEvent::LIMIT_REPETITION; } else if (type == KAEvent::NO_OCCURRENCE) { ltype = KAEvent::LIMIT_NONE; } else if (mReminderActive == ACTIVE_REMINDER && mReminderMinutes > 0 && (now < (reminderTime = endTime.addMins(-mReminderMinutes)))) { endTime = reminderTime; ltype = KAEvent::LIMIT_REMINDER; } else { ltype = KAEvent::LIMIT_RECURRENCE; } } else if (mReminderMinutes < 0) { // There is a reminder alarm which occurs AFTER the main alarm. // Don't allow the reminder to be deferred past the next main alarm time. if (KADateTime::currentUtcDateTime() < mNextMainDateTime.effectiveKDateTime()) { endTime = mNextMainDateTime; ltype = KAEvent::LIMIT_MAIN; } } else if (mReminderMinutes > 0 && KADateTime::currentUtcDateTime() < mNextMainDateTime.effectiveKDateTime()) { // It's a reminder BEFORE the main alarm. // Don't allow it to be deferred past its main alarm time. endTime = mNextMainDateTime; ltype = KAEvent::LIMIT_MAIN; } if (ltype != KAEvent::LIMIT_NONE) { endTime = endTime.addMins(-1); } if (limitType) { *limitType = ltype; } return endTime; } int KAEvent::deferDefaultMinutes() const { return d->mDeferDefaultMinutes; } bool KAEvent::deferDefaultDateOnly() const { return d->mDeferDefaultDateOnly; } DateTime KAEvent::startDateTime() const { return d->mStartDateTime; } void KAEvent::setTime(const KADateTime &dt) { d->mNextMainDateTime = dt; d->mTriggerChanged = true; } DateTime KAEvent::mainDateTime(bool withRepeats) const { return d->mainDateTime(withRepeats); } QTime KAEvent::mainTime() const { return d->mNextMainDateTime.effectiveTime(); } DateTime KAEvent::mainEndRepeatTime() const { return d->mainEndRepeatTime(); } /****************************************************************************** * Set the start-of-day time for date-only alarms. */ void KAEvent::setStartOfDay(const QTime &startOfDay) { DateTime::setStartOfDay(startOfDay); #ifdef __GNUC__ #warning Does this need all trigger times for date-only alarms to be recalculated? #endif } /****************************************************************************** * Called when the user changes the start-of-day time. * Adjust the start time of the recurrence to match, for each date-only event in * a list. */ void KAEvent::adjustStartOfDay(const KAEvent::List &events) { for (int i = 0, end = events.count(); i < end; ++i) { KAEventPrivate *const p = events[i]->d; if (p->mStartDateTime.isDateOnly() && p->checkRecur() != KARecurrence::NO_RECUR) { p->mRecurrence->setStartDateTime(p->mStartDateTime.effectiveKDateTime(), true); } } } DateTime KAEvent::nextTrigger(TriggerType type) const { d->calcTriggerTimes(); switch (type) { case ALL_TRIGGER: return d->mAllTrigger; case MAIN_TRIGGER: return d->mMainTrigger; case ALL_WORK_TRIGGER: return d->mAllWorkTrigger; case WORK_TRIGGER: return d->mMainWorkTrigger; case DISPLAY_TRIGGER: { const bool reminderAfter = d->mMainExpired && d->mReminderActive && d->mReminderMinutes < 0; return d->checkRecur() != KARecurrence::NO_RECUR && (d->mWorkTimeOnly || d->mExcludeHolidays) ? (reminderAfter ? d->mAllWorkTrigger : d->mMainWorkTrigger) : (reminderAfter ? d->mAllTrigger : d->mMainTrigger); } default: return DateTime(); } } void KAEvent::setCreatedDateTime(const KADateTime &dt) { d->mCreatedDateTime = dt; } KADateTime KAEvent::createdDateTime() const { return d->mCreatedDateTime; } /****************************************************************************** * Set or clear repeat-at-login. */ void KAEvent::setRepeatAtLogin(bool rl) { d->setRepeatAtLogin(rl); } void KAEventPrivate::setRepeatAtLogin(bool rl) { if (rl && !mRepeatAtLogin) { setRepeatAtLoginTrue(true); // clear incompatible statuses ++mAlarmCount; } else if (!rl && mRepeatAtLogin) { --mAlarmCount; } mRepeatAtLogin = rl; mTriggerChanged = true; } /****************************************************************************** * Clear incompatible statuses when repeat-at-login is set. */ void KAEventPrivate::setRepeatAtLoginTrue(bool clearReminder) { clearRecur(); // clear recurrences if (mReminderMinutes >= 0 && clearReminder) { setReminder(0, false); // clear pre-alarm reminder } mLateCancel = 0; mAutoClose = false; mCopyToKOrganizer = false; } bool KAEvent::repeatAtLogin(bool includeArchived) const { return d->mRepeatAtLogin || (includeArchived && d->mArchiveRepeatAtLogin); } void KAEvent::setExcludeHolidays(bool ex) { d->mExcludeHolidays = ex; d->mExcludeHolidayRegion = KAEventPrivate::holidays(); // Option only affects recurring alarms d->mTriggerChanged = (d->checkRecur() != KARecurrence::NO_RECUR); } bool KAEvent::holidaysExcluded() const { return d->mExcludeHolidays; } /****************************************************************************** * Set a new holiday region. * Alarms which exclude holidays record the pointer to the holiday definition * at the time their next trigger times were last calculated. The change in * holiday definition pointer will cause their next trigger times to be * recalculated. */ void KAEvent::setHolidays(const HolidayRegion &h) { KAEventPrivate::mHolidays.reset(new HolidayRegion(h.regionCode())); } void KAEvent::setWorkTimeOnly(bool wto) { d->mWorkTimeOnly = wto; // Option only affects recurring alarms d->mTriggerChanged = (d->checkRecur() != KARecurrence::NO_RECUR); } bool KAEvent::workTimeOnly() const { return d->mWorkTimeOnly; } /****************************************************************************** * Check whether a date/time is during working hours and/or holidays, depending * on the flags set for the specified event. */ bool KAEvent::isWorkingTime(const KADateTime &dt) const { return d->isWorkingTime(dt); } bool KAEventPrivate::isWorkingTime(const KADateTime &dt) const { if ((mWorkTimeOnly && !mWorkDays.testBit(dt.date().dayOfWeek() - 1)) || (mExcludeHolidays && holidays()->isHoliday(dt.date()))) { return false; } if (!mWorkTimeOnly) { return true; } return dt.isDateOnly() || (dt.time() >= mWorkDayStart && dt.time() < mWorkDayEnd); } /****************************************************************************** * Set new working days and times. * Increment a counter so that working-time-only alarms can detect that they * need to update their next trigger time. */ void KAEvent::setWorkTime(const QBitArray &days, const QTime &start, const QTime &end) { if (days != KAEventPrivate::mWorkDays || start != KAEventPrivate::mWorkDayStart || end != KAEventPrivate::mWorkDayEnd) { KAEventPrivate::mWorkDays = days; KAEventPrivate::mWorkDayStart = start; KAEventPrivate::mWorkDayEnd = end; if (!++KAEventPrivate::mWorkTimeIndex) { ++KAEventPrivate::mWorkTimeIndex; } } } /****************************************************************************** * Clear the event's recurrence and alarm repetition data. */ void KAEvent::setNoRecur() { d->clearRecur(); } void KAEventPrivate::clearRecur() { if (mRecurrence || mRepetition) { delete mRecurrence; mRecurrence = nullptr; mRepetition.set(0, 0); mTriggerChanged = true; } mNextRepeat = 0; } /****************************************************************************** * Initialise the event's recurrence from a KCal::Recurrence. * The event's start date/time is not changed. */ void KAEvent::setRecurrence(const KARecurrence &recurrence) { d->setRecurrence(recurrence); } void KAEventPrivate::setRecurrence(const KARecurrence &recurrence) { startChanges(); // prevent multiple trigger time evaluation here if (recurrence.recurs()) { delete mRecurrence; mRecurrence = new KARecurrence(recurrence); mRecurrence->setStartDateTime(mStartDateTime.effectiveKDateTime(), mStartDateTime.isDateOnly()); mTriggerChanged = true; // Adjust sub-repetition values to fit the recurrence. setRepetition(mRepetition); } else { clearRecur(); } endChanges(); } /****************************************************************************** * Set the recurrence to recur at a minutes interval. * Parameters: * freq = how many minutes between recurrences. * count = number of occurrences, including first and last. * = -1 to recur indefinitely. * = 0 to use 'end' instead. * end = end date/time (invalid to use 'count' instead). * Reply = false if no recurrence was set up. */ bool KAEvent::setRecurMinutely(int freq, int count, const KADateTime &end) { const bool success = d->setRecur(RecurrenceRule::rMinutely, freq, count, end); d->mTriggerChanged = true; return success; } /****************************************************************************** * Set the recurrence to recur daily. * Parameters: * freq = how many days between recurrences. * days = which days of the week alarms are allowed to occur on. * count = number of occurrences, including first and last. * = -1 to recur indefinitely. * = 0 to use 'end' instead. * end = end date (invalid to use 'count' instead). * Reply = false if no recurrence was set up. */ bool KAEvent::setRecurDaily(int freq, const QBitArray &days, int count, const QDate &end) { const bool success = d->setRecur(RecurrenceRule::rDaily, freq, count, end); if (success) { int n = 0; for (int i = 0; i < 7; ++i) { if (days.testBit(i)) { ++n; } } if (n < 7) { d->mRecurrence->addWeeklyDays(days); } } d->mTriggerChanged = true; return success; } /****************************************************************************** * Set the recurrence to recur weekly, on the specified weekdays. * Parameters: * freq = how many weeks between recurrences. * days = which days of the week alarms should occur on. * count = number of occurrences, including first and last. * = -1 to recur indefinitely. * = 0 to use 'end' instead. * end = end date (invalid to use 'count' instead). * Reply = false if no recurrence was set up. */ bool KAEvent::setRecurWeekly(int freq, const QBitArray &days, int count, const QDate &end) { const bool success = d->setRecur(RecurrenceRule::rWeekly, freq, count, end); if (success) { d->mRecurrence->addWeeklyDays(days); } d->mTriggerChanged = true; return success; } /****************************************************************************** * Set the recurrence to recur monthly, on the specified days within the month. * Parameters: * freq = how many months between recurrences. * days = which days of the month alarms should occur on. * count = number of occurrences, including first and last. * = -1 to recur indefinitely. * = 0 to use 'end' instead. * end = end date (invalid to use 'count' instead). * Reply = false if no recurrence was set up. */ bool KAEvent::setRecurMonthlyByDate(int freq, const QVector &days, int count, const QDate &end) { const bool success = d->setRecur(RecurrenceRule::rMonthly, freq, count, end); if (success) { for (int i = 0, end = days.count(); i < end; ++i) { d->mRecurrence->addMonthlyDate(days[i]); } } d->mTriggerChanged = true; return success; } /****************************************************************************** * Set the recurrence to recur monthly, on the specified weekdays in the * specified weeks of the month. * Parameters: * freq = how many months between recurrences. * posns = which days of the week/weeks of the month alarms should occur on. * count = number of occurrences, including first and last. * = -1 to recur indefinitely. * = 0 to use 'end' instead. * end = end date (invalid to use 'count' instead). * Reply = false if no recurrence was set up. */ bool KAEvent::setRecurMonthlyByPos(int freq, const QVector &posns, int count, const QDate &end) { const bool success = d->setRecur(RecurrenceRule::rMonthly, freq, count, end); if (success) { for (int i = 0, end = posns.count(); i < end; ++i) { d->mRecurrence->addMonthlyPos(posns[i].weeknum, posns[i].days); } } d->mTriggerChanged = true; return success; } /****************************************************************************** * Set the recurrence to recur annually, on the specified start date in each * of the specified months. * Parameters: * freq = how many years between recurrences. * months = which months of the year alarms should occur on. * day = day of month, or 0 to use start date * feb29 = when February 29th should recur in non-leap years. * count = number of occurrences, including first and last. * = -1 to recur indefinitely. * = 0 to use 'end' instead. * end = end date (invalid to use 'count' instead). * Reply = false if no recurrence was set up. */ bool KAEvent::setRecurAnnualByDate(int freq, const QVector &months, int day, KARecurrence::Feb29Type feb29, int count, const QDate &end) { const bool success = d->setRecur(RecurrenceRule::rYearly, freq, count, end, feb29); if (success) { for (int i = 0, end = months.count(); i < end; ++i) { d->mRecurrence->addYearlyMonth(months[i]); } if (day) { d->mRecurrence->addMonthlyDate(day); } } d->mTriggerChanged = true; return success; } /****************************************************************************** * Set the recurrence to recur annually, on the specified weekdays in the * specified weeks of the specified months. * Parameters: * freq = how many years between recurrences. * posns = which days of the week/weeks of the month alarms should occur on. * months = which months of the year alarms should occur on. * count = number of occurrences, including first and last. * = -1 to recur indefinitely. * = 0 to use 'end' instead. * end = end date (invalid to use 'count' instead). * Reply = false if no recurrence was set up. */ bool KAEvent::setRecurAnnualByPos(int freq, const QVector &posns, const QVector &months, int count, const QDate &end) { const bool success = d->setRecur(RecurrenceRule::rYearly, freq, count, end); if (success) { int i = 0; int iend; for (iend = months.count(); i < iend; ++i) { d->mRecurrence->addYearlyMonth(months[i]); } for (i = 0, iend = posns.count(); i < iend; ++i) { d->mRecurrence->addYearlyPos(posns[i].weeknum, posns[i].days); } } d->mTriggerChanged = true; return success; } /****************************************************************************** * Initialise the event's recurrence data. * Parameters: * freq = how many intervals between recurrences. * count = number of occurrences, including first and last. * = -1 to recur indefinitely. * = 0 to use 'end' instead. * end = end date/time (invalid to use 'count' instead). * Reply = false if no recurrence was set up. */ bool KAEventPrivate::setRecur(KCalCore::RecurrenceRule::PeriodType recurType, int freq, int count, const QDate &end, KARecurrence::Feb29Type feb29) { KADateTime edt = mNextMainDateTime.kDateTime(); edt.setDate(end); return setRecur(recurType, freq, count, edt, feb29); } bool KAEventPrivate::setRecur(KCalCore::RecurrenceRule::PeriodType recurType, int freq, int count, const KADateTime &end, KARecurrence::Feb29Type feb29) { if (count >= -1 && (count || end.date().isValid())) { if (!mRecurrence) { mRecurrence = new KARecurrence; } if (mRecurrence->init(recurType, freq, count, mNextMainDateTime.kDateTime(), end, feb29)) { return true; } } clearRecur(); return false; } bool KAEvent::recurs() const { return d->checkRecur() != KARecurrence::NO_RECUR; } KARecurrence::Type KAEvent::recurType() const { return d->checkRecur(); } KARecurrence *KAEvent::recurrence() const { return d->mRecurrence; } /****************************************************************************** * Return the recurrence interval in units of the recurrence period type. */ int KAEvent::recurInterval() const { if (d->mRecurrence) { switch (d->mRecurrence->type()) { case KARecurrence::MINUTELY: case KARecurrence::DAILY: case KARecurrence::WEEKLY: case KARecurrence::MONTHLY_DAY: case KARecurrence::MONTHLY_POS: case KARecurrence::ANNUAL_DATE: case KARecurrence::ANNUAL_POS: return d->mRecurrence->frequency(); default: break; } } return 0; } Duration KAEvent::longestRecurrenceInterval() const { return d->mRecurrence ? d->mRecurrence->longestInterval() : Duration(0); } /****************************************************************************** * Adjust the event date/time to the first recurrence of the event, on or after * start date/time. The event start date may not be a recurrence date, in which * case a later date will be set. */ void KAEvent::setFirstRecurrence() { d->setFirstRecurrence(); } void KAEventPrivate::setFirstRecurrence() { switch (checkRecur()) { case KARecurrence::NO_RECUR: case KARecurrence::MINUTELY: return; case KARecurrence::ANNUAL_DATE: case KARecurrence::ANNUAL_POS: if (mRecurrence->yearMonths().isEmpty()) { return; // (presumably it's a template) } break; case KARecurrence::DAILY: case KARecurrence::WEEKLY: case KARecurrence::MONTHLY_POS: case KARecurrence::MONTHLY_DAY: break; } const KADateTime recurStart = mRecurrence->startDateTime(); if (mRecurrence->recursOn(recurStart.date(), recurStart.timeSpec())) { return; // it already recurs on the start date } // Set the frequency to 1 to find the first possible occurrence const int frequency = mRecurrence->frequency(); mRecurrence->setFrequency(1); DateTime next; nextRecurrence(mNextMainDateTime.effectiveKDateTime(), next); if (!next.isValid()) { mRecurrence->setStartDateTime(recurStart, mStartDateTime.isDateOnly()); // reinstate the old value } else { mRecurrence->setStartDateTime(next.effectiveKDateTime(), next.isDateOnly()); mStartDateTime = mNextMainDateTime = next; mTriggerChanged = true; } mRecurrence->setFrequency(frequency); // restore the frequency } /****************************************************************************** * Return the recurrence interval as text suitable for display. */ QString KAEvent::recurrenceText(bool brief) const { if (d->mRepeatAtLogin) { return brief ? i18nc("@info Brief form of 'At Login'", "Login") : i18nc("@info", "At login"); } if (d->mRecurrence) { const int frequency = d->mRecurrence->frequency(); switch (d->mRecurrence->defaultRRuleConst()->recurrenceType()) { case RecurrenceRule::rMinutely: if (frequency < 60) { return i18ncp("@info", "1 Minute", "%1 Minutes", frequency); } else if (frequency % 60 == 0) { return i18ncp("@info", "1 Hour", "%1 Hours", frequency / 60); } else { QString mins; return i18nc("@info Hours and minutes", "%1h %2m", frequency / 60, mins.sprintf("%02d", frequency % 60)); } case RecurrenceRule::rDaily: return i18ncp("@info", "1 Day", "%1 Days", frequency); case RecurrenceRule::rWeekly: return i18ncp("@info", "1 Week", "%1 Weeks", frequency); case RecurrenceRule::rMonthly: return i18ncp("@info", "1 Month", "%1 Months", frequency); case RecurrenceRule::rYearly: return i18ncp("@info", "1 Year", "%1 Years", frequency); case RecurrenceRule::rNone: default: break; } } return brief ? QString() : i18nc("@info No recurrence", "None"); } /****************************************************************************** * Initialise the event's sub-repetition. * The repetition length is adjusted if necessary to fit the recurrence interval. * If the event doesn't recur, the sub-repetition is cleared. * Reply = false if a non-daily interval was specified for a date-only recurrence. */ bool KAEvent::setRepetition(const Repetition &r) { return d->setRepetition(r); } bool KAEventPrivate::setRepetition(const Repetition &repetition) { // Don't set mRepetition to zero at the start of this function, in case the // 'repetition' parameter passed in is a reference to mRepetition. mNextRepeat = 0; if (repetition && !mRepeatAtLogin) { Q_ASSERT(checkRecur() != KARecurrence::NO_RECUR); if (!repetition.isDaily() && mStartDateTime.isDateOnly()) { mRepetition.set(0, 0); return false; // interval must be in units of days for date-only alarms } Duration longestInterval = mRecurrence->longestInterval(); if (repetition.duration() >= longestInterval) { const int count = mStartDateTime.isDateOnly() ? (longestInterval.asDays() - 1) / repetition.intervalDays() : (longestInterval.asSeconds() - 1) / repetition.intervalSeconds(); mRepetition.set(repetition.interval(), count); } else { mRepetition = repetition; } mTriggerChanged = true; } else if (mRepetition) { mRepetition.set(0, 0); mTriggerChanged = true; } return true; } Repetition KAEvent::repetition() const { return d->mRepetition; } int KAEvent::nextRepetition() const { return d->mNextRepeat; } /****************************************************************************** * Return the repetition interval as text suitable for display. */ QString KAEvent::repetitionText(bool brief) const { if (d->mRepetition) { if (!d->mRepetition.isDaily()) { const int minutes = d->mRepetition.intervalMinutes(); if (minutes < 60) { return i18ncp("@info", "1 Minute", "%1 Minutes", minutes); } if (minutes % 60 == 0) { return i18ncp("@info", "1 Hour", "%1 Hours", minutes / 60); } QString mins; return i18nc("@info Hours and minutes", "%1h %2m", minutes / 60, mins.sprintf("%02d", minutes % 60)); } const int days = d->mRepetition.intervalDays(); if (days % 7) { return i18ncp("@info", "1 Day", "%1 Days", days); } return i18ncp("@info", "1 Week", "%1 Weeks", days / 7); } return brief ? QString() : i18nc("@info No repetition", "None"); } /****************************************************************************** * Determine whether the event will occur after the specified date/time. * If 'includeRepetitions' is true and the alarm has a sub-repetition, it * returns true if any repetitions occur after the specified date/time. */ bool KAEvent::occursAfter(const KADateTime &preDateTime, bool includeRepetitions) const { return d->occursAfter(preDateTime, includeRepetitions); } bool KAEventPrivate::occursAfter(const KADateTime &preDateTime, bool includeRepetitions) const { KADateTime dt; if (checkRecur() != KARecurrence::NO_RECUR) { if (mRecurrence->duration() < 0) { return true; // infinite recurrence } dt = mRecurrence->endDateTime(); } else { dt = mNextMainDateTime.effectiveKDateTime(); } if (mStartDateTime.isDateOnly()) { QDate pre = preDateTime.date(); if (preDateTime.toTimeSpec(mStartDateTime.timeSpec()).time() < DateTime::startOfDay()) { pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come } if (pre < dt.date()) { return true; } } else if (preDateTime < dt) { return true; } if (includeRepetitions && mRepetition) { if (preDateTime < KADateTime(mRepetition.duration().end(dt.qDateTime()))) { return true; } } return false; } /****************************************************************************** * Set the date/time of the event to the next scheduled occurrence after the * specified date/time, provided that this is later than its current date/time. * Any reminder alarm is adjusted accordingly. * If the alarm has a sub-repetition, and a repetition of a previous recurrence * occurs after the specified date/time, that repetition is set as the next * occurrence. */ KAEvent::OccurType KAEvent::setNextOccurrence(const KADateTime &preDateTime) { return d->setNextOccurrence(preDateTime); } KAEvent::OccurType KAEventPrivate::setNextOccurrence(const KADateTime &preDateTime) { if (preDateTime < mNextMainDateTime.effectiveKDateTime()) { return KAEvent::FIRST_OR_ONLY_OCCURRENCE; // it might not be the first recurrence - tant pis } KADateTime pre = preDateTime; // If there are repetitions, adjust the comparison date/time so that // we find the earliest recurrence which has a repetition falling after // the specified preDateTime. if (mRepetition) { pre = KADateTime(mRepetition.duration(-mRepetition.count()).end(preDateTime.qDateTime())); } DateTime afterPre; // next recurrence after 'pre' KAEvent::OccurType type; if (pre < mNextMainDateTime.effectiveKDateTime()) { afterPre = mNextMainDateTime; type = KAEvent::FIRST_OR_ONLY_OCCURRENCE; // may not actually be the first occurrence } else if (checkRecur() != KARecurrence::NO_RECUR) { type = nextRecurrence(pre, afterPre); if (type == KAEvent::NO_OCCURRENCE) { return KAEvent::NO_OCCURRENCE; } if (type != KAEvent::FIRST_OR_ONLY_OCCURRENCE && afterPre != mNextMainDateTime) { // Need to reschedule the next trigger date/time mNextMainDateTime = afterPre; if (mReminderMinutes > 0 && (mDeferral == REMINDER_DEFERRAL || mReminderActive != ACTIVE_REMINDER)) { // Reinstate the advance reminder for the rescheduled recurrence. // Note that a reminder AFTER the main alarm will be left active. activate_reminder(!mReminderOnceOnly); } if (mDeferral == REMINDER_DEFERRAL) { set_deferral(NO_DEFERRAL); } mTriggerChanged = true; } } else { return KAEvent::NO_OCCURRENCE; } if (mRepetition) { if (afterPre <= preDateTime) { // The next occurrence is a sub-repetition. type = static_cast(type | KAEvent::OCCURRENCE_REPEAT); mNextRepeat = mRepetition.nextRepeatCount(afterPre.effectiveKDateTime(), preDateTime); // Repetitions can't have a reminder, so remove any. activate_reminder(false); if (mDeferral == REMINDER_DEFERRAL) { set_deferral(NO_DEFERRAL); } mTriggerChanged = true; } else if (mNextRepeat) { // The next occurrence is the main occurrence, not a repetition mNextRepeat = 0; mTriggerChanged = true; } } return type; } /****************************************************************************** * Get the date/time of the next occurrence of the event, after the specified * date/time. * 'result' = date/time of next occurrence, or invalid date/time if none. */ KAEvent::OccurType KAEvent::nextOccurrence(const KADateTime &preDateTime, DateTime &result, OccurOption o) const { return d->nextOccurrence(preDateTime, result, o); } KAEvent::OccurType KAEventPrivate::nextOccurrence(const KADateTime &preDateTime, DateTime &result, KAEvent::OccurOption includeRepetitions) const { KADateTime pre = preDateTime; if (includeRepetitions != KAEvent::IGNORE_REPETITION) { // RETURN_REPETITION or ALLOW_FOR_REPETITION if (!mRepetition) { includeRepetitions = KAEvent::IGNORE_REPETITION; } else { pre = KADateTime(mRepetition.duration(-mRepetition.count()).end(preDateTime.qDateTime())); } } KAEvent::OccurType type; const bool recurs = (checkRecur() != KARecurrence::NO_RECUR); if (recurs) { type = nextRecurrence(pre, result); } else if (pre < mNextMainDateTime.effectiveKDateTime()) { result = mNextMainDateTime; type = KAEvent::FIRST_OR_ONLY_OCCURRENCE; } else { result = DateTime(); type = KAEvent::NO_OCCURRENCE; } if (type != KAEvent::NO_OCCURRENCE && result <= preDateTime && includeRepetitions != KAEvent::IGNORE_REPETITION) { // RETURN_REPETITION or ALLOW_FOR_REPETITION // The next occurrence is a sub-repetition int repetition = mRepetition.nextRepeatCount(result.kDateTime(), preDateTime); const DateTime repeatDT(mRepetition.duration(repetition).end(result.qDateTime())); if (recurs) { // We've found a recurrence before the specified date/time, which has // a sub-repetition after the date/time. // However, if the intervals between recurrences vary, we could possibly // have missed a later recurrence which fits the criterion, so check again. DateTime dt; const KAEvent::OccurType newType = previousOccurrence(repeatDT.effectiveKDateTime(), dt, false); if (dt > result) { type = newType; result = dt; if (includeRepetitions == KAEvent::RETURN_REPETITION && result <= preDateTime) { // The next occurrence is a sub-repetition repetition = mRepetition.nextRepeatCount(result.kDateTime(), preDateTime); result = DateTime(mRepetition.duration(repetition).end(result.qDateTime())); type = static_cast(type | KAEvent::OCCURRENCE_REPEAT); } return type; } } if (includeRepetitions == KAEvent::RETURN_REPETITION) { // The next occurrence is a sub-repetition result = repeatDT; type = static_cast(type | KAEvent::OCCURRENCE_REPEAT); } } return type; } /****************************************************************************** * Get the date/time of the last previous occurrence of the event, before the * specified date/time. * If 'includeRepetitions' is true and the alarm has a sub-repetition, the * last previous repetition is returned if appropriate. * 'result' = date/time of previous occurrence, or invalid date/time if none. */ KAEvent::OccurType KAEvent::previousOccurrence(const KADateTime &afterDateTime, DateTime &result, bool includeRepetitions) const { return d->previousOccurrence(afterDateTime, result, includeRepetitions); } KAEvent::OccurType KAEventPrivate::previousOccurrence(const KADateTime &afterDateTime, DateTime &result, bool includeRepetitions) const { Q_ASSERT(!afterDateTime.isDateOnly()); if (mStartDateTime >= afterDateTime) { result = KADateTime(); return KAEvent::NO_OCCURRENCE; // the event starts after the specified date/time } // Find the latest recurrence of the event KAEvent::OccurType type; if (checkRecur() == KARecurrence::NO_RECUR) { result = mStartDateTime; type = KAEvent::FIRST_OR_ONLY_OCCURRENCE; } else { const KADateTime recurStart = mRecurrence->startDateTime(); KADateTime after = afterDateTime.toTimeSpec(mStartDateTime.timeSpec()); if (mStartDateTime.isDateOnly() && afterDateTime.time() > DateTime::startOfDay()) { after = after.addDays(1); // today's recurrence (if today recurs) has passed } const KADateTime dt = mRecurrence->getPreviousDateTime(after); result = dt; result.setDateOnly(mStartDateTime.isDateOnly()); if (!dt.isValid()) { return KAEvent::NO_OCCURRENCE; } if (dt == recurStart) { type = KAEvent::FIRST_OR_ONLY_OCCURRENCE; } else if (mRecurrence->getNextDateTime(dt).isValid()) { type = result.isDateOnly() ? KAEvent::RECURRENCE_DATE : KAEvent::RECURRENCE_DATE_TIME; } else { type = KAEvent::LAST_RECURRENCE; } } if (includeRepetitions && mRepetition) { // Find the latest repetition which is before the specified time. const int repetition = mRepetition.previousRepeatCount(result.effectiveKDateTime(), afterDateTime); if (repetition > 0) { result = DateTime(mRepetition.duration(qMin(repetition, mRepetition.count())).end(result.qDateTime())); return static_cast(type | KAEvent::OCCURRENCE_REPEAT); } } return type; } /****************************************************************************** * Set the event to be a copy of the specified event, making the specified * alarm the 'displaying' alarm. * The purpose of setting up a 'displaying' alarm is to be able to reinstate * the alarm message in case of a crash, or to reinstate it should the user * choose to defer the alarm. Note that even repeat-at-login alarms need to be * saved in case their end time expires before the next login. * Reply = true if successful, false if alarm was not copied. */ bool KAEvent::setDisplaying(const KAEvent &e, KAAlarm::Type t, Akonadi::Collection::Id id, const KADateTime &dt, bool showEdit, bool showDefer) { return d->setDisplaying(*e.d, t, id, dt, showEdit, showDefer); } bool KAEventPrivate::setDisplaying(const KAEventPrivate &event, KAAlarm::Type alarmType, Akonadi::Collection::Id collectionId, const KADateTime &repeatAtLoginTime, bool showEdit, bool showDefer) { if (!mDisplaying && (alarmType == KAAlarm::MAIN_ALARM || alarmType == KAAlarm::REMINDER_ALARM || alarmType == KAAlarm::DEFERRED_REMINDER_ALARM || alarmType == KAAlarm::DEFERRED_ALARM || alarmType == KAAlarm::AT_LOGIN_ALARM)) { //qCDebug(KALARMCAL_LOG)<= 0) { qCDebug(KALARMCAL_LOG) << "-- mSoundVolume:" << mSoundVolume; if (mFadeVolume >= 0) { qCDebug(KALARMCAL_LOG) << "-- mFadeVolume:" << mFadeVolume; qCDebug(KALARMCAL_LOG) << "-- mFadeSeconds:" << mFadeSeconds; } else { qCDebug(KALARMCAL_LOG) << "-- mFadeVolume:-:"; } } else { qCDebug(KALARMCAL_LOG) << "-- mSoundVolume:-:"; } qCDebug(KALARMCAL_LOG) << "-- mRepeatSoundPause:" << mRepeatSoundPause; } - qCDebug(KALARMCAL_LOG) << "-- mKMailSerialNumber:" << mKMailSerialNumber; + qCDebug(KALARMCAL_LOG) << "-- mAkonadiItemId:" << mAkonadiItemId; qCDebug(KALARMCAL_LOG) << "-- mCopyToKOrganizer:" << mCopyToKOrganizer; qCDebug(KALARMCAL_LOG) << "-- mExcludeHolidays:" << mExcludeHolidays; qCDebug(KALARMCAL_LOG) << "-- mWorkTimeOnly:" << mWorkTimeOnly; qCDebug(KALARMCAL_LOG) << "-- mStartDateTime:" << mStartDateTime.toString(); // qCDebug(KALARMCAL_LOG) << "-- mCreatedDateTime:" << mCreatedDateTime; qCDebug(KALARMCAL_LOG) << "-- mRepeatAtLogin:" << mRepeatAtLogin; // if (mRepeatAtLogin) // qCDebug(KALARMCAL_LOG) << "-- mAtLoginDateTime:" << mAtLoginDateTime; qCDebug(KALARMCAL_LOG) << "-- mArchiveRepeatAtLogin:" << mArchiveRepeatAtLogin; qCDebug(KALARMCAL_LOG) << "-- mConfirmAck:" << mConfirmAck; qCDebug(KALARMCAL_LOG) << "-- mEnabled:" << mEnabled; qCDebug(KALARMCAL_LOG) << "-- mItemId:" << mItemId; qCDebug(KALARMCAL_LOG) << "-- mCollectionId:" << mCollectionId; qCDebug(KALARMCAL_LOG) << "-- mCompatibility:" << mCompatibility; qCDebug(KALARMCAL_LOG) << "-- mReadOnly:" << mReadOnly; if (mReminderMinutes) { qCDebug(KALARMCAL_LOG) << "-- mReminderMinutes:" << mReminderMinutes; qCDebug(KALARMCAL_LOG) << "-- mReminderActive:" << (mReminderActive == ACTIVE_REMINDER ? "active" : mReminderActive == HIDDEN_REMINDER ? "hidden" : "no"); qCDebug(KALARMCAL_LOG) << "-- mReminderOnceOnly:" << mReminderOnceOnly; } else if (mDeferral > 0) { qCDebug(KALARMCAL_LOG) << "-- mDeferral:" << (mDeferral == NORMAL_DEFERRAL ? "normal" : "reminder"); qCDebug(KALARMCAL_LOG) << "-- mDeferralTime:" << mDeferralTime.toString(); } qCDebug(KALARMCAL_LOG) << "-- mDeferDefaultMinutes:" << mDeferDefaultMinutes; if (mDeferDefaultMinutes) { qCDebug(KALARMCAL_LOG) << "-- mDeferDefaultDateOnly:" << mDeferDefaultDateOnly; } if (mDisplaying) { qCDebug(KALARMCAL_LOG) << "-- mDisplayingTime:" << mDisplayingTime.toString(); qCDebug(KALARMCAL_LOG) << "-- mDisplayingFlags:" << mDisplayingFlags; qCDebug(KALARMCAL_LOG) << "-- mDisplayingDefer:" << mDisplayingDefer; qCDebug(KALARMCAL_LOG) << "-- mDisplayingEdit:" << mDisplayingEdit; } qCDebug(KALARMCAL_LOG) << "-- mRevision:" << mRevision; qCDebug(KALARMCAL_LOG) << "-- mRecurrence:" << mRecurrence; if (!mRepetition) { qCDebug(KALARMCAL_LOG) << "-- mRepetition: 0"; } else if (mRepetition.isDaily()) { qCDebug(KALARMCAL_LOG) << "-- mRepetition: count:" << mRepetition.count() << ", interval:" << mRepetition.intervalDays() << "days"; } else { qCDebug(KALARMCAL_LOG) << "-- mRepetition: count:" << mRepetition.count() << ", interval:" << mRepetition.intervalMinutes() << "minutes"; } qCDebug(KALARMCAL_LOG) << "-- mNextRepeat:" << mNextRepeat; qCDebug(KALARMCAL_LOG) << "-- mAlarmCount:" << mAlarmCount; qCDebug(KALARMCAL_LOG) << "-- mMainExpired:" << mMainExpired; qCDebug(KALARMCAL_LOG) << "-- mDisplaying:" << mDisplaying; qCDebug(KALARMCAL_LOG) << "KAEvent dump end"; } #endif /****************************************************************************** * Fetch the start and next date/time for a KCal::Event. * Reply = next main date/time. */ DateTime KAEventPrivate::readDateTime(const Event::Ptr &event, bool localZone, bool dateOnly, DateTime &start) { start = DateTime(event->dtStart()); if (dateOnly) { // A date-only event is indicated by the X-KDE-KALARM-FLAGS:DATE property, not // by a date-only start date/time (for the reasons given in updateKCalEvent()). start.setDateOnly(true); } if (localZone) { // The local system time zone is indicated by the X-KDE-KALARM-FLAGS:LOCAL // property, because QDateTime values with time spec Qt::LocalTime are not // stored correctly in the calendar file. start.setTimeSpec(KADateTime::LocalZone); } DateTime next = start; const int SZ_YEAR = 4; // number of digits in year value const int SZ_MONTH = 2; // number of digits in month value const int SZ_DAY = 2; // number of digits in day value const int SZ_DATE = SZ_YEAR + SZ_MONTH + SZ_DAY; // total size of date value const int IX_TIME = SZ_DATE + 1; // offset to time value const int SZ_HOUR = 2; // number of digits in hour value const int SZ_MIN = 2; // number of digits in minute value const int SZ_SEC = 2; // number of digits in second value const int SZ_TIME = SZ_HOUR + SZ_MIN + SZ_SEC; // total size of time value const QString prop = event->customProperty(KACalendar::APPNAME, KAEventPrivate::NEXT_RECUR_PROPERTY); if (prop.length() >= SZ_DATE) { // The next due recurrence time is specified const QDate d(prop.leftRef(SZ_YEAR).toInt(), prop.midRef(SZ_YEAR, SZ_MONTH).toInt(), prop.midRef(SZ_YEAR + SZ_MONTH, SZ_DAY).toInt()); if (d.isValid()) { if (dateOnly && prop.length() == SZ_DATE) { next.setDate(d); } else if (!dateOnly && prop.length() == IX_TIME + SZ_TIME && prop[SZ_DATE] == QLatin1Char('T')) { const QTime t(prop.midRef(IX_TIME, SZ_HOUR).toInt(), prop.midRef(IX_TIME + SZ_HOUR, SZ_MIN).toInt(), prop.midRef(IX_TIME + SZ_HOUR + SZ_MIN, SZ_SEC).toInt()); if (t.isValid()) { next.setDate(d); next.setTime(t); } } if (next < start) { next = start; // ensure next recurrence time is valid } } } return next; } /****************************************************************************** * Parse the alarms for a KCal::Event. * Reply = map of alarm data, indexed by KAAlarm::Type */ void KAEventPrivate::readAlarms(const Event::Ptr &event, AlarmMap *alarmMap, bool cmdDisplay) { const Alarm::List alarms = event->alarms(); // Check if it's an audio event with no display alarm bool audioOnly = false; for (int i = 0, end = alarms.count(); i < end; ++i) { switch (alarms[i]->type()) { case Alarm::Display: case Alarm::Procedure: audioOnly = false; i = end; // exit from the 'for' loop break; case Alarm::Audio: audioOnly = true; break; default: break; } } for (int i = 0, end = alarms.count(); i < end; ++i) { // Parse the next alarm's text AlarmData data; readAlarm(alarms[i], data, audioOnly, cmdDisplay); if (data.type != INVALID_ALARM) { alarmMap->insert(data.type, data); } } } /****************************************************************************** * Parse a KCal::Alarm. * If 'audioMain' is true, the event contains an audio alarm but no display alarm. * Reply = alarm ID (sequence number) */ void KAEventPrivate::readAlarm(const Alarm::Ptr &alarm, AlarmData &data, bool audioMain, bool cmdDisplay) { // Parse the next alarm's text data.alarm = alarm; data.displayingFlags = 0; data.isEmailText = false; data.speak = false; data.hiddenReminder = false; data.timedDeferral = false; data.nextRepeat = 0; data.repeatSoundPause = -1; if (alarm->repeatCount()) { bool ok; const QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::NEXT_REPEAT_PROPERTY); int n = static_cast(property.toUInt(&ok)); if (ok) { data.nextRepeat = n; } } QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY); const QStringList flags = property.split(KAEventPrivate::SC, QString::SkipEmptyParts); switch (alarm->type()) { case Alarm::Procedure: data.action = KAAlarm::COMMAND; data.cleanText = alarm->programFile(); data.commandScript = data.cleanText.isEmpty(); // blank command indicates a script if (!alarm->programArguments().isEmpty()) { if (!data.commandScript) { data.cleanText += QLatin1Char(' '); } data.cleanText += alarm->programArguments(); } data.extraActionOptions = {}; if (flags.contains(KAEventPrivate::EXEC_ON_DEFERRAL_FLAG)) { data.extraActionOptions |= KAEvent::ExecPreActOnDeferral; } if (flags.contains(KAEventPrivate::CANCEL_ON_ERROR_FLAG)) { data.extraActionOptions |= KAEvent::CancelOnPreActError; } if (flags.contains(KAEventPrivate::DONT_SHOW_ERROR_FLAG)) { data.extraActionOptions |= KAEvent::DontShowPreActError; } if (!cmdDisplay) { break; } // fall through to Display Q_FALLTHROUGH(); case Alarm::Display: { if (alarm->type() == Alarm::Display) { data.action = KAAlarm::MESSAGE; data.cleanText = AlarmText::fromCalendarText(alarm->text(), data.isEmailText); } const QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::FONT_COLOUR_PROPERTY); const QStringList list = property.split(QLatin1Char(';'), QString::KeepEmptyParts); data.bgColour = QColor(255, 255, 255); // white data.fgColour = QColor(0, 0, 0); // black const int n = list.count(); if (n > 0) { if (!list[0].isEmpty()) { QColor c(list[0]); if (c.isValid()) { data.bgColour = c; } } if (n > 1 && !list[1].isEmpty()) { QColor c(list[1]); if (c.isValid()) { data.fgColour = c; } } } data.defaultFont = (n <= 2 || list[2].isEmpty()); if (!data.defaultFont) { data.font.fromString(list[2]); } break; } case Alarm::Email: { data.action = KAAlarm::EMAIL; data.cleanText = alarm->mailText(); const int i = flags.indexOf(KAEventPrivate::EMAIL_ID_FLAG); data.emailFromId = (i >= 0 && i + 1 < flags.count()) ? flags[i + 1].toUInt() : 0; break; } case Alarm::Audio: { data.action = KAAlarm::AUDIO; data.cleanText = alarm->audioFile(); data.repeatSoundPause = (alarm->repeatCount() == -2) ? alarm->snoozeTime().asSeconds() : (alarm->repeatCount() == -1) ? 0 : -1; data.soundVolume = -1; data.fadeVolume = -1; data.fadeSeconds = 0; QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::VOLUME_PROPERTY); if (!property.isEmpty()) { bool ok; float fadeVolume; int fadeSecs = 0; const QStringList list = property.split(QLatin1Char(';'), QString::KeepEmptyParts); data.soundVolume = list[0].toFloat(&ok); if (!ok || data.soundVolume > 1.0f) { data.soundVolume = -1; } if (data.soundVolume >= 0 && list.count() >= 3) { fadeVolume = list[1].toFloat(&ok); if (ok) { fadeSecs = static_cast(list[2].toUInt(&ok)); } if (ok && fadeVolume >= 0 && fadeVolume <= 1.0f && fadeSecs > 0) { data.fadeVolume = fadeVolume; data.fadeSeconds = fadeSecs; } } } if (!audioMain) { data.type = AUDIO_ALARM; data.speak = flags.contains(KAEventPrivate::SPEAK_FLAG); return; } break; } case Alarm::Invalid: data.type = INVALID_ALARM; return; } bool atLogin = false; bool reminder = false; bool deferral = false; bool dateDeferral = false; bool repeatSound = false; data.type = MAIN_ALARM; property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY); const QStringList types = property.split(QLatin1Char(','), QString::SkipEmptyParts); for (int i = 0, end = types.count(); i < end; ++i) { const QString type = types[i]; if (type == KAEventPrivate::AT_LOGIN_TYPE) { atLogin = true; } else if (type == KAEventPrivate::FILE_TYPE && data.action == KAAlarm::MESSAGE) { data.action = KAAlarm::FILE; } else if (type == KAEventPrivate::REMINDER_TYPE) { reminder = true; } else if (type == KAEventPrivate::TIME_DEFERRAL_TYPE) { deferral = true; } else if (type == KAEventPrivate::DATE_DEFERRAL_TYPE) { dateDeferral = deferral = true; } else if (type == KAEventPrivate::DISPLAYING_TYPE) { data.type = DISPLAYING_ALARM; } else if (type == KAEventPrivate::PRE_ACTION_TYPE && data.action == KAAlarm::COMMAND) { data.type = PRE_ACTION_ALARM; } else if (type == KAEventPrivate::POST_ACTION_TYPE && data.action == KAAlarm::COMMAND) { data.type = POST_ACTION_ALARM; } else if (type == KAEventPrivate::SOUND_REPEAT_TYPE && data.action == KAAlarm::AUDIO) { repeatSound = true; if (i + 1 < end) { bool ok; uint n = types[i + 1].toUInt(&ok); if (ok) { data.repeatSoundPause = n; ++i; } } } } if (repeatSound && data.repeatSoundPause < 0) { data.repeatSoundPause = 0; } else if (!repeatSound) { data.repeatSoundPause = -1; } if (reminder) { if (data.type == MAIN_ALARM) { data.type = deferral ? DEFERRED_REMINDER_ALARM : REMINDER_ALARM; data.timedDeferral = (deferral && !dateDeferral); if (data.type == REMINDER_ALARM && flags.contains(KAEventPrivate::HIDDEN_REMINDER_FLAG)) { data.hiddenReminder = true; } } else if (data.type == DISPLAYING_ALARM) data.displayingFlags = dateDeferral ? REMINDER | DATE_DEFERRAL : deferral ? REMINDER | TIME_DEFERRAL : REMINDER; } else if (deferral) { if (data.type == MAIN_ALARM) { data.type = DEFERRED_ALARM; data.timedDeferral = !dateDeferral; } else if (data.type == DISPLAYING_ALARM) { data.displayingFlags = dateDeferral ? DATE_DEFERRAL : TIME_DEFERRAL; } } if (atLogin) { if (data.type == MAIN_ALARM) { data.type = AT_LOGIN_ALARM; } else if (data.type == DISPLAYING_ALARM) { data.displayingFlags = KAEvent::REPEAT_AT_LOGIN; } } //qCDebug(KALARMCAL_LOG)<<"text="<text()<<", time="<time().toString()<<", valid time="<time().isValid(); } QSharedPointer KAEventPrivate::holidays() { if (!mHolidays) mHolidays.reset(new HolidayRegion()); return mHolidays; } inline void KAEventPrivate::set_deferral(DeferType type) { if (type) { if (mDeferral == NO_DEFERRAL) { ++mAlarmCount; } } else { if (mDeferral != NO_DEFERRAL) { --mAlarmCount; } } mDeferral = type; } /****************************************************************************** * Calculate the next trigger times of the alarm. * This should only be called when changes have actually occurred which might * affect the event's trigger times. * mMainTrigger is set to the next scheduled recurrence/sub-repetition, or the * deferral time if a deferral is pending. * mAllTrigger is the same as mMainTrigger, but takes account of reminders. * mMainWorkTrigger is set to the next scheduled recurrence/sub-repetition * which occurs in working hours, if working-time-only is set. * mAllWorkTrigger is the same as mMainWorkTrigger, but takes account of reminders. */ void KAEventPrivate::calcTriggerTimes() const { if (mChangeCount) { return; } #ifdef __GNUC__ #warning May need to set date-only alarms to after start-of-day time in working-time checks #endif holidays(); // initialise mHolidays if necessary bool recurs = (checkRecur() != KARecurrence::NO_RECUR); if ((recurs && mWorkTimeOnly && mWorkTimeOnly != mWorkTimeIndex) || (recurs && mExcludeHolidays && mExcludeHolidayRegion->regionCode() != mHolidays->regionCode())) { // It's a work time alarm, and work days/times have changed, or // it excludes holidays, and the holidays definition has changed. mTriggerChanged = true; } else if (!mTriggerChanged) { return; } mTriggerChanged = false; if (recurs && mWorkTimeOnly) { mWorkTimeOnly = mWorkTimeIndex; // note which work time definition was used in calculation } if (recurs && mExcludeHolidays) { mExcludeHolidayRegion = mHolidays; // note which holiday definition was used in calculation } bool excludeHolidays = mExcludeHolidays && mExcludeHolidayRegion->isValid(); if (mCategory == CalEvent::ARCHIVED || mCategory == CalEvent::TEMPLATE) { // It's a template or archived mAllTrigger = mMainTrigger = mAllWorkTrigger = mMainWorkTrigger = KADateTime(); } else if (mDeferral == NORMAL_DEFERRAL) { // For a deferred alarm, working time setting is ignored mAllTrigger = mMainTrigger = mAllWorkTrigger = mMainWorkTrigger = mDeferralTime; } else { mMainTrigger = mainDateTime(true); // next recurrence or sub-repetition mAllTrigger = (mDeferral == REMINDER_DEFERRAL) ? mDeferralTime : (mReminderActive != ACTIVE_REMINDER) ? mMainTrigger : (mReminderMinutes < 0) ? mReminderAfterTime : mMainTrigger.addMins(-mReminderMinutes); // It's not deferred. // If only-during-working-time is set and it recurs, it won't actually trigger // unless it falls during working hours. if ((!mWorkTimeOnly && !excludeHolidays) || !recurs || isWorkingTime(mMainTrigger.kDateTime())) { // It only occurs once, or it complies with any working hours/holiday // restrictions. mMainWorkTrigger = mMainTrigger; mAllWorkTrigger = mAllTrigger; } else if (mWorkTimeOnly) { // The alarm is restricted to working hours. // Finding the next occurrence during working hours can sometimes take a long time, // so mark the next actual trigger as invalid until the calculation completes. // Note that reminders are only triggered if the main alarm is during working time. if (!excludeHolidays) { // There are no holiday restrictions. calcNextWorkingTime(mMainTrigger); } else if (mHolidays->isValid()) { // Holidays are excluded. DateTime nextTrigger = mMainTrigger; KADateTime kdt; for (int i = 0; i < 20; ++i) { calcNextWorkingTime(nextTrigger); if (!mHolidays->isHoliday(mMainWorkTrigger.date())) { return; // found a non-holiday occurrence } kdt = mMainWorkTrigger.effectiveKDateTime(); kdt.setTime(QTime(23, 59, 59)); const KAEvent::OccurType type = nextOccurrence(kdt, nextTrigger, KAEvent::RETURN_REPETITION); if (!nextTrigger.isValid()) { break; } if (isWorkingTime(nextTrigger.kDateTime())) { const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm mMainWorkTrigger = nextTrigger; mAllWorkTrigger = (type & KAEvent::OCCURRENCE_REPEAT) ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder); return; // found a non-holiday occurrence } } mMainWorkTrigger = mAllWorkTrigger = DateTime(); } } else if (excludeHolidays && mHolidays->isValid()) { // Holidays are excluded. DateTime nextTrigger = mMainTrigger; KADateTime kdt; for (int i = 0; i < 20; ++i) { kdt = nextTrigger.effectiveKDateTime(); kdt.setTime(QTime(23, 59, 59)); const KAEvent::OccurType type = nextOccurrence(kdt, nextTrigger, KAEvent::RETURN_REPETITION); if (!nextTrigger.isValid()) { break; } if (!mHolidays->isHoliday(nextTrigger.date())) { const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm mMainWorkTrigger = nextTrigger; mAllWorkTrigger = (type & KAEvent::OCCURRENCE_REPEAT) ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder); return; // found a non-holiday occurrence } } mMainWorkTrigger = mAllWorkTrigger = DateTime(); } } } /****************************************************************************** * Return the time of the next scheduled occurrence of the event during working * hours, for an alarm which is restricted to working hours. * On entry, 'nextTrigger' = the next recurrence or repetition (as returned by * mainDateTime(true) ). */ void KAEventPrivate::calcNextWorkingTime(const DateTime &nextTrigger) const { qCDebug(KALARMCAL_LOG) << "next=" << nextTrigger.kDateTime().toString(QStringLiteral("%Y-%m-%d %H:%M")); mMainWorkTrigger = mAllWorkTrigger = DateTime(); for (int i = 0; ; ++i) { if (i >= 7) { return; // no working days are defined } if (mWorkDays.testBit(i)) { break; } } const KARecurrence::Type recurType = checkRecur(); KADateTime kdt = nextTrigger.effectiveKDateTime(); const int reminder = (mReminderMinutes > 0) ? mReminderMinutes : 0; // only interested in reminders BEFORE the alarm // Check if it always falls on the same day(s) of the week. const RecurrenceRule *rrule = mRecurrence->defaultRRuleConst(); if (!rrule) { return; // no recurrence rule! } unsigned allDaysMask = 0x7F; // mask bits for all days of week bool noWorkPos = false; // true if no recurrence day position is working day const QList pos = rrule->byDays(); const int nDayPos = pos.count(); // number of day positions if (nDayPos) { noWorkPos = true; allDaysMask = 0; for (int i = 0; i < nDayPos; ++i) { const int day = pos[i].day() - 1; // Monday = 0 if (mWorkDays.testBit(day)) { noWorkPos = false; // found a working day occurrence } allDaysMask |= 1 << day; } if (noWorkPos && !mRepetition) { return; // never occurs on a working day } } DateTime newdt; if (mStartDateTime.isDateOnly()) { // It's a date-only alarm. // Sub-repetitions also have to be date-only. const int repeatFreq = mRepetition.intervalDays(); const bool weeklyRepeat = mRepetition && !(repeatFreq % 7); const Duration interval = mRecurrence->regularInterval(); if ((!interval.isNull() && !(interval.asDays() % 7)) || nDayPos == 1) { // It recurs on the same day each week if (!mRepetition || weeklyRepeat) { return; // any repetitions are also weekly } // It's a weekly recurrence with a non-weekly sub-repetition. // Check one cycle of repetitions for the next one that lands // on a working day. KADateTime dt(nextTrigger.kDateTime().addDays(1)); dt.setTime(QTime(0, 0, 0)); previousOccurrence(dt, newdt, false); if (!newdt.isValid()) { return; // this should never happen } kdt = newdt.effectiveKDateTime(); const int day = kdt.date().dayOfWeek() - 1; // Monday = 0 for (int repeatNum = mNextRepeat + 1; ; ++repeatNum) { if (repeatNum > mRepetition.count()) { repeatNum = 0; } if (repeatNum == mNextRepeat) { break; } if (!repeatNum) { nextOccurrence(newdt.kDateTime(), newdt, KAEvent::IGNORE_REPETITION); if (mWorkDays.testBit(day)) { mMainWorkTrigger = newdt; mAllWorkTrigger = mMainWorkTrigger.addMins(-reminder); return; } kdt = newdt.effectiveKDateTime(); } else { const int inc = repeatFreq * repeatNum; if (mWorkDays.testBit((day + inc) % 7)) { kdt = kdt.addDays(inc); kdt.setDateOnly(true); mMainWorkTrigger = mAllWorkTrigger = kdt; return; } } } return; } if (!mRepetition || weeklyRepeat) { // It's a date-only alarm with either no sub-repetition or a // sub-repetition which always falls on the same day of the week // as the recurrence (if any). unsigned days = 0; for (; ;) { kdt.setTime(QTime(23, 59, 59)); nextOccurrence(kdt, newdt, KAEvent::IGNORE_REPETITION); if (!newdt.isValid()) { return; } kdt = newdt.effectiveKDateTime(); const int day = kdt.date().dayOfWeek() - 1; if (mWorkDays.testBit(day)) { break; // found a working day occurrence } // Prevent indefinite looping (which should never happen anyway) if ((days & allDaysMask) == allDaysMask) { return; // found a recurrence on every possible day of the week!?! } days |= 1 << day; } kdt.setDateOnly(true); mMainWorkTrigger = kdt; mAllWorkTrigger = kdt.addSecs(-60 * reminder); return; } // It's a date-only alarm which recurs on different days of the week, // as does the sub-repetition. // Find the previous recurrence (as opposed to sub-repetition) unsigned days = 1 << (kdt.date().dayOfWeek() - 1); KADateTime dt(nextTrigger.kDateTime().addDays(1)); dt.setTime(QTime(0, 0, 0)); previousOccurrence(dt, newdt, false); if (!newdt.isValid()) { return; // this should never happen } kdt = newdt.effectiveKDateTime(); int day = kdt.date().dayOfWeek() - 1; // Monday = 0 for (int repeatNum = mNextRepeat; ; repeatNum = 0) { while (++repeatNum <= mRepetition.count()) { const int inc = repeatFreq * repeatNum; if (mWorkDays.testBit((day + inc) % 7)) { kdt = kdt.addDays(inc); kdt.setDateOnly(true); mMainWorkTrigger = mAllWorkTrigger = kdt; return; } if ((days & allDaysMask) == allDaysMask) { return; // found an occurrence on every possible day of the week!?! } days |= 1 << day; } nextOccurrence(kdt, newdt, KAEvent::IGNORE_REPETITION); if (!newdt.isValid()) { return; } kdt = newdt.effectiveKDateTime(); day = kdt.date().dayOfWeek() - 1; if (mWorkDays.testBit(day)) { kdt.setDateOnly(true); mMainWorkTrigger = kdt; mAllWorkTrigger = kdt.addSecs(-60 * reminder); return; } if ((days & allDaysMask) == allDaysMask) { return; // found an occurrence on every possible day of the week!?! } days |= 1 << day; } return; } // It's a date-time alarm. /* Check whether the recurrence or sub-repetition occurs at the same time * every day. Note that because of seasonal time changes, a recurrence * defined in terms of minutes will vary its time of day even if its value * is a multiple of a day (24*60 minutes). Sub-repetitions are considered * to repeat at the same time of day regardless of time changes if they * are multiples of a day, which doesn't strictly conform to the iCalendar * format because this only allows their interval to be recorded in seconds. */ const bool recurTimeVaries = (recurType == KARecurrence::MINUTELY); const bool repeatTimeVaries = (mRepetition && !mRepetition.isDaily()); if (!recurTimeVaries && !repeatTimeVaries) { // The alarm always occurs at the same time of day. // Check whether it can ever occur during working hours. if (!mayOccurDailyDuringWork(kdt)) { return; // never occurs during working hours } // Find the next working day it occurs on bool repetition = false; unsigned days = 0; for (; ;) { KAEvent::OccurType type = nextOccurrence(kdt, newdt, KAEvent::RETURN_REPETITION); if (!newdt.isValid()) { return; } repetition = (type & KAEvent::OCCURRENCE_REPEAT); kdt = newdt.effectiveKDateTime(); const int day = kdt.date().dayOfWeek() - 1; if (mWorkDays.testBit(day)) { break; // found a working day occurrence } // Prevent indefinite looping (which should never happen anyway) if (!repetition) { if ((days & allDaysMask) == allDaysMask) { return; // found a recurrence on every possible day of the week!?! } days |= 1 << day; } } mMainWorkTrigger = nextTrigger; mMainWorkTrigger.setDate(kdt.date()); mAllWorkTrigger = repetition ? mMainWorkTrigger : mMainWorkTrigger.addMins(-reminder); return; } // The alarm occurs at different times of day. // We may need to check for a full annual cycle of seasonal time changes, in // case it only occurs during working hours after a time change. const QTimeZone tz = kdt.timeZone(); // Get time zone transitions for the next 10 years. QDateTime endTransitionsTime = QDateTime::currentDateTimeUtc().addYears(10); QTimeZone::OffsetDataList tzTransitions = tz.transitions(mStartDateTime.qDateTime(), endTransitionsTime); if (recurTimeVaries) { /* The alarm recurs at regular clock intervals, at different times of day. * Note that for this type of recurrence, it's necessary to avoid the * performance overhead of Recurrence class calls since these can in the * worst case cause the program to hang for a significant length of time. * In this case, we can calculate the next recurrence by simply adding the * recurrence interval, since KAlarm offers no facility to regularly miss * recurrences. (But exception dates/times need to be taken into account.) */ KADateTime kdtRecur; int repeatFreq = 0; int repeatNum = 0; if (mRepetition) { // It's a repetition inside a recurrence, each of which occurs // at different times of day (bearing in mind that the repetition // may occur at daily intervals after each recurrence). // Find the previous recurrence (as opposed to sub-repetition) repeatFreq = mRepetition.intervalSeconds(); previousOccurrence(kdt.addSecs(1), newdt, false); if (!newdt.isValid()) { return; // this should never happen } kdtRecur = newdt.effectiveKDateTime(); repeatNum = kdtRecur.secsTo(kdt) / repeatFreq; kdt = kdtRecur.addSecs(repeatNum * repeatFreq); } else { // There is no sub-repetition. // (N.B. Sub-repetitions can't exist without a recurrence.) // Check until the original time wraps round, but ensure that // if there are seasonal time changes, that all other subsequent // time offsets within the next year are checked. // This does not guarantee to find the next working time, // particularly if there are exceptions, but it's a // reasonable try. kdtRecur = kdt; } QTime firstTime = kdtRecur.time(); int firstOffset = kdtRecur.utcOffset(); int currentOffset = firstOffset; int dayRecur = kdtRecur.date().dayOfWeek() - 1; // Monday = 0 int firstDay = dayRecur; QDate finalDate; const bool subdaily = (repeatFreq < 24 * 3600); // int period = mRecurrence->frequency() % (24*60); // it is by definition a MINUTELY recurrence // int limit = (24*60 + period - 1) / period; // number of times until recurrence wraps round int transitionIndex = -1; for (int n = 0; n < 7 * 24 * 60; ++n) { if (mRepetition) { // Check the sub-repetitions for this recurrence for (; ;) { // Find the repeat count to the next start of the working day const int inc = subdaily ? nextWorkRepetition(kdt) : 1; repeatNum += inc; if (repeatNum > mRepetition.count()) { break; } kdt = kdt.addSecs(inc * repeatFreq); const QTime t = kdt.time(); if (t >= mWorkDayStart && t < mWorkDayEnd) { if (mWorkDays.testBit(kdt.date().dayOfWeek() - 1)) { mMainWorkTrigger = mAllWorkTrigger = kdt; return; } } } repeatNum = 0; } nextOccurrence(kdtRecur, newdt, KAEvent::IGNORE_REPETITION); if (!newdt.isValid()) { return; } kdtRecur = newdt.effectiveKDateTime(); dayRecur = kdtRecur.date().dayOfWeek() - 1; // Monday = 0 const QTime t = kdtRecur.time(); if (t >= mWorkDayStart && t < mWorkDayEnd) { if (mWorkDays.testBit(dayRecur)) { mMainWorkTrigger = kdtRecur; mAllWorkTrigger = kdtRecur.addSecs(-60 * reminder); return; } } if (kdtRecur.utcOffset() != currentOffset) { currentOffset = kdtRecur.utcOffset(); } if (t == firstTime && dayRecur == firstDay && currentOffset == firstOffset) { // We've wrapped round to the starting day and time. // If there are seasonal time changes, check for up // to the next year in other time offsets in case the // alarm occurs inside working hours then. if (!finalDate.isValid()) { finalDate = kdtRecur.date(); } const int i = KAEventPrivate::transitionIndex(kdtRecur.toUtc().qDateTime(), tzTransitions); if (i < 0) { return; } if (i > transitionIndex) { transitionIndex = i; } if (++transitionIndex >= tzTransitions.count()) { return; } previousOccurrence(KADateTime(tzTransitions[transitionIndex].atUtc), newdt, KAEvent::IGNORE_REPETITION); kdtRecur = newdt.effectiveKDateTime(); if (finalDate.daysTo(kdtRecur.date()) > 365) { return; } firstTime = kdtRecur.time(); firstOffset = kdtRecur.utcOffset(); currentOffset = firstOffset; firstDay = kdtRecur.date().dayOfWeek() - 1; } kdt = kdtRecur; } //qCDebug(KALARMCAL_LOG)<<"-----exit loop: count="<= mWorkDayStart && kdtRecur.time() < mWorkDayEnd); // Use the previous recurrence as a base for checking whether // our tests have wrapped round to the same time/day of week. const bool subdaily = (repeatFreq < 24 * 3600); unsigned days = 0; bool checkTimeChangeOnly = false; int transitionIndex = -1; for (int limit = 10; --limit >= 0;) { // Check the next seasonal time change (for an arbitrary 10 times, // even though that might not guarantee the correct result) QDate dateRecur = kdtRecur.date(); int dayRecur = dateRecur.dayOfWeek() - 1; // Monday = 0 int repeatNum = kdtRecur.secsTo(kdt) / repeatFreq; kdt = kdtRecur.addSecs(repeatNum * repeatFreq); // Find the next recurrence, which sets the limit on possible sub-repetitions. // Note that for a monthly recurrence, for example, a sub-repetition could // be defined which is longer than the recurrence interval in short months. // In these cases, the sub-repetition is truncated by the following // recurrence. nextOccurrence(kdtRecur, newdt, KAEvent::IGNORE_REPETITION); KADateTime kdtNextRecur = newdt.effectiveKDateTime(); int repeatsToCheck = mRepetition.count(); int repeatsDuringWork = 0; // 0=unknown, 1=does, -1=never for (; ;) { // Check the sub-repetitions for this recurrence if (repeatsDuringWork >= 0) { for (; ;) { // Find the repeat count to the next start of the working day int inc = subdaily ? nextWorkRepetition(kdt) : 1; repeatNum += inc; const bool pastEnd = (repeatNum > mRepetition.count()); if (pastEnd) { inc -= repeatNum - mRepetition.count(); } repeatsToCheck -= inc; kdt = kdt.addSecs(inc * repeatFreq); if (kdtNextRecur.isValid() && kdt >= kdtNextRecur) { // This sub-repetition is past the next recurrence, // so start the check again from the next recurrence. repeatsToCheck = mRepetition.count(); break; } if (pastEnd) { break; } const QTime t = kdt.time(); if (t >= mWorkDayStart && t < mWorkDayEnd) { if (mWorkDays.testBit(kdt.date().dayOfWeek() - 1)) { mMainWorkTrigger = mAllWorkTrigger = kdt; return; } repeatsDuringWork = 1; } else if (!repeatsDuringWork && repeatsToCheck <= 0) { // Sub-repetitions never occur during working hours repeatsDuringWork = -1; break; } } } repeatNum = 0; if (repeatsDuringWork < 0 && !recurDuringWork) { break; // it never occurs during working hours } // Check the next recurrence if (!kdtNextRecur.isValid()) { return; } if (checkTimeChangeOnly || (days & allDaysMask) == allDaysMask) { break; // found a recurrence on every possible day of the week!?! } kdtRecur = kdtNextRecur; nextOccurrence(kdtRecur, newdt, KAEvent::IGNORE_REPETITION); kdtNextRecur = newdt.effectiveKDateTime(); dateRecur = kdtRecur.date(); dayRecur = dateRecur.dayOfWeek() - 1; if (recurDuringWork && mWorkDays.testBit(dayRecur)) { mMainWorkTrigger = kdtRecur; mAllWorkTrigger = kdtRecur.addSecs(-60 * reminder); return; } days |= 1 << dayRecur; kdt = kdtRecur; } // Find the next recurrence before a seasonal time change, // and ensure the time change is after the last one processed. checkTimeChangeOnly = true; const int i = KAEventPrivate::transitionIndex(kdtRecur.toUtc().qDateTime(), tzTransitions); if (i < 0) { return; } if (i > transitionIndex) { transitionIndex = i; } if (++transitionIndex >= tzTransitions.count()) { return; } kdt = KADateTime(tzTransitions[transitionIndex].atUtc); previousOccurrence(kdt, newdt, KAEvent::IGNORE_REPETITION); kdtRecur = newdt.effectiveKDateTime(); } return; // not found - give up } } /****************************************************************************** * Find the repeat count to the next start of a working day. * This allows for possible daylight saving time changes during the repetition. * Use for repetitions which occur at different times of day. */ int KAEventPrivate::nextWorkRepetition(const KADateTime &pre) const { KADateTime nextWork(pre); if (pre.time() < mWorkDayStart) { nextWork.setTime(mWorkDayStart); } else { const int preDay = pre.date().dayOfWeek() - 1; // Monday = 0 for (int n = 1; ; ++n) { if (n >= 7) { return mRepetition.count() + 1; // should never happen } if (mWorkDays.testBit((preDay + n) % 7)) { nextWork = nextWork.addDays(n); nextWork.setTime(mWorkDayStart); break; } } } return (pre.secsTo(nextWork) - 1) / mRepetition.intervalSeconds() + 1; } /****************************************************************************** * Check whether an alarm which recurs at the same time of day can possibly * occur during working hours. * This does not determine whether it actually does, but rather whether it could * potentially given enough repetitions. * Reply = false if it can never occur during working hours, true if it might. */ bool KAEventPrivate::mayOccurDailyDuringWork(const KADateTime &kdt) const { if (!kdt.isDateOnly() && (kdt.time() < mWorkDayStart || kdt.time() >= mWorkDayEnd)) { return false; // its time is outside working hours } // Check if it always occurs on the same day of the week const Duration interval = mRecurrence->regularInterval(); if (!interval.isNull() && interval.isDaily() && !(interval.asDays() % 7)) { // It recurs weekly if (!mRepetition || (mRepetition.isDaily() && !(mRepetition.intervalDays() % 7))) { return false; // any repetitions are also weekly } // Repetitions are daily. Check if any occur on working days // by checking the first recurrence and up to 6 repetitions. int day = mRecurrence->startDateTime().date().dayOfWeek() - 1; // Monday = 0 const int repeatDays = mRepetition.intervalDays(); const int maxRepeat = (mRepetition.count() < 6) ? mRepetition.count() : 6; for (int i = 0; !mWorkDays.testBit(day); ++i, day = (day + repeatDays) % 7) { if (i >= maxRepeat) { return false; // no working day occurrences } } } return true; } /****************************************************************************** * Set the specified alarm to be an audio alarm with the given file name. */ void KAEventPrivate::setAudioAlarm(const Alarm::Ptr &alarm) const { alarm->setAudioAlarm(mAudioFile); // empty for a beep or for speaking if (mSoundVolume >= 0) alarm->setCustomProperty(KACalendar::APPNAME, VOLUME_PROPERTY, QStringLiteral("%1;%2;%3").arg(QString::number(mSoundVolume, 'f', 2), QString::number(mFadeVolume, 'f', 2), QString::number(mFadeSeconds))); } /****************************************************************************** * Get the date/time of the next recurrence of the event, after the specified * date/time. * 'result' = date/time of next occurrence, or invalid date/time if none. */ KAEvent::OccurType KAEventPrivate::nextRecurrence(const KADateTime &preDateTime, DateTime &result) const { const KADateTime recurStart = mRecurrence->startDateTime(); KADateTime pre = preDateTime.toTimeSpec(mStartDateTime.timeSpec()); if (mStartDateTime.isDateOnly() && !pre.isDateOnly() && pre.time() < DateTime::startOfDay()) { pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come pre.setTime(DateTime::startOfDay()); } const KADateTime dt = mRecurrence->getNextDateTime(pre); result = dt; result.setDateOnly(mStartDateTime.isDateOnly()); if (!dt.isValid()) { return KAEvent::NO_OCCURRENCE; } if (dt == recurStart) { return KAEvent::FIRST_OR_ONLY_OCCURRENCE; } if (mRecurrence->duration() >= 0 && dt == mRecurrence->endDateTime()) { return KAEvent::LAST_RECURRENCE; } return result.isDateOnly() ? KAEvent::RECURRENCE_DATE : KAEvent::RECURRENCE_DATE_TIME; } /****************************************************************************** * Validate the event's recurrence data, correcting any inconsistencies (which * should never occur!). * Reply = recurrence period type. */ KARecurrence::Type KAEventPrivate::checkRecur() const { if (mRecurrence) { KARecurrence::Type type = mRecurrence->type(); switch (type) { case KARecurrence::MINUTELY: // hourly case KARecurrence::DAILY: // daily case KARecurrence::WEEKLY: // weekly on multiple days of week case KARecurrence::MONTHLY_DAY: // monthly on multiple dates in month case KARecurrence::MONTHLY_POS: // monthly on multiple nth day of week case KARecurrence::ANNUAL_DATE: // annually on multiple months (day of month = start date) case KARecurrence::ANNUAL_POS: // annually on multiple nth day of week in multiple months return type; default: if (mRecurrence) { const_cast(this)->clearRecur(); // this shouldn't ever be necessary!! } break; } } if (mRepetition) { // can't have a repetition without a recurrence const_cast(this)->clearRecur(); // this shouldn't ever be necessary!! } return KARecurrence::NO_RECUR; } /****************************************************************************** * If the calendar was written by a previous version of KAlarm, do any * necessary format conversions on the events to ensure that when the calendar * is saved, no information is lost or corrupted. * Reply = true if any conversions were done. */ bool KAEvent::convertKCalEvents(const Calendar::Ptr &calendar, int calendarVersion) { // KAlarm pre-0.9 codes held in the alarm's DESCRIPTION property static const QChar SEPARATOR = QLatin1Char(';'); static const QChar LATE_CANCEL_CODE = QLatin1Char('C'); static const QChar AT_LOGIN_CODE = QLatin1Char('L'); // subsidiary alarm at every login static const QChar DEFERRAL_CODE = QLatin1Char('D'); // extra deferred alarm static const QString TEXT_PREFIX = QStringLiteral("TEXT:"); static const QString FILE_PREFIX = QStringLiteral("FILE:"); static const QString COMMAND_PREFIX = QStringLiteral("CMD:"); // KAlarm pre-0.9.2 codes held in the event's CATEGORY property static const QString BEEP_CATEGORY = QStringLiteral("BEEP"); // KAlarm pre-1.1.1 LATECANCEL category with no parameter static const QString LATE_CANCEL_CAT = QStringLiteral("LATECANCEL"); // KAlarm pre-1.3.0 TMPLDEFTIME category with no parameter static const QString TEMPL_DEF_TIME_CAT = QStringLiteral("TMPLDEFTIME"); // KAlarm pre-1.3.1 XTERM category static const QString EXEC_IN_XTERM_CAT = QStringLiteral("XTERM"); // KAlarm pre-1.9.0 categories static const QString DATE_ONLY_CATEGORY = QStringLiteral("DATE"); static const QString EMAIL_BCC_CATEGORY = QStringLiteral("BCC"); static const QString CONFIRM_ACK_CATEGORY = QStringLiteral("ACKCONF"); static const QString KORGANIZER_CATEGORY = QStringLiteral("KORG"); static const QString DEFER_CATEGORY = QStringLiteral("DEFER;"); static const QString ARCHIVE_CATEGORY = QStringLiteral("SAVE"); static const QString ARCHIVE_CATEGORIES = QStringLiteral("SAVE:"); static const QString LATE_CANCEL_CATEGORY = QStringLiteral("LATECANCEL;"); static const QString AUTO_CLOSE_CATEGORY = QStringLiteral("LATECLOSE;"); static const QString TEMPL_AFTER_TIME_CATEGORY = QStringLiteral("TMPLAFTTIME;"); static const QString KMAIL_SERNUM_CATEGORY = QStringLiteral("KMAIL:"); static const QString LOG_CATEGORY = QStringLiteral("LOG:"); // KAlarm pre-1.5.0/1.9.9 properties static const QByteArray KMAIL_ID_PROPERTY("KMAILID"); // X-KDE-KALARM-KMAILID property // KAlarm pre-2.6.0 properties static const QByteArray ARCHIVE_PROPERTY("ARCHIVE"); // X-KDE-KALARM-ARCHIVE property static const QString ARCHIVE_REMINDER_ONCE_TYPE = QStringLiteral("ONCE"); static const QString REMINDER_ONCE_TYPE = QStringLiteral("REMINDER_ONCE"); static const QByteArray EMAIL_ID_PROPERTY("EMAILID"); // X-KDE-KALARM-EMAILID property static const QByteArray SPEAK_PROPERTY("SPEAK"); // X-KDE-KALARM-SPEAK property static const QByteArray CANCEL_ON_ERROR_PROPERTY("ERRCANCEL");// X-KDE-KALARM-ERRCANCEL property static const QByteArray DONT_SHOW_ERROR_PROPERTY("ERRNOSHOW");// X-KDE-KALARM-ERRNOSHOW property bool adjustSummerTime = false; if (calendarVersion == -Version(0, 5, 7)) { // The calendar file was written by the KDE 3.0.0 version of KAlarm 0.5.7. // Summer time was ignored when converting to UTC. calendarVersion = -calendarVersion; adjustSummerTime = true; } if (calendarVersion >= currentCalendarVersion()) { return false; } qCDebug(KALARMCAL_LOG) << "Adjusting version" << calendarVersion; const bool pre_0_7 = (calendarVersion < Version(0, 7, 0)); const bool pre_0_9 = (calendarVersion < Version(0, 9, 0)); const bool pre_0_9_2 = (calendarVersion < Version(0, 9, 2)); const bool pre_1_1_1 = (calendarVersion < Version(1, 1, 1)); const bool pre_1_2_1 = (calendarVersion < Version(1, 2, 1)); const bool pre_1_3_0 = (calendarVersion < Version(1, 3, 0)); const bool pre_1_3_1 = (calendarVersion < Version(1, 3, 1)); const bool pre_1_4_14 = (calendarVersion < Version(1, 4, 14)); const bool pre_1_5_0 = (calendarVersion < Version(1, 5, 0)); const bool pre_1_9_0 = (calendarVersion < Version(1, 9, 0)); const bool pre_1_9_2 = (calendarVersion < Version(1, 9, 2)); const bool pre_1_9_7 = (calendarVersion < Version(1, 9, 7)); const bool pre_1_9_9 = (calendarVersion < Version(1, 9, 9)); const bool pre_1_9_10 = (calendarVersion < Version(1, 9, 10)); const bool pre_2_2_9 = (calendarVersion < Version(2, 2, 9)); const bool pre_2_3_0 = (calendarVersion < Version(2, 3, 0)); const bool pre_2_3_2 = (calendarVersion < Version(2, 3, 2)); const bool pre_2_7_0 = (calendarVersion < Version(2, 7, 0)); Q_ASSERT(currentCalendarVersion() == Version(2, 7, 0)); const QTimeZone localZone = QTimeZone::systemTimeZone(); bool converted = false; const Event::List events = calendar->rawEvents(); for (int ei = 0, eend = events.count(); ei < eend; ++ei) { Event::Ptr event = events[ei]; const Alarm::List alarms = event->alarms(); if (alarms.isEmpty()) { continue; // KAlarm isn't interested in events without alarms } event->startUpdates(); // prevent multiple update notifications const bool readOnly = event->isReadOnly(); if (readOnly) { event->setReadOnly(false); } QStringList cats = event->categories(); bool addLateCancel = false; QStringList flags; if (pre_0_7 && event->allDay()) { // It's a KAlarm pre-0.7 calendar file. // Ensure that when the calendar is saved, the alarm time isn't lost. event->setAllDay(false); } if (pre_0_9) { /* * It's a KAlarm pre-0.9 calendar file. * All alarms were of type DISPLAY. Instead of the X-KDE-KALARM-TYPE * alarm property, characteristics were stored as a prefix to the * alarm DESCRIPTION property, as follows: * SEQNO;[FLAGS];TYPE:TEXT * where * SEQNO = sequence number of alarm within the event * FLAGS = C for late-cancel, L for repeat-at-login, D for deferral * TYPE = TEXT or FILE or CMD * TEXT = message text, file name/URL or command */ for (int ai = 0, aend = alarms.count(); ai < aend; ++ai) { Alarm::Ptr alarm = alarms[ai]; bool atLogin = false; bool deferral = false; bool lateCancel = false; KAAlarm::Action action = KAAlarm::MESSAGE; const QString txt = alarm->text(); const int length = txt.length(); int i = 0; if (txt[0].isDigit()) { while (++i < length && txt[i].isDigit()) ; if (i < length && txt[i++] == SEPARATOR) { while (i < length) { const QChar ch = txt[i++]; if (ch == SEPARATOR) { break; } if (ch == LATE_CANCEL_CODE) { lateCancel = true; } else if (ch == AT_LOGIN_CODE) { atLogin = true; } else if (ch == DEFERRAL_CODE) { deferral = true; } } } else { i = 0; // invalid prefix } } if (txt.indexOf(TEXT_PREFIX, i) == i) { i += TEXT_PREFIX.length(); } else if (txt.indexOf(FILE_PREFIX, i) == i) { action = KAAlarm::FILE; i += FILE_PREFIX.length(); } else if (txt.indexOf(COMMAND_PREFIX, i) == i) { action = KAAlarm::COMMAND; i += COMMAND_PREFIX.length(); } else { i = 0; } const QString altxt = txt.mid(i); QStringList types; switch (action) { case KAAlarm::FILE: types += KAEventPrivate::FILE_TYPE; // fall through to MESSAGE Q_FALLTHROUGH(); case KAAlarm::MESSAGE: alarm->setDisplayAlarm(altxt); break; case KAAlarm::COMMAND: setProcedureAlarm(alarm, altxt); break; case KAAlarm::EMAIL: // email alarms were introduced in KAlarm 0.9 case KAAlarm::AUDIO: // audio alarms (with no display) were introduced in KAlarm 2.3.2 break; } if (atLogin) { types += KAEventPrivate::AT_LOGIN_TYPE; lateCancel = false; } else if (deferral) { types += KAEventPrivate::TIME_DEFERRAL_TYPE; } if (lateCancel) { addLateCancel = true; } if (types.count() > 0) { alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY, types.join(QStringLiteral(","))); } if (pre_0_7 && alarm->repeatCount() > 0 && alarm->snoozeTime().value() > 0) { // It's a KAlarm pre-0.7 calendar file. // Minutely recurrences were stored differently. Recurrence *recur = event->recurrence(); if (recur && recur->recurs()) { recur->setMinutely(alarm->snoozeTime().asSeconds() / 60); recur->setDuration(alarm->repeatCount() + 1); alarm->setRepeatCount(0); alarm->setSnoozeTime(0); } } if (adjustSummerTime) { // The calendar file was written by the KDE 3.0.0 version of KAlarm 0.5.7. // Summer time was ignored when converting to UTC. KADateTime dt(alarm->time()); const time_t t = dt.toTime_t(); const struct tm *dtm = localtime(&t); if (dtm->tm_isdst) { dt = dt.addSecs(-3600); alarm->setTime(dt.qDateTime()); } } } } if (pre_0_9_2) { /* * It's a KAlarm pre-0.9.2 calendar file. * For the archive calendar, set the CREATED time to the DTEND value. * Convert date-only DTSTART to date/time, and add category "DATE". * Set the DTEND time to the DTSTART time. * Convert all alarm times to DTSTART offsets. * For display alarms, convert the first unlabelled category to an * X-KDE-KALARM-FONTCOLOUR property. * Convert BEEP category into an audio alarm with no audio file. */ if (CalEvent::status(event) == CalEvent::ARCHIVED) { event->setCreated(event->dtEnd()); } QDateTime start = event->dtStart(); if (event->allDay()) { start.setTime(QTime(0, 0)); flags += KAEventPrivate::DATE_ONLY_FLAG; } event->setDtEnd(QDateTime()); for (int ai = 0, aend = alarms.count(); ai < aend; ++ai) { Alarm::Ptr alarm = alarms[ai]; alarm->setStartOffset(start.secsTo(alarm->time())); } if (!cats.isEmpty()) { for (int ai = 0, aend = alarms.count(); ai < aend; ++ai) { Alarm::Ptr alarm = alarms[ai]; if (alarm->type() == Alarm::Display) alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FONT_COLOUR_PROPERTY, QStringLiteral("%1;;").arg(cats.at(0))); } cats.removeAt(0); } for (int i = 0, end = cats.count(); i < end; ++i) { if (cats.at(i) == BEEP_CATEGORY) { cats.removeAt(i); Alarm::Ptr alarm = event->newAlarm(); alarm->setEnabled(true); alarm->setAudioAlarm(); QDateTime dt = event->dtStart(); // default // Parse and order the alarms to know which one's date/time to use KAEventPrivate::AlarmMap alarmMap; KAEventPrivate::readAlarms(event, &alarmMap); KAEventPrivate::AlarmMap::ConstIterator it = alarmMap.constBegin(); if (it != alarmMap.constEnd()) { dt = it.value().alarm->time(); break; } alarm->setStartOffset(start.secsTo(dt)); break; } } } if (pre_1_1_1) { /* * It's a KAlarm pre-1.1.1 calendar file. * Convert simple LATECANCEL category to LATECANCEL:n where n = minutes late. */ int i; while ((i = cats.indexOf(LATE_CANCEL_CAT)) >= 0) { cats.removeAt(i); addLateCancel = true; } } if (pre_1_2_1) { /* * It's a KAlarm pre-1.2.1 calendar file. * Convert email display alarms from translated to untranslated header prefixes. */ for (int ai = 0, aend = alarms.count(); ai < aend; ++ai) { Alarm::Ptr alarm = alarms[ai]; if (alarm->type() == Alarm::Display) { const QString oldtext = alarm->text(); const QString newtext = AlarmText::toCalendarText(oldtext); if (oldtext != newtext) { alarm->setDisplayAlarm(newtext); } } } } if (pre_1_3_0) { /* * It's a KAlarm pre-1.3.0 calendar file. * Convert simple TMPLDEFTIME category to TMPLAFTTIME:n where n = minutes after. */ int i; while ((i = cats.indexOf(TEMPL_DEF_TIME_CAT)) >= 0) { cats.removeAt(i); (flags += KAEventPrivate::TEMPL_AFTER_TIME_FLAG) += QStringLiteral("0"); } } if (pre_1_3_1) { /* * It's a KAlarm pre-1.3.1 calendar file. * Convert simple XTERM category to LOG:xterm: */ int i; while ((i = cats.indexOf(EXEC_IN_XTERM_CAT)) >= 0) { cats.removeAt(i); event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::LOG_PROPERTY, KAEventPrivate::xtermURL); } } if (pre_1_9_0) { /* * It's a KAlarm pre-1.9 calendar file. * Add the X-KDE-KALARM-STATUS custom property. * Convert KAlarm categories to custom fields. */ CalEvent::setStatus(event, CalEvent::status(event)); for (int i = 0; i < cats.count();) { const QString cat = cats.at(i); if (cat == DATE_ONLY_CATEGORY) { flags += KAEventPrivate::DATE_ONLY_FLAG; } else if (cat == CONFIRM_ACK_CATEGORY) { flags += KAEventPrivate::CONFIRM_ACK_FLAG; } else if (cat == EMAIL_BCC_CATEGORY) { flags += KAEventPrivate::EMAIL_BCC_FLAG; } else if (cat == KORGANIZER_CATEGORY) { flags += KAEventPrivate::KORGANIZER_FLAG; } else if (cat.startsWith(DEFER_CATEGORY)) { (flags += KAEventPrivate::DEFER_FLAG) += cat.mid(DEFER_CATEGORY.length()); } else if (cat.startsWith(TEMPL_AFTER_TIME_CATEGORY)) { (flags += KAEventPrivate::TEMPL_AFTER_TIME_FLAG) += cat.mid(TEMPL_AFTER_TIME_CATEGORY.length()); } else if (cat.startsWith(LATE_CANCEL_CATEGORY)) { (flags += KAEventPrivate::LATE_CANCEL_FLAG) += cat.mid(LATE_CANCEL_CATEGORY.length()); } else if (cat.startsWith(AUTO_CLOSE_CATEGORY)) { (flags += KAEventPrivate::AUTO_CLOSE_FLAG) += cat.mid(AUTO_CLOSE_CATEGORY.length()); } else if (cat.startsWith(KMAIL_SERNUM_CATEGORY)) { - (flags += KAEventPrivate::KMAIL_SERNUM_FLAG) += cat.mid(KMAIL_SERNUM_CATEGORY.length()); + (flags += KAEventPrivate::KMAIL_ITEM_FLAG) += cat.mid(KMAIL_SERNUM_CATEGORY.length()); } else if (cat == ARCHIVE_CATEGORY) { event->setCustomProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY, QStringLiteral("0")); } else if (cat.startsWith(ARCHIVE_CATEGORIES)) { event->setCustomProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY, cat.mid(ARCHIVE_CATEGORIES.length())); } else if (cat.startsWith(LOG_CATEGORY)) { event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::LOG_PROPERTY, cat.mid(LOG_CATEGORY.length())); } else { ++i; // Not a KAlarm category, so leave it continue; } cats.removeAt(i); } } if (pre_1_9_2) { /* * It's a KAlarm pre-1.9.2 calendar file. * Convert from clock time to the local system time zone. */ event->shiftTimes(localZone, localZone); converted = true; } if (addLateCancel) { (flags += KAEventPrivate::LATE_CANCEL_FLAG) += QStringLiteral("1"); } if (!flags.isEmpty()) { event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY, flags.join(KAEventPrivate::SC)); } event->setCategories(cats); if ((pre_1_4_14 || (pre_1_9_7 && !pre_1_9_0)) && event->recurrence() && event->recurrence()->recurs()) { /* * It's a KAlarm pre-1.4.14 or KAlarm 1.9 series pre-1.9.7 calendar file. * For recurring events, convert the main alarm offset to an absolute * time in the X-KDE-KALARM-NEXTRECUR property, and set main alarm * offsets to zero, and convert deferral alarm offsets to be relative to * the next recurrence. */ const QStringList flags = event->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY).split(KAEventPrivate::SC, QString::SkipEmptyParts); const bool dateOnly = flags.contains(KAEventPrivate::DATE_ONLY_FLAG); KADateTime startDateTime(event->dtStart()); if (dateOnly) { startDateTime.setDateOnly(true); } // Convert the main alarm and get the next main trigger time from it KADateTime nextMainDateTime; bool mainExpired = true; for (int i = 0, alend = alarms.count(); i < alend; ++i) { Alarm::Ptr alarm = alarms[i]; if (!alarm->hasStartOffset()) { continue; } // Find whether the alarm triggers at the same time as the main // alarm, in which case its offset needs to be set to 0. The // following trigger with the main alarm: // - Additional audio alarm // - PRE_ACTION_TYPE // - POST_ACTION_TYPE // - DISPLAYING_TYPE bool mainAlarm = true; QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY); const QStringList types = property.split(QLatin1Char(','), QString::SkipEmptyParts); for (int t = 0; t < types.count(); ++t) { QString type = types[t]; if (type == KAEventPrivate::AT_LOGIN_TYPE || type == KAEventPrivate::TIME_DEFERRAL_TYPE || type == KAEventPrivate::DATE_DEFERRAL_TYPE || type == KAEventPrivate::REMINDER_TYPE || type == REMINDER_ONCE_TYPE) { mainAlarm = false; break; } } if (mainAlarm) { if (mainExpired) { // All main alarms are supposed to be at the same time, so // don't readjust the event's time for subsequent main alarms. mainExpired = false; nextMainDateTime = KADateTime(alarm->time()); nextMainDateTime.setDateOnly(dateOnly); nextMainDateTime = nextMainDateTime.toTimeSpec(startDateTime); if (nextMainDateTime != startDateTime) { QDateTime dt = nextMainDateTime.qDateTime(); event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::NEXT_RECUR_PROPERTY, dt.toString(dateOnly ? QStringLiteral("yyyyMMdd") : QStringLiteral("yyyyMMddThhmmss"))); } } alarm->setStartOffset(0); converted = true; } } int adjustment; if (mainExpired) { // It's an expired recurrence. // Set the alarm offset relative to the first actual occurrence // (taking account of possible exceptions). KADateTime dt(event->recurrence()->getNextDateTime(startDateTime.qDateTime().addDays(-1))); dt.setDateOnly(dateOnly); adjustment = startDateTime.secsTo(dt); } else { adjustment = startDateTime.secsTo(nextMainDateTime); } if (adjustment) { // Convert deferred alarms for (int i = 0, alend = alarms.count(); i < alend; ++i) { Alarm::Ptr alarm = alarms[i]; if (!alarm->hasStartOffset()) { continue; } const QString property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY); const QStringList types = property.split(QLatin1Char(','), QString::SkipEmptyParts); for (int t = 0; t < types.count(); ++t) { const QString type = types[t]; if (type == KAEventPrivate::TIME_DEFERRAL_TYPE || type == KAEventPrivate::DATE_DEFERRAL_TYPE) { alarm->setStartOffset(alarm->startOffset().asSeconds() - adjustment); converted = true; break; } } } } } if (pre_1_5_0 || (pre_1_9_9 && !pre_1_9_0)) { /* * It's a KAlarm pre-1.5.0 or KAlarm 1.9 series pre-1.9.9 calendar file. * Convert email identity names to uoids. */ for (int i = 0, alend = alarms.count(); i < alend; ++i) { Alarm::Ptr alarm = alarms[i]; const QString name = alarm->customProperty(KACalendar::APPNAME, KMAIL_ID_PROPERTY); if (name.isEmpty()) { continue; } const uint id = Identities::identityUoid(name); if (id) { alarm->setCustomProperty(KACalendar::APPNAME, EMAIL_ID_PROPERTY, QString::number(id)); } alarm->removeCustomProperty(KACalendar::APPNAME, KMAIL_ID_PROPERTY); converted = true; } } if (pre_1_9_10) { /* * It's a KAlarm pre-1.9.10 calendar file. * Convert simple repetitions without a recurrence, to a recurrence. */ if (KAEventPrivate::convertRepetition(event)) { converted = true; } } if (pre_2_2_9 || (pre_2_3_2 && !pre_2_3_0)) { /* * It's a KAlarm pre-2.2.9 or KAlarm 2.3 series pre-2.3.2 calendar file. * Set the time in the calendar for all date-only alarms to 00:00. */ if (KAEventPrivate::convertStartOfDay(event)) { converted = true; } } if (pre_2_7_0) { /* * It's a KAlarm pre-2.7.0 calendar file. * Archive and at-login flags were stored in event's ARCHIVE property when the main alarm had expired. * Reminder parameters were stored in event's ARCHIVE property when no reminder was pending. * Negative reminder periods (i.e. alarm offset > 0) were invalid, so convert to 0. * Now store reminder information in FLAGS property, whether reminder is pending or not. * Move EMAILID, SPEAK, ERRCANCEL and ERRNOSHOW alarm properties into new FLAGS property. */ bool flagsValid = false; QStringList flags; QString reminder; bool reminderOnce = false; const QString prop = event->customProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY); if (!prop.isEmpty()) { // Convert the event's ARCHIVE property to parameters in the FLAGS property flags = event->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY).split(KAEventPrivate::SC, QString::SkipEmptyParts); flags << KAEventPrivate::ARCHIVE_FLAG; flagsValid = true; if (prop != QLatin1String("0")) { // "0" was a dummy parameter if no others were present // It's the archive property containing a reminder time and/or repeat-at-login flag. // This was present when no reminder/at-login alarm was pending. const QStringList list = prop.split(KAEventPrivate::SC, QString::SkipEmptyParts); for (int i = 0; i < list.count(); ++i) { if (list[i] == KAEventPrivate::AT_LOGIN_TYPE) { flags << KAEventPrivate::AT_LOGIN_TYPE; } else if (list[i] == ARCHIVE_REMINDER_ONCE_TYPE) { reminderOnce = true; } else if (!list[i].isEmpty() && !list[i].startsWith(QChar::fromLatin1('-'))) { reminder = list[i]; } } } event->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY, flags.join(KAEventPrivate::SC)); event->removeCustomProperty(KACalendar::APPNAME, ARCHIVE_PROPERTY); } for (int i = 0, alend = alarms.count(); i < alend; ++i) { Alarm::Ptr alarm = alarms[i]; // Convert EMAILID, SPEAK, ERRCANCEL, ERRNOSHOW properties QStringList flags; QString property = alarm->customProperty(KACalendar::APPNAME, EMAIL_ID_PROPERTY); if (!property.isEmpty()) { flags << KAEventPrivate::EMAIL_ID_FLAG << property; alarm->removeCustomProperty(KACalendar::APPNAME, EMAIL_ID_PROPERTY); } if (!alarm->customProperty(KACalendar::APPNAME, SPEAK_PROPERTY).isEmpty()) { flags << KAEventPrivate::SPEAK_FLAG; alarm->removeCustomProperty(KACalendar::APPNAME, SPEAK_PROPERTY); } if (!alarm->customProperty(KACalendar::APPNAME, CANCEL_ON_ERROR_PROPERTY).isEmpty()) { flags << KAEventPrivate::CANCEL_ON_ERROR_FLAG; alarm->removeCustomProperty(KACalendar::APPNAME, CANCEL_ON_ERROR_PROPERTY); } if (!alarm->customProperty(KACalendar::APPNAME, DONT_SHOW_ERROR_PROPERTY).isEmpty()) { flags << KAEventPrivate::DONT_SHOW_ERROR_FLAG; alarm->removeCustomProperty(KACalendar::APPNAME, DONT_SHOW_ERROR_PROPERTY); } if (!flags.isEmpty()) { alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY, flags.join(KAEventPrivate::SC)); } // Invalidate negative reminder periods in alarms if (!alarm->hasStartOffset()) { continue; } property = alarm->customProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY); QStringList types = property.split(QChar::fromLatin1(','), QString::SkipEmptyParts); const int r = types.indexOf(REMINDER_ONCE_TYPE); if (r >= 0) { // Move reminder-once indicator from the alarm to the event's FLAGS property types[r] = KAEventPrivate::REMINDER_TYPE; alarm->setCustomProperty(KACalendar::APPNAME, KAEventPrivate::TYPE_PROPERTY, types.join(QChar::fromLatin1(','))); reminderOnce = true; } if (r >= 0 || types.contains(KAEventPrivate::REMINDER_TYPE)) { // The alarm is a reminder alarm const int offset = alarm->startOffset().asSeconds(); if (offset > 0) { alarm->setStartOffset(0); converted = true; } else if (offset < 0) { reminder = reminderToString(offset / 60); } } } if (!reminder.isEmpty()) { // Write reminder parameters into the event's FLAGS property if (!flagsValid) { flags = event->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY).split(KAEventPrivate::SC, QString::SkipEmptyParts); } if (!flags.contains(KAEventPrivate::REMINDER_TYPE)) { flags += KAEventPrivate::REMINDER_TYPE; if (reminderOnce) { flags += KAEventPrivate::REMINDER_ONCE_FLAG; } flags += reminder; } } } if (readOnly) { event->setReadOnly(true); } event->endUpdates(); // finally issue an update notification } return converted; } /****************************************************************************** * Set the time for a date-only event to 00:00. * Reply = true if the event was updated. */ bool KAEventPrivate::convertStartOfDay(const Event::Ptr &event) { bool changed = false; const QTime midnight(0, 0); const QStringList flags = event->customProperty(KACalendar::APPNAME, KAEventPrivate::FLAGS_PROPERTY).split(KAEventPrivate::SC, QString::SkipEmptyParts); if (flags.contains(KAEventPrivate::DATE_ONLY_FLAG)) { // It's an untimed event, so fix it const QDateTime oldDt = event->dtStart(); const int adjustment = oldDt.time().secsTo(midnight); if (adjustment) { event->setDtStart(QDateTime(oldDt.date(), midnight, oldDt.timeSpec())); int deferralOffset = 0; AlarmMap alarmMap; readAlarms(event, &alarmMap); for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it) { const AlarmData &data = it.value(); if (!data.alarm->hasStartOffset()) { continue; } if (data.timedDeferral) { // Found a timed deferral alarm, so adjust the offset deferralOffset = data.alarm->startOffset().asSeconds(); const_cast(data.alarm.data())->setStartOffset(deferralOffset - adjustment); } else if (data.type == AUDIO_ALARM && data.alarm->startOffset().asSeconds() == deferralOffset) { // Audio alarm is set for the same time as the above deferral alarm const_cast(data.alarm.data())->setStartOffset(deferralOffset - adjustment); } } changed = true; } } else { // It's a timed event. Fix any untimed alarms. bool foundDeferral = false; int deferralOffset = 0; int newDeferralOffset = 0; DateTime start; const KADateTime nextMainDateTime = readDateTime(event, false, false, start).kDateTime(); AlarmMap alarmMap; readAlarms(event, &alarmMap); for (AlarmMap::ConstIterator it = alarmMap.constBegin(); it != alarmMap.constEnd(); ++it) { const AlarmData &data = it.value(); if (!data.alarm->hasStartOffset()) { continue; } if ((data.type & DEFERRED_ALARM) && !data.timedDeferral) { // Found a date-only deferral alarm, so adjust its time QDateTime altime = data.alarm->startOffset().end(nextMainDateTime.qDateTime()); altime.setTime(midnight); deferralOffset = data.alarm->startOffset().asSeconds(); newDeferralOffset = event->dtStart().secsTo(altime); const_cast(data.alarm.data())->setStartOffset(newDeferralOffset); foundDeferral = true; changed = true; } else if (foundDeferral && data.type == AUDIO_ALARM && data.alarm->startOffset().asSeconds() == deferralOffset) { // Audio alarm is set for the same time as the above deferral alarm const_cast(data.alarm.data())->setStartOffset(newDeferralOffset); changed = true; } } } return changed; } /****************************************************************************** * Convert simple repetitions in an event without a recurrence, to a * recurrence. Repetitions which are an exact multiple of 24 hours are converted * to daily recurrences; else they are converted to minutely recurrences. Note * that daily and minutely recurrences produce different results when they span * a daylight saving time change. * Reply = true if any conversions were done. */ bool KAEventPrivate::convertRepetition(const Event::Ptr &event) { const Alarm::List alarms = event->alarms(); if (alarms.isEmpty()) { return false; } Recurrence *recur = event->recurrence(); // guaranteed to return non-null if (recur->recurs()) { return false; } bool converted = false; const bool readOnly = event->isReadOnly(); for (int ai = 0, aend = alarms.count(); ai < aend; ++ai) { Alarm::Ptr alarm = alarms[ai]; if (alarm->repeatCount() > 0 && alarm->snoozeTime().value() > 0) { if (!converted) { event->startUpdates(); // prevent multiple update notifications if (readOnly) { event->setReadOnly(false); } if ((alarm->snoozeTime().asSeconds() % (24 * 3600)) != 0) { recur->setMinutely(alarm->snoozeTime().asSeconds() / 60); } else { recur->setDaily(alarm->snoozeTime().asDays()); } recur->setDuration(alarm->repeatCount() + 1); converted = true; } alarm->setRepeatCount(0); alarm->setSnoozeTime(0); } } if (converted) { if (readOnly) { event->setReadOnly(true); } event->endUpdates(); // finally issue an update notification } return converted; } /*============================================================================= = Class KAAlarm = Corresponds to a single KCal::Alarm instance. =============================================================================*/ KAAlarm::KAAlarm() : d(new Private) { } KAAlarm::Private::Private() : mType(INVALID_ALARM), mNextRepeat(0), mRepeatAtLogin(false), mDeferred(false) { } KAAlarm::KAAlarm(const KAAlarm &other) : d(new Private(*other.d)) { } KAAlarm::~KAAlarm() { delete d; } KAAlarm &KAAlarm::operator=(const KAAlarm &other) { if (&other != this) { *d = *other.d; } return *this; } KAAlarm::Action KAAlarm::action() const { return d->mActionType; } bool KAAlarm::isValid() const { return d->mType != INVALID_ALARM; } KAAlarm::Type KAAlarm::type() const { return d->mType; } DateTime KAAlarm::dateTime(bool withRepeats) const { return (withRepeats && d->mNextRepeat && d->mRepetition) ? DateTime(d->mRepetition.duration(d->mNextRepeat).end(d->mNextMainDateTime.qDateTime())) : d->mNextMainDateTime; } QDate KAAlarm::date() const { return d->mNextMainDateTime.date(); } QTime KAAlarm::time() const { return d->mNextMainDateTime.effectiveTime(); } bool KAAlarm::repeatAtLogin() const { return d->mRepeatAtLogin; } bool KAAlarm::isReminder() const { return d->mType == REMINDER_ALARM; } bool KAAlarm::deferred() const { return d->mDeferred; } bool KAAlarm::timedDeferral() const { return d->mDeferred && d->mTimedDeferral; } void KAAlarm::setTime(const DateTime &dt) { d->mNextMainDateTime = dt; } void KAAlarm::setTime(const KADateTime &dt) { d->mNextMainDateTime = dt; } #ifdef KDE_NO_DEBUG_OUTPUT const char *KAAlarm::debugType(Type) { return ""; } #else const char *KAAlarm::debugType(Type type) { switch (type) { case MAIN_ALARM: return "MAIN"; case REMINDER_ALARM: return "REMINDER"; case DEFERRED_ALARM: return "DEFERRED"; case DEFERRED_REMINDER_ALARM: return "DEFERRED_REMINDER"; case AT_LOGIN_ALARM: return "LOGIN"; case DISPLAYING_ALARM: return "DISPLAYING"; default: return "INVALID"; } } #endif /*============================================================================= = Class EmailAddressList =============================================================================*/ /****************************************************************************** * Sets the list of email addresses, removing any empty addresses. * Reply = false if empty addresses were found. */ EmailAddressList &EmailAddressList::operator=(const Person::List &addresses) { clear(); for (int p = 0, end = addresses.count(); p < end; ++p) { if (!addresses[p].email().isEmpty()) { append(addresses[p]); } } return *this; } /****************************************************************************** * Return the email address list as a string list of email addresses. */ EmailAddressList::operator QStringList() const { QStringList list; for (int p = 0, end = count(); p < end; ++p) { list += address(p); } return list; } /****************************************************************************** * Return the email address list as a string, each address being delimited by * the specified separator string. */ QString EmailAddressList::join(const QString &separator) const { QString result; bool first = true; for (int p = 0, end = count(); p < end; ++p) { if (first) { first = false; } else { result += separator; } result += address(p); } return result; } /****************************************************************************** * Convert one item into an email address, including name. */ QString EmailAddressList::address(int index) const { if (index < 0 || index > count()) { return QString(); } QString result; bool quote = false; const Person &person = (*this)[index]; const QString name = person.name(); if (!name.isEmpty()) { // Need to enclose the name in quotes if it has any special characters for (int i = 0, len = name.length(); i < len; ++i) { const QChar ch = name[i]; if (!ch.isLetterOrNumber()) { quote = true; result += QLatin1Char('\"'); break; } } result += (*this)[index].name(); result += (quote ? QLatin1String("\" <") : QLatin1String(" <")); quote = true; // need angle brackets round email address } result += person.email(); if (quote) { result += QLatin1Char('>'); } return result; } /****************************************************************************** * Return a list of the pure email addresses, excluding names. */ QStringList EmailAddressList::pureAddresses() const { QStringList list; for (int p = 0, end = count(); p < end; ++p) { list += at(p).email(); } return list; } /****************************************************************************** * Return a list of the pure email addresses, excluding names, as a string. */ QString EmailAddressList::pureAddresses(const QString &separator) const { QString result; bool first = true; for (int p = 0, end = count(); p < end; ++p) { if (first) { first = false; } else { result += separator; } result += at(p).email(); } return result; } /*============================================================================= = Static functions =============================================================================*/ /****************************************************************************** * Set the specified alarm to be a procedure alarm with the given command line. * The command line is first split into its program file and arguments before * initialising the alarm. */ static void setProcedureAlarm(const Alarm::Ptr &alarm, const QString &commandLine) { //TODO: cater for environment variables prefixed to command QString command; QString arguments; QChar quoteChar; bool quoted = false; const uint posMax = commandLine.length(); uint pos; for (pos = 0; pos < posMax; ++pos) { const QChar ch = commandLine[pos]; if (quoted) { if (ch == quoteChar) { ++pos; // omit the quote character break; } command += ch; } else { bool done = false; switch (ch.toLatin1()) { case ' ': case ';': case '|': case '<': case '>': done = !command.isEmpty(); break; case '\'': case '"': if (command.isEmpty()) { // Start of a quoted string. Omit the quote character. quoted = true; quoteChar = ch; break; } // fall through to default Q_FALLTHROUGH(); default: command += ch; break; } if (done) { break; } } } // Skip any spaces after the command for (; pos < posMax && commandLine[pos] == QLatin1Char(' '); ++pos) ; arguments = commandLine.mid(pos); alarm->setProcedureAlarm(command, arguments); } /****************************************************************************** * Converts a reminder interval into a parameter string for the * X-KDE-KALARM-FLAGS property. */ QString reminderToString(int minutes) { char unit = 'M'; int count = abs(minutes); if (count % 1440 == 0) { unit = 'D'; count /= 1440; } else if (count % 60 == 0) { unit = 'H'; count /= 60; } if (minutes < 0) { count = -count; } return QStringLiteral("%1%2").arg(count).arg(unit); } } // namespace KAlarmCal // vim: et sw=4: diff --git a/src/kaevent.h b/src/kaevent.h index 4a0e359..e9c0cd7 100644 --- a/src/kaevent.h +++ b/src/kaevent.h @@ -1,1319 +1,1325 @@ /* * kaevent.h - represents calendar events * This file is part of kalarmcal library, which provides access to KAlarm * calendar data. - * Copyright © 2001-2013 by David Jarvie + * Copyright © 2001-2019 David Jarvie * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as published * by the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public * License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #ifndef KALARM_KAEVENT_H #define KALARM_KAEVENT_H #include "kalarmcal_export.h" #include "datetime.h" #include "karecurrence.h" #include "kacalendar.h" #include "repetition.h" #include #include #include #include +#include #include #include #include #include #include #include namespace KHolidays { class HolidayRegion; } namespace KAlarmCal { /** * @short KAAlarm represents individual alarms within a KAEvent. * * The KAAlarm class represents one of the main or subsidiary alarms in * a KAEvent instance. It contains the alarm's type and trigger time. * * Note that valid KAAlarm instances can only be created by the KAEvent * class. * * @see KAEvent::alarm(), KAEvent::firstAlarm(), KAEvent::nextAlarm(). * * @author David Jarvie */ class KALARMCAL_EXPORT KAAlarm { public: /** The basic KAAlarm action types. */ enum Action { MESSAGE, //!< KCal::Alarm::Display type: display a text message FILE, //!< KCal::Alarm::Display type: display a file (URL given by the alarm text) COMMAND, //!< KCal::Alarm::Procedure type: execute a shell command EMAIL, //!< KCal::Alarm::Email type: send an email AUDIO //!< KCal::Alarm::Audio type: play a sound file }; /** Alarm types. * KAAlarm's of different types may be contained in a KAEvent, * each KAAlarm defining a different component of the overall alarm. */ enum Type { INVALID_ALARM = 0, //!< Not an alarm MAIN_ALARM = 1, //!< THE real alarm. Must be the first in the enumeration. REMINDER_ALARM = 0x02, //!< Reminder in advance of/after the main alarm DEFERRED_ALARM = 0x04, //!< Deferred alarm DEFERRED_REMINDER_ALARM = REMINDER_ALARM | DEFERRED_ALARM, //!< Deferred reminder alarm // The following values must be greater than the preceding ones, to // ensure that in ordered processing they are processed afterwards. AT_LOGIN_ALARM = 0x10, //!< Additional repeat-at-login trigger DISPLAYING_ALARM = 0x20 //!< Copy of the alarm currently being displayed // IMPORTANT: if any values are added to this list, ensure that the // KAEventPrivate::AlarmType enum is adjusted similarly. }; /** Default constructor, which creates an invalid instance. */ KAAlarm(); /** Copy constructor. */ KAAlarm(const KAAlarm &other); /** Destructor. */ ~KAAlarm(); /** Assignment operator. */ KAAlarm &operator=(const KAAlarm &other); /** Return the action type for the alarm. */ Action action() const; /** Return whether the alarm is valid, i.e. whether it contains any alarm data. */ bool isValid() const; /** Return the alarm's type (main, reminder, etc.). */ Type type() const; /** Return the trigger time for the alarm. * Sub-repetitions can optionally be ignored; in this case, if a sub-repetition * is due next, the last main recurrence will be returned instead. * @param withRepeats if true, returns the next sub-repetition time where appropriate; * if false, ignores sub-repetitions. */ DateTime dateTime(bool withRepeats = false) const; /** Return the trigger date for the alarm. * Sub-repetitions are ignored: if a sub-repetition is due next, the * last main recurrence will be returned instead. */ QDate date() const; /** Return the trigger time-of-day for the alarm. * Sub-repetitions are ignored: if a sub-repetition is due next, the * last main recurrence will be returned instead. * @return trigger time-of-day. If the alarm is date-only, this will be * the user-defined start-of-day time. */ QTime time() const; /** Set the alarm's trigger time. */ void setTime(const DateTime &dt); /** Set the alarm's trigger time. */ void setTime(const KADateTime &dt); /** Return whether this is a repeat-at-login alarm. */ bool repeatAtLogin() const; /** Return whether this is a reminder alarm. */ bool isReminder() const; /** Return whether this is a deferred alarm. */ bool deferred() const; /** Return whether in the case of a deferred alarm, it is timed (as * opposed to date-only). * @return @c true if a timed deferral alarm, @c false if date-only or not a deferred alarm. */ bool timedDeferral() const; /** Return an alarm type as a string. * @return alarm type string, or the empty string if debug output is disabled. */ static const char *debugType(Type); private: //@cond PRIVATE class Private; Private *const d; //@endcond friend class KAEvent; friend class KAEventPrivate; }; class KAEventPrivate; /** * @short KAEvent represents a KAlarm event * * KAEvent represents a KAlarm event. An event contains a main alarm together * with optional subsidiary alarms such as reminders and deferrals. Individual * alarms are represented by the KAAlarm class. KAEvent includes the complete * definition of the event including recurrence information, and also holds * current status information such as the next due occurrence and command * execution error status. It provides methods to set and get the event * properties, to defer the alarm, to convert it for storage in the displaying * calendar. * * Methods which act globally or on multiple KAEvent instances include * convertKCalEvents() which converts events stored in an older KAlarm * calendar format to the current format; setStartOfDay() and adjustStartOfDay() * which set a new start-of-day time for date-only alarms; setHolidays() * and setWorkTime() which set holiday region and working days/hours. * * @author David Jarvie */ class KALARMCAL_EXPORT KAEvent { public: /** A list of pointers to KAEvent objects. */ typedef QVector List; /** Flags for use in D-Bus calls, etc. Flags may be combined by OR'ing them together. */ enum Flag { BEEP = 0x02, //!< sound an audible beep when the alarm is displayed REPEAT_AT_LOGIN = 0x04, //!< repeat the alarm at every login ANY_TIME = 0x08, //!< only a date is specified for the alarm, not a time CONFIRM_ACK = 0x10, //!< closing the alarm message window requires a confirmation prompt EMAIL_BCC = 0x20, //!< blind copy the email to the user DEFAULT_FONT = 0x40, //!< use the default alarm message font. Overrides any specified font. REPEAT_SOUND = 0x80, //!< repeat the sound file while the alarm is displayed DISABLED = 0x100, //!< the alarm is currently disabled AUTO_CLOSE = 0x200, //!< auto-close the alarm window after the late-cancel period SCRIPT = 0x400, //!< the command is a script, not a shell command line EXEC_IN_XTERM = 0x800, //!< execute the command in a terminal window SPEAK = 0x1000, //!< speak the message when the alarm is displayed COPY_KORGANIZER = 0x2000, //!< KOrganizer should hold a copy of the event EXCL_HOLIDAYS = 0x4000, //!< don't trigger the alarm on holidays. Only valid //!< if a holiday region has been set by setHolidays(). WORK_TIME_ONLY = 0x8000, //!< trigger the alarm only during working hours DISPLAY_COMMAND = 0x10000, //!< display command output in the alarm window REMINDER_ONCE = 0x20000 //!< only trigger the reminder on the first recurrence // IMPORTANT: if any values are added to this list, ensure that the // additional enum values in KAEventPrivate are also adjusted. }; Q_DECLARE_FLAGS(Flags, Flag) /** The basic action type(s) for the event's main alarm. * Values may be combined by OR'ing them together. */ enum Actions { ACT_NONE = 0, //!< invalid ACT_DISPLAY = 0x01, //!< the alarm displays something ACT_COMMAND = 0x02, //!< the alarm executes a command ACT_EMAIL = 0x04, //!< the alarm sends an email ACT_AUDIO = 0x08, //!< the alarm plays an audio file (without any display) ACT_DISPLAY_COMMAND = ACT_DISPLAY | ACT_COMMAND, //!< the alarm displays command output ACT_ALL = ACT_DISPLAY | ACT_COMMAND | ACT_EMAIL | ACT_AUDIO //!< all types mask }; /** The sub-action type for the event's main alarm. */ enum SubAction { MESSAGE = KAAlarm::MESSAGE, //!< display a message text FILE = KAAlarm::FILE, //!< display the contents of a file COMMAND = KAAlarm::COMMAND, //!< execute a command EMAIL = KAAlarm::EMAIL, //!< send an email AUDIO = KAAlarm::AUDIO //!< play an audio file }; /** What type of occurrence is due. */ enum OccurType { NO_OCCURRENCE = 0, //!< no occurrence is due FIRST_OR_ONLY_OCCURRENCE = 0x01, //!< the first occurrence (takes precedence over LAST_RECURRENCE) RECURRENCE_DATE = 0x02, //!< a recurrence with only a date, not a time RECURRENCE_DATE_TIME = 0x03, //!< a recurrence with a date and time LAST_RECURRENCE = 0x04, //!< the last recurrence OCCURRENCE_REPEAT = 0x10, //!< (bitmask for a sub-repetition of an occurrence) FIRST_OR_ONLY_OCCURRENCE_REPEAT = OCCURRENCE_REPEAT | FIRST_OR_ONLY_OCCURRENCE, //!< a sub-repetition of the first occurrence RECURRENCE_DATE_REPEAT = OCCURRENCE_REPEAT | RECURRENCE_DATE, //!< a sub-repetition of a date-only recurrence RECURRENCE_DATE_TIME_REPEAT = OCCURRENCE_REPEAT | RECURRENCE_DATE_TIME, //!< a sub-repetition of a date/time recurrence LAST_RECURRENCE_REPEAT = OCCURRENCE_REPEAT | LAST_RECURRENCE //!< a sub-repetition of the last recurrence }; /** How to treat sub-repetitions in nextOccurrence(). */ enum OccurOption { IGNORE_REPETITION, //!< check for recurrences only, ignore sub-repetitions RETURN_REPETITION, //!< return a sub-repetition if it's the next occurrence ALLOW_FOR_REPETITION //!< if a sub-repetition is the next occurrence, return the previous recurrence, not the sub-repetition }; /** What type of occurrence currently limits how long the alarm can be deferred. */ enum DeferLimitType { LIMIT_NONE, //!< there is no limit LIMIT_MAIN, //!< the main alarm LIMIT_RECURRENCE, //!< a recurrence LIMIT_REPETITION, //!< a sub-repetition LIMIT_REMINDER //!< a reminder }; /** Alarm trigger type. */ enum TriggerType { ALL_TRIGGER, //!< next trigger, including reminders, ignoring working hours & holidays MAIN_TRIGGER, //!< next trigger, excluding reminders, ignoring working hours & holidays WORK_TRIGGER, //!< next main working time trigger, excluding reminders ALL_WORK_TRIGGER, //!< next actual working time trigger, including reminders DISPLAY_TRIGGER //!< next trigger time for display purposes (i.e. excluding reminders) }; /** Command execution error type for last time the alarm was triggered. */ enum CmdErrType { CMD_NO_ERROR = 0, //!< no error CMD_ERROR = 0x01, //!< command alarm execution failed CMD_ERROR_PRE = 0x02, //!< pre-alarm command execution failed CMD_ERROR_POST = 0x04, //!< post-alarm command execution failed CMD_ERROR_PRE_POST = CMD_ERROR_PRE | CMD_ERROR_POST }; /** Options for pre- or post-alarm actions. These may be OR'ed together. * @since 4.9 */ enum ExtraActionOption { CancelOnPreActError = 0x01, //!< cancel alarm on pre-alarm action error DontShowPreActError = 0x02, //!< do not notify pre-alarm action errors to user ExecPreActOnDeferral = 0x04 //!< execute pre-alarm action also for deferred alarms }; Q_DECLARE_FLAGS(ExtraActionOptions, ExtraActionOption) /** How to deal with the event UID in updateKCalEvent(). */ enum UidAction { UID_IGNORE, //!< leave KCal::Event UID unchanged UID_CHECK, //!< verify that the KCal::Event UID is already the same as the KAEvent ID, if the latter is non-empty UID_SET //!< set the KCal::Event UID to the KAEvent ID }; /** Default constructor which creates an invalid event. */ KAEvent(); /** Construct an event and initialise with the specified parameters. * @param dt start date/time. If @p dt is date-only, or if #ANY_TIME flag * is specified, the event will be date-only. * @param text alarm message (@p action = #MESSAGE); * file to display (@p action = #FILE); * command to execute (@p action = #COMMAND); * email body (@p action = #EMAIL); * audio file (@p action = #AUDIO). * @param bg background color (for display alarms, ignored otherwise). * @param fg foreground color (for display alarms, ignored otherwise). * @param font font (for display alarms, ignored otherwise). Ignored if * #DEFAULT_FONT flag is specified. * @param action alarm action type. * @param lateCancel late-cancellation period (minutes), else 0. * @param flags OR of #Flag enum values. * @param changesPending true to inhibit automatic calculations and data * updates until further changes have been applied * to the instance; call endChanges() when changes * are complete. */ KAEvent(const KADateTime &dt, const QString &text, const QColor &bg, const QColor &fg, const QFont &font, SubAction action, int lateCancel, Flags flags, bool changesPending = false); /** Construct an event and initialise it from a KCalCore::Event. * * The initialisation is identical to that performed by set(). */ explicit KAEvent(const KCalCore::Event::Ptr &); /** Initialise the instance from a KCalCore::Event. * * It uses the following properties from KCalCore::Event: * - Unique ID. * - Summary. * - Creation date/time. * - Start date/time. * - Recurrence rule. * - Alarms. * - Read only. * - Custom properties. X-KDE-KALARM- properties are interpreted and used to * set numerous KAEvent properties; non-KAlarm properties are simply stored. * - Custom status if equal to "DISABLED". * - Revision number. */ void set(const KCalCore::Event::Ptr &); KAEvent(const KAEvent &other); ~KAEvent(); KAEvent &operator=(const KAEvent &other); /** Initialise the instance with the specified parameters. * @param dt start date/time * @param text alarm message (@p action = #MESSAGE); * file to display (@p action = #FILE); * command to execute (@p action = #COMMAND); * email body (@p action = #EMAIL); * audio file (@p action = #AUDIO) * @param bg background color (for display alarms, ignored otherwise) * @param fg background color (for display alarms, ignored otherwise) * @param font font (for display alarms, ignored otherwise) * @param action alarm action type * @param lateCancel late-cancellation period (minutes), else 0 * @param flags OR of #Flag enum values * @param changesPending true to inhibit automatic data updates until * further changes have been applied to the instance; * call endChanges() when changes are complete. */ void set(const KADateTime &dt, const QString &text, const QColor &bg, const QColor &fg, const QFont &font, SubAction action, int lateCancel, Flags flags, bool changesPending = false); /** Update an existing KCalCore::Event with the KAEvent data. * @param event Event to update. * @param u how to deal with the Event's UID. * @param setCustomProperties if true, all the Event's existing custom * properties are cleared and replaced with the * KAEvent's custom properties. If false, the * KCal::Event's non-KAlarm custom properties * are left untouched. */ bool updateKCalEvent(const KCalCore::Event::Ptr &event, UidAction u, bool setCustomProperties = true) const; /** Return whether the instance represents a valid event. */ bool isValid() const; /** Enable or disable the alarm. */ void setEnabled(bool enable); /** Return the enabled status of the alarm. */ bool enabled() const; /** Set the read-only status of the alarm. */ void setReadOnly(bool ro); /** Return the read-only status of the alarm. */ bool isReadOnly() const; /** Set the event to be archived when it expires or is deleted. * Normally this is set when the event has triggered at least once. */ void setArchive(); /** Return whether the event should be archived when it expires or is deleted. */ bool toBeArchived() const; /** Return whether the event's main alarm has expired. If so, a deferral alarm will exist. */ bool mainExpired() const; /** Return whether the event has expired. * @return @c true if the event has expired and is currently being displayed, * or it is an archived event. */ bool expired() const; /** Return the OR of various Flag enum status values. */ Flags flags() const; /** Set the alarm category (active/archived/template, or displaying). */ void setCategory(CalEvent::Type type); /** Return the alarm category (active/archived/template, or displaying). */ CalEvent::Type category() const; /** Set the event's unique identifier. Note that the UID is guaranteed to be unique * only within the calendar containing the event. */ void setEventId(const QString &id); /** Return the event's unique identifier. Note that the UID is guaranteed to be unique * only within the calendar containing the event. */ QString id() const; /** Increment the revision number of the event (SEQUENCE property in iCalendar). */ void incrementRevision(); /** Return the revision number of the event (SEQUENCE property in iCalendar). */ int revision() const; /** Set the ID of the Akonadi Collection which contains the event. */ void setCollectionId(Akonadi::Collection::Id id); /** Set the ID of the Akonadi Collection which contains the event. * @warning This is a const method, which means that any other instance * which references the same shared data will also be * updated. It is designed to be used when a KAEvent is * being created from an Akonadi Item, to avoid unnecessary * copying. Use with caution! */ void setCollectionId_const(Akonadi::Collection::Id id) const; /** Return the ID of the Akonadi Collection which contains the event. */ Akonadi::Collection::Id collectionId() const; /** Set the ID of the Akonadi Item which contains the event. */ void setItemId(Akonadi::Item::Id id); /** Return the ID of the Akonadi Item which contains the event. */ Akonadi::Item::Id itemId() const; /** Initialise an Akonadi::Item with the event's data. * Note that the event is not updated with the Item ID. * @return @c true if successful; @c false if the event's category does not match the * collection's mime types. */ bool setItemPayload(Akonadi::Item &, const QStringList &collectionMimeTypes) const; /** Note the event's storage format compatibility compared to the current KAlarm calendar format. */ void setCompatibility(KACalendar::Compat c); /** Return the event's storage format compatibility compared to the current KAlarm calendar format. */ KACalendar::Compat compatibility() const; /** Return the original KCalCore::Event's custom properties in the source calendar. */ QMap customProperties() const; /** Return the action sub-type of the event's main alarm. For display alarms, * this is MESSAGE or FILE, while other types of alarm simply return the * basic action type (COMMAND, EMAIL, AUDIO). * Note that for a display alarm whose text is generated by a command, the * returned type is COMMAND. */ SubAction actionSubType() const; /** Return the OR of the basic action types of the event's main alarm (display, * command, email, audio). * Note that for a display alarm whose text is generated by a command, the * returned type is @c ACT_DISPLAY|ACT_COMMAND. */ Actions actionTypes() const; /** Set or clear the late-cancel option. This determines whether the alarm * will be cancelled if it is late in triggering. * @param minutes late cancellation period in minutes, or 0 to clear * @see lateCancel() */ void setLateCancel(int minutes); /** Get the late cancellation period. This is how late the alarm can * trigger after its scheduled time, before it will be cancelled. * @return period in minutes, or 0 if no late cancellation is specified * @see setLateCancel() */ int lateCancel() const; /** Enable or disable auto-close for a display alarm, i.e. whether the * alarm window will be closed on expiry of the late-cancellation * time. Note that auto-close will only take effect if the late-cancel * option is also set. * @see setLateCancel(), autoClose() */ void setAutoClose(bool autoclose); /** Return whether auto-close is enabled, i.e. whether the alarm window * will be closed on expiry of the late-cancellation time. Note that * auto-close will only operate if in addition to being enabled, * late-cancel is also set. * @return @c true if it is a display alarm and auto-close is enabled. * @see lateCancel(), setAutoClose() */ bool autoClose() const; - void setKMailSerialNumber(unsigned long n); - unsigned long kmailSerialNumber() const; + /** Set the Akonadi item ID of the email which the alarm is related to. + */ + void setAkonadiItemId(Akonadi::Item::Id id); + + /** Return the Akonadi item ID of the email which the alarm is related to. + */ + Akonadi::Item::Id akonadiItemId() const; /** Return the alarm's text. Its significance depends on the type of alarm; * alternatively, use message(), displayMessage(), fileName() or command(), * which incorporate checks on alarm type. */ QString cleanText() const; /** Return the message text for a display alarm, or the email body for * an email alarm. * @return message/email text, or empty if not a display or email alarm. */ QString message() const; /** Return the message text for a display alarm. * @return message text, or empty if not a text display alarm. */ QString displayMessage() const; /** Return the path of the file whose contents are to be shown, for a display alarm. * @return file path, or empty if not a file display alarm. */ QString fileName() const; /** Return the message window background color, for a display alarm. */ QColor bgColour() const; /** Return the message window foreground color, for a display alarm. */ QColor fgColour() const; /** Set the global default font for alarm message texts. */ static void setDefaultFont(const QFont &font); /** Return whether to use the default font (as set by setDefaultFont()) * for alarm message texts. */ bool useDefaultFont() const; /** Return the font to use for alarm message texts. */ QFont font() const; /** Return the command or script to execute, for a command alarm. * @return command, or empty if not a command alarm. */ QString command() const; /** Return whether a command script is specified, for a command alarm. */ bool commandScript() const; /** Return whether to execute the command in a terminal window, for a command alarm. */ bool commandXterm() const; /** Return whether the command output is to be displayed in an alarm message window. */ bool commandDisplay() const; /** Set or clear the command execution error for the last time the alarm triggered. */ void setCommandError(CmdErrType error) const; /** Return the command execution error for the last time the alarm triggered. */ CmdErrType commandError() const; /** Set the log file to write command alarm output to. * @param logfile log file path */ void setLogFile(const QString &logfile); /** Return the log file which command alarm output should be written to. * @return log file path, or empty if no log file. */ QString logFile() const; /** Return whether alarm acknowledgement must be confirmed by the user, for a display alarm. */ bool confirmAck() const; /** Return whether KOrganizer should hold a copy of the event. */ bool copyToKOrganizer() const; /** Set the email related data for the event. */ void setEmail(uint from, const KCalCore::Person::List &, const QString &subject, const QStringList &attachments); /** Return the email message body, for an email alarm. * @return email body, or empty if not an email alarm */ QString emailMessage() const; /** Return the email identity to be used as the sender, for an email alarm. * @return email UOID */ uint emailFromId() const; /** Return the list of email addressees, including names, for an email alarm. */ KCalCore::Person::List emailAddressees() const; /** Return a list of the email addresses, including names, for an email alarm. */ QStringList emailAddresses() const; /** Return a string containing the email addressees, including names, for an email alarm. * @param sep separator string to insert between addresses. */ QString emailAddresses(const QString &sep) const; /** Concatenate a list of email addresses into a string. * @param addresses list of email addresses * @param sep separator string to insert between addresses */ static QString joinEmailAddresses(const KCalCore::Person::List &addresses, const QString &sep); /** Return the list of email addressees, excluding names, for an email alarm. */ QStringList emailPureAddresses() const; /** Return a string containing the email addressees, excluding names, for an email alarm. * @param sep separator string to insert between addresses. */ QString emailPureAddresses(const QString &sep) const; /** Return the email subject line, for an email alarm. */ QString emailSubject() const; /** Return the list of file paths of the attachments, for an email alarm. */ QStringList emailAttachments() const; /** Return the file paths of the attachments, as a string, for an email alarm. * @param sep string separator */ QString emailAttachments(const QString &sep) const; /** Return whether to send a blind copy of the email to the sender, for an email alarm. */ bool emailBcc() const; /** Set the audio file related data for the event. * @param filename audio file path * @param volume final volume (0 - 1), or -1 for default volume * @param fadeVolume initial volume (0 - 1), or -1 for no fade * @param fadeSeconds number of seconds to fade from @p fadeVolume to @p volume * @param repeatPause number of seconds to pause between repetitions, or -1 if no repeat * @param allowEmptyFile true to set the volume levels even if @p filename is empty * @see audioFile(), soundVolume(), fadeVolume(), fadeSeconds() */ void setAudioFile(const QString &filename, float volume, float fadeVolume, int fadeSeconds, int repeatPause = -1, bool allowEmptyFile = false); /** Return the audio file path. * @see setAudioFile() */ QString audioFile() const; /** Return the sound volume (the final volume if fade is specified). * @return volume in range 0 - 1, or -1 for default volume. * @see setAudioFile() */ float soundVolume() const; /** Return the initial volume which will fade to the final volume. * @return volume in range 0 - 1, or -1 if no fade specified. * @see setAudioFile() */ float fadeVolume() const; /** Return the fade period in seconds, or 0 if no fade is specified. * @see setAudioFile() */ int fadeSeconds() const; /** Return whether the sound file will be repeated indefinitely. */ bool repeatSound() const; /** Return how many seconds to pause between repetitions of the sound file. * @return pause interval, or -1 if sound does not repeat. */ int repeatSoundPause() const; /** Return whether a beep should sound when the alarm is displayed. */ bool beep() const; /** Return whether the displayed alarm text should be spoken. */ bool speak() const; /** Set the event to be an alarm template. * @param name template's name * @param afterTime number of minutes after default time to schedule alarm for, or * -1 to not use 'time from now' * @see isTemplate(), templateName() */ void setTemplate(const QString &name, int afterTime = -1); /** Return whether the event is an alarm template. * @see setTemplate() */ bool isTemplate() const; /** Return the alarm template's name. * @return template name, or empty if not a template * @see setTemplate() */ QString templateName() const; /** Return whether the alarm template does not specify a time. * @return @c true if no time is specified, i.e. the normal default alarm time will * be used, @c false if the template specifies a time. */ bool usingDefaultTime() const; /** Return the number of minutes (>= 0) after the default alarm time which is * specified in the alarm template. If this is specified, an alarm based on * this template wll have the "Time from now" radio button enabled in the alarm * edit dialog. * @return minutes after the default time, or -1 if the template specifies a time * of day or a date-only alarm. */ int templateAfterTime() const; /** Set the pre-alarm and post-alarm actions, and their options. * @param pre shell command to execute before the alarm is displayed * @param post shell command to execute after the alarm is acknowledged * @param preOptions options for pre-alarm actions * @see preAction(), postAction(), extraActionOptions() * @since 4.9 */ void setActions(const QString &pre, const QString &post, ExtraActionOptions preOptions); /** Return the shell command to execute before the alarm is displayed. */ QString preAction() const; /** Return the shell command to execute after the display alarm is acknowledged. * @see setActions() */ QString postAction() const; /** Return the pre-alarm action options. * @see preAction(), setActions() * @since 4.9 */ ExtraActionOptions extraActionOptions() const; /** Set an additional reminder alarm. * @param minutes number of minutes BEFORE the main alarm; if negative, the reminder * will occur AFTER the main alarm. * 0 = clear the reminder. * @param onceOnly true to trigger a reminder only for the first recurrence. * @see reminderMinutes(), reminderOnceOnly() */ void setReminder(int minutes, bool onceOnly); /** If there is a reminder which occurs AFTER the main alarm, * activate the event's reminder which occurs after the given main alarm time. * If there is no reminder after the main alarm, this method does nothing. * @return @c true if successful (i.e. reminder falls before the next main alarm). */ void activateReminderAfter(const DateTime &mainAlarmTime); /** Return the number of minutes BEFORE the main alarm when a reminder alarm is set. * @return >0 if the reminder is before the main alarm; * <0 if the reminder is after the main alarm; * 0 if no reminder is configured. * @see setReminder() */ int reminderMinutes() const; /** Return whether a reminder is currently due (before the next, or after the last, * main alarm/recurrence). * @see reminderDeferral() */ bool reminderActive() const; /** Return whether the reminder alarm is triggered only for the first recurrence. * @see setReminder() */ bool reminderOnceOnly() const; /** Return whether there is currently a deferred reminder alarm pending. */ bool reminderDeferral() const; /** Defer the event to the specified time. * If the main alarm time has passed, the main alarm is marked as expired. * @param dt date/time to defer the event to * @param reminder true if deferring a reminder alarm * @param adjustRecurrence if true, ensure that the next scheduled recurrence is * after the current time. * * @see cancelDefer(), deferred(), deferDateTime() */ void defer(const DateTime &dt, bool reminder, bool adjustRecurrence = false); /** Cancel any deferral alarm which is pending. * @see defer() */ void cancelDefer(); /** Set defaults for the deferral dialog. * @param minutes default number of minutes, or 0 to select time control. * @param dateOnly true to select date-only by default. * @see deferDefaultMinutes() */ void setDeferDefaultMinutes(int minutes, bool dateOnly = false); /** Return whether there is currently a deferred alarm pending. * @see defer(), deferDateTime() */ bool deferred() const; /** Return the time at which the currently pending deferred alarm should trigger. * @return trigger time, or invalid if no deferral pending. * @see defer(), deferred() */ DateTime deferDateTime() const; /** Return the latest time which the alarm can currently be deferred to. * @param limitType if non-null, pointer to variable which will be updated to hold * the type of occurrence which currently limits the deferral. * @return deferral limit, or invalid if no limit */ DateTime deferralLimit(DeferLimitType *limitType = nullptr) const; /** Return the default deferral interval used in the deferral dialog. * @see setDeferDefaultMinutes() */ int deferDefaultMinutes() const; /** Return the default date-only setting used in the deferral dialog. */ bool deferDefaultDateOnly() const; /** Return the start time for the event. If the event recurs, this is the * time of the first recurrence. If the event is date-only, this returns a * date-only value. * @see mainDateTime() */ DateTime startDateTime() const; /** Set the next time to trigger the alarm (excluding sub-repetitions). * Note that for a recurring event, this should match one of the * recurrence times. */ void setTime(const KADateTime &dt); /** Return the next time the main alarm will trigger. * @param withRepeats true to include sub-repetitions, false to exclude them. * @see mainTime(), startDateTime(), setTime() */ DateTime mainDateTime(bool withRepeats = false) const; /** Return the time at which the main alarm will next trigger. * Sub-repetitions are ignored. */ QTime mainTime() const; /** Return the time at which the last sub-repetition of the main * alarm will occur. * @return last sub-repetition time, or main alarm time if no * sub-repetitions are configured. */ DateTime mainEndRepeatTime() const; /** Set the start-of-day time used by all date-only alarms. * Note that adjustStartOfDay() should be called immediately after this, * to adjust all events' internal data. */ static void setStartOfDay(const QTime &); /** Call when the user changes the start-of-day time, to adjust the data * for each date-only event in a list. * @param events list of events. Any date-time events in the list are ignored. * @see setStartOfDay() */ static void adjustStartOfDay(const KAEvent::List &events); /** Return the next time the alarm will trigger. * @param type specifies whether to ignore reminders, working time * restrictions, etc. */ DateTime nextTrigger(TriggerType type) const; /** Set the date/time the event was created, or saved in the archive calendar. * @see createdDateTime() */ void setCreatedDateTime(const KADateTime &dt); /** Return the date/time the event was created, or saved in the archive calendar. * @see setCreatedDateTime() */ KADateTime createdDateTime() const; /** Enable or disable repeat-at-login. * If @p repeat is true, any existing pre-alarm reminder, late-cancel and * copy-to-KOrganizer will all be disabled. * @see repeatAtLogin() */ void setRepeatAtLogin(bool repeat); /** Return whether the alarm repeats at login. * @param includeArchived true to also test for archived repeat-at-login status, * false to test only for a current repeat-at-login alarm. * @see setRepeatAtLogin() */ bool repeatAtLogin(bool includeArchived = false) const; /** Enable or disable the alarm on holiday dates. The currently selected * holiday region determines which dates are holidays. * Note that this option only has any effect for recurring alarms. * @param exclude true to disable on holidays, false to enable * @see holidaysExcluded(), setHolidays() */ void setExcludeHolidays(bool exclude); /** Return whether the alarm is disabled on holiday dates. * If no holiday region has been set by setHolidays(), this returns false; * @see setExcludeHolidays() */ bool holidaysExcluded() const; /** Set the holiday region to be used by all KAEvent instances. * Alarms which exclude holidays record the pointer to the holiday definition * at the time their next trigger times were last calculated. The change in * holiday definition pointer will cause their next trigger times to be * recalculated. * @param region the holiday region data. The data object must persist for * the lifetime of the application, since this class just * stores a pointer to @p region. * @see setExcludeHolidays() */ static void setHolidays(const KHolidays::HolidayRegion ®ion); /** Enable or disable the alarm on non-working days and outside working hours. * Note that this option only has any effect for recurring alarms. * @param wto true to restrict to working time, false to enable any time * @see workTimeOnly(), setWorkTime() */ void setWorkTimeOnly(bool wto); /** Return whether the alarm is disabled on non-working days and outside working hours. * @see setWorkTimeOnly() */ bool workTimeOnly() const; /** Check whether a date/time is during working hours and/or holidays, depending * on the flags set for the specified event. */ bool isWorkingTime(const KADateTime &dt) const; /** Set working days and times, to be used by all KAEvent instances. * @param days bits set to 1 for each working day. Array element 0 = Monday ... 6 = Sunday. * @param start start time in working day. * @param end end time in working day. * @see setWorkTimeOnly(), isWorkingTime() */ static void setWorkTime(const QBitArray &days, const QTime &start, const QTime &end); /** Clear the event's recurrence and sub-repetition data. * @see setRecurrence(), recurs() */ void setNoRecur(); /** Initialise the event's recurrence from a KARecurrence. * The event's start date/time is not changed. * @see setRecurMinutely(), setRecurDaily(), setRecurWeekly(), setRecurMonthlyByDate(), setRecurMonthlyByPos(), setRecurAnnualByDate(), setRecurAnnualByPos(), setFirstRecurrence() */ void setRecurrence(const KARecurrence &r); /** Set the recurrence to recur at a minutes interval. * @param freq how many minutes between recurrences. * @param count number of occurrences, including first and last; * = -1 to recur indefinitely; * = 0 to use @p end instead. * @param end = end date/time (set invalid to use @p count instead). * @return @c false if no recurrence was set up. */ bool setRecurMinutely(int freq, int count, const KADateTime &end); /** Set the recurrence to recur daily. * @param freq how many days between recurrences. * @param days which days of the week alarms are allowed to occur on. * @param count number of occurrences, including first and last; * = -1 to recur indefinitely; * = 0 to use @p end instead. * @param end = end date (set invalid to use @p count instead). * @return @c false if no recurrence was set up. */ bool setRecurDaily(int freq, const QBitArray &days, int count, const QDate &end); /** Set the recurrence to recur weekly, on the specified weekdays. * @param freq how many weeks between recurrences. * @param days which days of the week alarms are allowed to occur on. * @param count number of occurrences, including first and last; * = -1 to recur indefinitely; * = 0 to use @p end instead. * @param end = end date (set invalid to use @p count instead). * @return @c false if no recurrence was set up. */ bool setRecurWeekly(int freq, const QBitArray &days, int count, const QDate &end); /** Set the recurrence to recur monthly, on the specified days within the month. * @param freq how many months between recurrences. * @param days which days of the month alarms should occur on. * @param count number of occurrences, including first and last; * = -1 to recur indefinitely; * = 0 to use @p end instead. * @param end = end date (set invalid to use @p count instead). * @return @c false if no recurrence was set up. */ bool setRecurMonthlyByDate(int freq, const QVector &days, int count, const QDate &end); /** Holds days of the week combined with a week number in the month, * used to specify some monthly or annual recurrences. */ struct MonthPos { MonthPos() : days(7) {} //krazy:exclude=inline (need default constructor) int weeknum; //!< Week in month, or < 0 to count from end of month. QBitArray days; //!< Days in week, element 0 = Monday. }; /** Set the recurrence to recur monthly, on the specified weekdays in the * specified weeks of the month. * @param freq how many months between recurrences. * @param pos which days of the week/weeks of the month alarms should occur on. * @param count number of occurrences, including first and last; * = -1 to recur indefinitely; * = 0 to use @p end instead. * @param end = end date (set invalid to use @p count instead). * @return @c false if no recurrence was set up. */ bool setRecurMonthlyByPos(int freq, const QVector &pos, int count, const QDate &end); /** Set the recurrence to recur annually, on the specified day in each * of the specified months. * @param freq how many years between recurrences. * @param months which months of the year alarms should occur on. * @param day day of month, or 0 to use event start date. * @param feb29 for a February 29th recurrence, when February 29th should * recur in non-leap years. * @param count number of occurrences, including first and last; * = -1 to recur indefinitely; * = 0 to use @p end instead. * @param end = end date (set invalid to use @p count instead). * @return @c false if no recurrence was set up. */ bool setRecurAnnualByDate(int freq, const QVector &months, int day, KARecurrence::Feb29Type, int count, const QDate &end); /** Set the recurrence to recur annually, on the specified weekdays in the * specified weeks of the specified months. * @param freq how many years between recurrences. * @param pos which days of the week/weeks of the month alarms should occur on. * @param months which months of the year alarms should occur on. * @param count number of occurrences, including first and last; * = -1 to recur indefinitely; * = 0 to use @p end instead. * @param end = end date (set invalid to use @p count instead). * @return @c false if no recurrence was set up. */ bool setRecurAnnualByPos(int freq, const QVector &pos, const QVector &months, int count, const QDate &end); /** Return whether the event recurs. * @see recurType() */ bool recurs() const; /** Return the recurrence period type for the event. * Note that this does not test for repeat-at-login. * @see recurInterval() */ KARecurrence::Type recurType() const; /** Return the full recurrence data for the event. * @return recurrence data, or null if none. * @see recurrenceText() */ KARecurrence *recurrence() const; /** Return the recurrence interval in units of the recurrence period type * (minutes, days, etc). * @see longestRecurrenceInterval() */ int recurInterval() const; /** Return the longest interval which can occur between consecutive recurrences. * @see recurInterval() */ KCalCore::Duration longestRecurrenceInterval() const; /** Adjust the event date/time to the first recurrence of the event, on or after * the event start date/time. The event start date may not be a recurrence date, * in which case a later date will be set. */ void setFirstRecurrence(); /** Return the recurrence interval as text suitable for display. */ QString recurrenceText(bool brief = false) const; /** Initialise the event's sub-repetition. * The repetition length is adjusted if necessary to fit the recurrence interval. * If the event doesn't recur, the sub-repetition is cleared. * @return @c false if a non-daily interval was specified for a date-only recurrence. * @see repetition() */ bool setRepetition(const Repetition &r); /** Return the event's sub-repetition data. * @see setRepetition(), repetitionText() */ Repetition repetition() const; /** Return the count of the next sub-repetition which is due. * @return sub-repetition count (>=1), or 0 for the main recurrence. * @see nextOccurrence() */ int nextRepetition() const; /** Return the repetition interval as text suitable for display. */ QString repetitionText(bool brief = false) const; /** Determine whether the event will occur after the specified date/time. * @param preDateTime the specified date/time. * @param includeRepetitions if true and the alarm has a sub-repetition, the * method will return true if any sub-repetitions * occur after @p preDateTime. * @see nextOccurrence() */ bool occursAfter(const KADateTime &preDateTime, bool includeRepetitions) const; /** Set the date/time of the event to the next scheduled occurrence after a * specified date/time, provided that this is later than its current date/time. * Any reminder alarm is adjusted accordingly. * If the alarm has a sub-repetition, and a sub-repetition of a previous * recurrence occurs after the specified date/time, that sub-repetition is * set as the next occurrence. * @see nextOccurrence() */ OccurType setNextOccurrence(const KADateTime &preDateTime); /** Get the date/time of the next occurrence of the event, after the specified * date/time. * @param preDateTime the specified date/time. * @param result date/time of next occurrence, or invalid date/time if none. * @param option how/whether to make allowance for sub-repetitions. * @see nextRepetition(), setNextOccurrence(), previousOccurrence(), occursAfter() */ OccurType nextOccurrence(const KADateTime &preDateTime, DateTime &result, OccurOption option = IGNORE_REPETITION) const; /** Get the date/time of the last previous occurrence of the event, before the * specified date/time. * @param afterDateTime the specified date/time. * @param result date/time of previous occurrence, or invalid * date/time if none. * @param includeRepetitions if true and the alarm has a sub-repetition, the * last previous repetition is returned if * appropriate. * @see nextOccurrence() */ OccurType previousOccurrence(const KADateTime &afterDateTime, DateTime &result, bool includeRepetitions = false) const; /** Set the event to be a copy of the specified event, making the specified * alarm the 'displaying' alarm. * The purpose of setting up a 'displaying' alarm is to be able to reinstate * the alarm message in case of a crash, or to reinstate it should the user * choose to defer the alarm. Note that even repeat-at-login alarms need to be * saved in case their end time expires before the next login. * @param event the event to copy * @param type the alarm type (main, reminder, deferred etc.) * @param colId the ID of the collection which originally contained the event * @param repeatAtLoginTime repeat-at-login time if @p type == AT_LOGIN_ALARM, else ignored * @param showEdit whether the Edit button was displayed * @param showDefer whether the Defer button was displayed * @return @c true if successful, @c false if alarm was not copied. */ bool setDisplaying(const KAEvent &event, KAAlarm::Type type, Akonadi::Collection::Id colId, const KADateTime &repeatAtLoginTime, bool showEdit, bool showDefer); /** Reinstate the original event from the 'displaying' event. * This instance is initialised from the supplied displaying @p event, * and appropriate adjustments are made to convert it back to the * original pre-displaying state. * @param event the displaying event * @param colId updated to the ID of the collection which originally contained the event * @param showEdit updated to true if Edit button was displayed, else false * @param showDefer updated to true if Defer button was displayed, else false */ void reinstateFromDisplaying(const KCalCore::Event::Ptr &event, Akonadi::Collection::Id &colId, bool &showEdit, bool &showDefer); /** Return the original alarm which the displaying alarm refers to. * Note that the caller is responsible for ensuring that the event was * a displaying event; this check is not made in convertDisplayingAlarm() * since it is normally called after reinstateFromDisplaying(), which * resets the instance so that displaying() returns false. */ KAAlarm convertDisplayingAlarm() const; /** Return whether the alarm is currently being displayed, i.e. is in the displaying calendar. */ bool displaying() const; /** Return the alarm of a specified type. * @param type alarm type to return. * @see nextAlarm(), alarmCount() */ KAAlarm alarm(KAAlarm::Type type) const; /** Return the main alarm for the event. * If the main alarm does not exist, one of the subsidiary ones is returned if * possible. * N.B. a repeat-at-login alarm can only be returned if it has been read from/ * written to the calendar file. * @see nextAlarm() */ KAAlarm firstAlarm() const; /** Return the next alarm for the event, after the specified alarm. * @see firstAlarm() */ KAAlarm nextAlarm(const KAAlarm &previousAlarm) const; /** Return the next alarm for the event, after the specified alarm type. * @see firstAlarm() */ KAAlarm nextAlarm(KAAlarm::Type previousType) const; /** Return the number of alarms in the event, i.e. the count of: * - main alarm * - repeat-at-login alarm * - deferral alarm * - reminder alarm * - displaying alarm */ int alarmCount() const; /** Remove the alarm of the specified type from the event. * This must only be called to remove an alarm which has expired, not to * reconfigure the event. */ void removeExpiredAlarm(KAAlarm::Type type); /** Call before making a group of changes to the event, to avoid unnecessary * calculation intensive recalculations of trigger times from being * performed until all the changes have been applied. When the changes * are complete, endChanges() should be called to allow resultant * updates to occur. */ void startChanges(); /** Call when a group of changes preceded by startChanges() is complete, to * allow resultant updates to occur. */ void endChanges(); /** Return the current KAlarm calendar storage format version. * This is the KAlarm version which first used the current calendar/event * format. It is NOT the KAlarmCal library .so version! * @return version in the format returned by KAlarmCal::Version(). * @see currentCalendarVersionString() */ static int currentCalendarVersion(); /** Return the current KAlarm calendar storage format version. * This is the KAlarm version which first used the current calendar/event * format. It is NOT the KAlarmCal library .so version! * @return version as a string in the format "1.2.3". * @see currentCalendarVersion() */ static QByteArray currentCalendarVersionString(); /** If a calendar was written by a previous version of KAlarm, do any * necessary format conversions on the events to ensure that when the calendar * is saved, no information is lost or corrupted. * @param calendar calendar whose events are to be converted. * @param calendarVersion KAlarm calendar format version of @p calendar, in the * format returned by KAlarmCal::Version(). The KDE 3.0.0 * version 0.5.7 requires a special adjustment for * summer time and should be passed negated (-507) to * distinguish it from the KDE 3.0.1 version 0.5.7 * which does not require the adjustment. * @return @c true if any conversions were done. */ static bool convertKCalEvents(const KCalCore::Calendar::Ptr &, int calendarVersion); /** Return a list of pointers to a list of KAEvent objects. */ static List ptrList(QVector &events); /** Output the event's data as debug output. */ void dumpDebug() const; private: QSharedDataPointer d; }; } // namespace KAlarmCal Q_DECLARE_OPERATORS_FOR_FLAGS(KAlarmCal::KAEvent::Flags) Q_DECLARE_METATYPE(KAlarmCal::KAEvent) #endif // KALARM_KAEVENT_H // vim: et sw=4: