kalarm

alarmevent.cpp

00001 /*
00002  *  alarmevent.cpp  -  represents calendar alarms and events
00003  *  Program:  kalarm
00004  *  Copyright © 2001-2008 by David Jarvie <software@astrojar.org.uk>
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License along
00017  *  with this program; if not, write to the Free Software Foundation, Inc.,
00018  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019  */
00020 
00021 #include "kalarm.h"
00022 
00023 #include <stdlib.h>
00024 #include <time.h>
00025 #include <ctype.h>
00026 #include <qcolor.h>
00027 #include <qregexp.h>
00028 
00029 #include <klocale.h>
00030 #include <kdebug.h>
00031 
00032 #include "alarmtext.h"
00033 #include "functions.h"
00034 #include "kalarmapp.h"
00035 #include "kamail.h"
00036 #include "preferences.h"
00037 #include "alarmcalendar.h"
00038 #include "alarmevent.h"
00039 using namespace KCal;
00040 
00041 
00042 const QCString APPNAME("KALARM");
00043 
00044 // KAlarm version which first used the current calendar/event format.
00045 // If this changes, KAEvent::convertKCalEvents() must be changed correspondingly.
00046 // The string version is the KAlarm version string used in the calendar file.
00047 QString KAEvent::calVersionString()  { return QString::fromLatin1("1.5.0"); }
00048 int     KAEvent::calVersion()        { return KAlarm::Version(1,5,0); }
00049 
00050 // Custom calendar properties.
00051 // Note that all custom property names are prefixed with X-KDE-KALARM- in the calendar file.
00052 // - Event properties
00053 static const QCString NEXT_RECUR_PROPERTY("NEXTRECUR");     // X-KDE-KALARM-NEXTRECUR property
00054 static const QCString REPEAT_PROPERTY("REPEAT");            // X-KDE-KALARM-REPEAT property
00055 // - General alarm properties
00056 static const QCString TYPE_PROPERTY("TYPE");    // X-KDE-KALARM-TYPE property
00057 static const QString FILE_TYPE                  = QString::fromLatin1("FILE");
00058 static const QString AT_LOGIN_TYPE              = QString::fromLatin1("LOGIN");
00059 static const QString REMINDER_TYPE              = QString::fromLatin1("REMINDER");
00060 static const QString REMINDER_ONCE_TYPE         = QString::fromLatin1("REMINDER_ONCE");
00061 static const QString ARCHIVE_REMINDER_ONCE_TYPE = QString::fromLatin1("ONCE");
00062 static const QString TIME_DEFERRAL_TYPE         = QString::fromLatin1("DEFERRAL");
00063 static const QString DATE_DEFERRAL_TYPE         = QString::fromLatin1("DATE_DEFERRAL");
00064 static const QString DISPLAYING_TYPE            = QString::fromLatin1("DISPLAYING");   // used only in displaying calendar
00065 static const QString PRE_ACTION_TYPE            = QString::fromLatin1("PRE");
00066 static const QString POST_ACTION_TYPE           = QString::fromLatin1("POST");
00067 static const QCString NEXT_REPEAT_PROPERTY("NEXTREPEAT");   // X-KDE-KALARM-NEXTREPEAT property
00068 // - Display alarm properties
00069 static const QCString FONT_COLOUR_PROPERTY("FONTCOLOR");    // X-KDE-KALARM-FONTCOLOR property
00070 // - Email alarm properties
00071 static const QCString EMAIL_ID_PROPERTY("EMAILID");         // X-KDE-KALARM-EMAILID property
00072 // - Audio alarm properties
00073 static const QCString VOLUME_PROPERTY("VOLUME");            // X-KDE-KALARM-VOLUME property
00074 static const QCString SPEAK_PROPERTY("SPEAK");              // X-KDE-KALARM-SPEAK property
00075 
00076 // Event categories
00077 static const QString DATE_ONLY_CATEGORY        = QString::fromLatin1("DATE");
00078 static const QString EMAIL_BCC_CATEGORY        = QString::fromLatin1("BCC");
00079 static const QString CONFIRM_ACK_CATEGORY      = QString::fromLatin1("ACKCONF");
00080 static const QString LATE_CANCEL_CATEGORY      = QString::fromLatin1("LATECANCEL;");
00081 static const QString AUTO_CLOSE_CATEGORY       = QString::fromLatin1("LATECLOSE;");
00082 static const QString TEMPL_AFTER_TIME_CATEGORY = QString::fromLatin1("TMPLAFTTIME;");
00083 static const QString KMAIL_SERNUM_CATEGORY     = QString::fromLatin1("KMAIL:");
00084 static const QString KORGANIZER_CATEGORY       = QString::fromLatin1("KORG");
00085 static const QString DEFER_CATEGORY            = QString::fromLatin1("DEFER;");
00086 static const QString ARCHIVE_CATEGORY          = QString::fromLatin1("SAVE");
00087 static const QString ARCHIVE_CATEGORIES        = QString::fromLatin1("SAVE:");
00088 static const QString LOG_CATEGORY              = QString::fromLatin1("LOG:");
00089 static const QString xtermURL = QString::fromLatin1("xterm:");
00090 
00091 // Event status strings
00092 static const QString DISABLED_STATUS           = QString::fromLatin1("DISABLED");
00093 
00094 static const QString EXPIRED_UID    = QString::fromLatin1("-exp-");
00095 static const QString DISPLAYING_UID = QString::fromLatin1("-disp-");
00096 static const QString TEMPLATE_UID   = QString::fromLatin1("-tmpl-");
00097 static const QString KORGANIZER_UID = QString::fromLatin1("-korg-");
00098 
00099 struct AlarmData
00100 {
00101     const Alarm*           alarm;
00102     QString                cleanText;       // text or audio file name
00103     uint                   emailFromId;
00104     EmailAddressList       emailAddresses;
00105     QString                emailSubject;
00106     QStringList            emailAttachments;
00107     QFont                  font;
00108     QColor                 bgColour, fgColour;
00109     float                  soundVolume;
00110     float                  fadeVolume;
00111     int                    fadeSeconds;
00112     int                    startOffsetSecs;
00113     bool                   speak;
00114     KAAlarm::SubType       type;
00115     KAAlarmEventBase::Type action;
00116     int                    displayingFlags;
00117     bool                   defaultFont;
00118     bool                   reminderOnceOnly;
00119     bool                   isEmailText;
00120     bool                   commandScript;
00121     int                    repeatCount;
00122     int                    repeatInterval;
00123     int                    nextRepeat;
00124 };
00125 typedef QMap<KAAlarm::SubType, AlarmData> AlarmMap;
00126 
00127 static void setProcedureAlarm(Alarm*, const QString& commandLine);
00128 
00129 
00130 /*=============================================================================
00131 = Class KAEvent
00132 = Corresponds to a KCal::Event instance.
00133 =============================================================================*/
00134 
00135 inline void KAEvent::set_deferral(DeferType type)
00136 {
00137     if (type)
00138     {
00139         if (!mDeferral)
00140             ++mAlarmCount;
00141     }
00142     else
00143     {
00144         if (mDeferral)
00145             --mAlarmCount;
00146     }
00147     mDeferral = type;
00148 }
00149 
00150 inline void KAEvent::set_reminder(int minutes)
00151 {
00152     if (!mReminderMinutes)
00153         ++mAlarmCount;
00154     mReminderMinutes        = minutes;
00155     mArchiveReminderMinutes = 0;
00156 }
00157 
00158 inline void KAEvent::set_archiveReminder()
00159 {
00160     if (mReminderMinutes)
00161         --mAlarmCount;
00162     mArchiveReminderMinutes = mReminderMinutes;
00163     mReminderMinutes        = 0;
00164 }
00165 
00166 
00167 void KAEvent::copy(const KAEvent& event)
00168 {
00169     KAAlarmEventBase::copy(event);
00170     mTemplateName            = event.mTemplateName;
00171     mAudioFile               = event.mAudioFile;
00172     mPreAction               = event.mPreAction;
00173     mPostAction              = event.mPostAction;
00174     mStartDateTime           = event.mStartDateTime;
00175     mSaveDateTime            = event.mSaveDateTime;
00176     mAtLoginDateTime         = event.mAtLoginDateTime;
00177     mDeferralTime            = event.mDeferralTime;
00178     mDisplayingTime          = event.mDisplayingTime;
00179     mDisplayingFlags         = event.mDisplayingFlags;
00180     mReminderMinutes         = event.mReminderMinutes;
00181     mArchiveReminderMinutes  = event.mArchiveReminderMinutes;
00182     mDeferDefaultMinutes     = event.mDeferDefaultMinutes;
00183     mRevision                = event.mRevision;
00184     mAlarmCount              = event.mAlarmCount;
00185     mDeferral                = event.mDeferral;
00186     mLogFile                 = event.mLogFile;
00187     mCommandXterm            = event.mCommandXterm;
00188     mKMailSerialNumber       = event.mKMailSerialNumber;
00189     mCopyToKOrganizer        = event.mCopyToKOrganizer;
00190     mReminderOnceOnly        = event.mReminderOnceOnly;
00191     mMainExpired             = event.mMainExpired;
00192     mArchiveRepeatAtLogin    = event.mArchiveRepeatAtLogin;
00193     mArchive                 = event.mArchive;
00194     mTemplateAfterTime       = event.mTemplateAfterTime;
00195     mEnabled                 = event.mEnabled;
00196     mUpdated                 = event.mUpdated;
00197     delete mRecurrence;
00198     if (event.mRecurrence)
00199         mRecurrence = new KARecurrence(*event.mRecurrence);
00200     else
00201         mRecurrence = 0;
00202 }
00203 
00204 /******************************************************************************
00205  * Initialise the KAEvent from a KCal::Event.
00206  */
00207 void KAEvent::set(const Event& event)
00208 {
00209     // Extract status from the event
00210     mEventID                = event.uid();
00211     mRevision               = event.revision();
00212     mTemplateName           = QString::null;
00213     mLogFile                = QString::null;
00214     mTemplateAfterTime      = -1;
00215     mBeep                   = false;
00216     mSpeak                  = false;
00217     mEmailBcc               = false;
00218     mCommandXterm           = false;
00219     mCopyToKOrganizer       = false;
00220     mConfirmAck             = false;
00221     mArchive                = false;
00222     mReminderOnceOnly       = false;
00223     mAutoClose              = false;
00224     mArchiveRepeatAtLogin   = false;
00225     mArchiveReminderMinutes = 0;
00226     mDeferDefaultMinutes    = 0;
00227     mLateCancel             = 0;
00228     mKMailSerialNumber      = 0;
00229     mBgColour               = QColor(255, 255, 255);    // missing/invalid colour - return white background
00230     mFgColour               = QColor(0, 0, 0);          // and black foreground
00231     mDefaultFont            = true;
00232     mEnabled                = true;
00233     clearRecur();
00234     bool ok;
00235     bool dateOnly = false;
00236     const QStringList cats = event.categories();
00237     for (unsigned int i = 0;  i < cats.count();  ++i)
00238     {
00239         if (cats[i] == DATE_ONLY_CATEGORY)
00240             dateOnly = true;
00241         else if (cats[i] == CONFIRM_ACK_CATEGORY)
00242             mConfirmAck = true;
00243         else if (cats[i] == EMAIL_BCC_CATEGORY)
00244             mEmailBcc = true;
00245         else if (cats[i] == ARCHIVE_CATEGORY)
00246             mArchive = true;
00247         else if (cats[i] == KORGANIZER_CATEGORY)
00248             mCopyToKOrganizer = true;
00249         else if (cats[i].startsWith(KMAIL_SERNUM_CATEGORY))
00250             mKMailSerialNumber = cats[i].mid(KMAIL_SERNUM_CATEGORY.length()).toULong();
00251         else if (cats[i].startsWith(LOG_CATEGORY))
00252         {
00253             QString logUrl = cats[i].mid(LOG_CATEGORY.length());
00254             if (logUrl == xtermURL)
00255                 mCommandXterm = true;
00256             else
00257                 mLogFile = logUrl;
00258         }
00259         else if (cats[i].startsWith(ARCHIVE_CATEGORIES))
00260         {
00261             // It's the archive flag plus a reminder time and/or repeat-at-login flag
00262             mArchive = true;
00263             QStringList list = QStringList::split(';', cats[i].mid(ARCHIVE_CATEGORIES.length()));
00264             for (unsigned int j = 0;  j < list.count();  ++j)
00265             {
00266                 if (list[j] == AT_LOGIN_TYPE)
00267                     mArchiveRepeatAtLogin = true;
00268                 else if (list[j] == ARCHIVE_REMINDER_ONCE_TYPE)
00269                     mReminderOnceOnly = true;
00270                 else
00271                 {
00272                     char ch;
00273                     const char* cat = list[j].latin1();
00274                     while ((ch = *cat) != 0  &&  (ch < '0' || ch > '9'))
00275                         ++cat;
00276                     if (ch)
00277                     {
00278                         mArchiveReminderMinutes = ch - '0';
00279                         while ((ch = *++cat) >= '0'  &&  ch <= '9')
00280                             mArchiveReminderMinutes = mArchiveReminderMinutes * 10 + ch - '0';
00281                         switch (ch)
00282                         {
00283                             case 'M':  break;
00284                             case 'H':  mArchiveReminderMinutes *= 60;    break;
00285                             case 'D':  mArchiveReminderMinutes *= 1440;  break;
00286                         }
00287                     }
00288                 }
00289             }
00290         }
00291         else if (cats[i].startsWith(DEFER_CATEGORY))
00292         {
00293             mDeferDefaultMinutes = static_cast<int>(cats[i].mid(DEFER_CATEGORY.length()).toUInt(&ok));
00294             if (!ok)
00295                 mDeferDefaultMinutes = 0;    // invalid parameter
00296         }
00297         else if (cats[i].startsWith(TEMPL_AFTER_TIME_CATEGORY))
00298         {
00299             mTemplateAfterTime = static_cast<int>(cats[i].mid(TEMPL_AFTER_TIME_CATEGORY.length()).toUInt(&ok));
00300             if (!ok)
00301                 mTemplateAfterTime = -1;    // invalid parameter
00302         }
00303         else if (cats[i].startsWith(LATE_CANCEL_CATEGORY))
00304         {
00305             mLateCancel = static_cast<int>(cats[i].mid(LATE_CANCEL_CATEGORY.length()).toUInt(&ok));
00306             if (!ok  ||  !mLateCancel)
00307                 mLateCancel = 1;    // invalid parameter defaults to 1 minute
00308         }
00309         else if (cats[i].startsWith(AUTO_CLOSE_CATEGORY))
00310         {
00311             mLateCancel = static_cast<int>(cats[i].mid(AUTO_CLOSE_CATEGORY.length()).toUInt(&ok));
00312             if (!ok  ||  !mLateCancel)
00313                 mLateCancel = 1;    // invalid parameter defaults to 1 minute
00314             mAutoClose = true;
00315         }
00316     }
00317     QString prop = event.customProperty(APPNAME, REPEAT_PROPERTY);
00318     if (!prop.isEmpty())
00319     {
00320         // This property is used when the main alarm has expired
00321         QStringList list = QStringList::split(':', prop);
00322         if (list.count() >= 2)
00323         {
00324             int interval = static_cast<int>(list[0].toUInt());
00325             int count = static_cast<int>(list[1].toUInt());
00326             if (interval && count)
00327             {
00328                 mRepeatInterval = interval;
00329                 mRepeatCount    = count;
00330             }
00331         }
00332     }
00333     mNextMainDateTime = readDateTime(event, dateOnly, mStartDateTime);
00334     mSaveDateTime = event.created();
00335     if (uidStatus() == TEMPLATE)
00336         mTemplateName = event.summary();
00337     if (event.statusStr() == DISABLED_STATUS)
00338         mEnabled = false;
00339 
00340     // Extract status from the event's alarms.
00341     // First set up defaults.
00342     mActionType        = T_MESSAGE;
00343     mMainExpired       = true;
00344     mRepeatAtLogin     = false;
00345     mDisplaying        = false;
00346     mRepeatSound       = false;
00347     mCommandScript     = false;
00348     mDeferral          = NO_DEFERRAL;
00349     mSoundVolume       = -1;
00350     mFadeVolume        = -1;
00351     mFadeSeconds       = 0;
00352     mReminderMinutes   = 0;
00353     mEmailFromIdentity = 0;
00354     mText              = "";
00355     mAudioFile         = "";
00356     mPreAction         = "";
00357     mPostAction        = "";
00358     mEmailSubject      = "";
00359     mEmailAddresses.clear();
00360     mEmailAttachments.clear();
00361 
00362     // Extract data from all the event's alarms and index the alarms by sequence number
00363     AlarmMap alarmMap;
00364     readAlarms(event, &alarmMap);
00365 
00366     // Incorporate the alarms' details into the overall event
00367     mAlarmCount = 0;       // initialise as invalid
00368     DateTime alTime;
00369     bool set = false;
00370     bool isEmailText = false;
00371     bool setDeferralTime = false;
00372     Duration deferralOffset;
00373     for (AlarmMap::ConstIterator it = alarmMap.begin();  it != alarmMap.end();  ++it)
00374     {
00375         const AlarmData& data = it.data();
00376         DateTime dateTime = data.alarm->hasStartOffset() ? mNextMainDateTime.addSecs(data.alarm->startOffset().asSeconds()) : data.alarm->time();
00377         switch (data.type)
00378         {
00379             case KAAlarm::MAIN__ALARM:
00380                 mMainExpired = false;
00381                 alTime = dateTime;
00382                 alTime.setDateOnly(mStartDateTime.isDateOnly());
00383                 if (data.repeatCount  &&  data.repeatInterval)
00384                 {
00385                     mRepeatInterval = data.repeatInterval;   // values may be adjusted in setRecurrence()
00386                     mRepeatCount    = data.repeatCount;
00387                     mNextRepeat     = data.nextRepeat;
00388                 }
00389                 break;
00390             case KAAlarm::AT_LOGIN__ALARM:
00391                 mRepeatAtLogin   = true;
00392                 mAtLoginDateTime = dateTime.rawDateTime();
00393                 alTime = mAtLoginDateTime;
00394                 break;
00395             case KAAlarm::REMINDER__ALARM:
00396                 mReminderMinutes = -(data.startOffsetSecs / 60);
00397                 if (mReminderMinutes)
00398                     mArchiveReminderMinutes = 0;
00399                 break;
00400             case KAAlarm::DEFERRED_REMINDER_DATE__ALARM:
00401             case KAAlarm::DEFERRED_DATE__ALARM:
00402                 mDeferral = (data.type == KAAlarm::DEFERRED_REMINDER_DATE__ALARM) ? REMINDER_DEFERRAL : NORMAL_DEFERRAL;
00403                 mDeferralTime = dateTime;
00404                 mDeferralTime.setDateOnly(true);
00405                 if (data.alarm->hasStartOffset())
00406                     deferralOffset = data.alarm->startOffset();
00407                 break;
00408             case KAAlarm::DEFERRED_REMINDER_TIME__ALARM:
00409             case KAAlarm::DEFERRED_TIME__ALARM:
00410                 mDeferral = (data.type == KAAlarm::DEFERRED_REMINDER_TIME__ALARM) ? REMINDER_DEFERRAL : NORMAL_DEFERRAL;
00411                 mDeferralTime = dateTime;
00412                 if (data.alarm->hasStartOffset())
00413                     deferralOffset = data.alarm->startOffset();
00414                 break;
00415             case KAAlarm::DISPLAYING__ALARM:
00416             {
00417                 mDisplaying      = true;
00418                 mDisplayingFlags = data.displayingFlags;
00419                 bool dateOnly = (mDisplayingFlags & DEFERRAL) ? !(mDisplayingFlags & TIMED_FLAG)
00420                               : mStartDateTime.isDateOnly();
00421                 mDisplayingTime = dateTime;
00422                 mDisplayingTime.setDateOnly(dateOnly);
00423                 alTime = mDisplayingTime;
00424                 break;
00425             }
00426             case KAAlarm::AUDIO__ALARM:
00427                 mAudioFile   = data.cleanText;
00428                 mSpeak       = data.speak  &&  mAudioFile.isEmpty();
00429                 mBeep        = !mSpeak  &&  mAudioFile.isEmpty();
00430                 mSoundVolume = (!mBeep && !mSpeak) ? data.soundVolume : -1;
00431                 mFadeVolume  = (mSoundVolume >= 0  &&  data.fadeSeconds > 0) ? data.fadeVolume : -1;
00432                 mFadeSeconds = (mFadeVolume >= 0) ? data.fadeSeconds : 0;
00433                 mRepeatSound = (!mBeep && !mSpeak)  &&  (data.repeatCount < 0);
00434                 break;
00435             case KAAlarm::PRE_ACTION__ALARM:
00436                 mPreAction = data.cleanText;
00437                 break;
00438             case KAAlarm::POST_ACTION__ALARM:
00439                 mPostAction = data.cleanText;
00440                 break;
00441             case KAAlarm::INVALID__ALARM:
00442             default:
00443                 break;
00444         }
00445 
00446         if (data.reminderOnceOnly)
00447             mReminderOnceOnly = true;
00448         bool noSetNextTime = false;
00449         switch (data.type)
00450         {
00451             case KAAlarm::DEFERRED_REMINDER_DATE__ALARM:
00452             case KAAlarm::DEFERRED_DATE__ALARM:
00453             case KAAlarm::DEFERRED_REMINDER_TIME__ALARM:
00454             case KAAlarm::DEFERRED_TIME__ALARM:
00455                 if (!set)
00456                 {
00457                     // The recurrence has to be evaluated before we can
00458                     // calculate the time of a deferral alarm.
00459                     setDeferralTime = true;
00460                     noSetNextTime = true;
00461                 }
00462                 // fall through to AT_LOGIN__ALARM etc.
00463             case KAAlarm::AT_LOGIN__ALARM:
00464             case KAAlarm::REMINDER__ALARM:
00465             case KAAlarm::DISPLAYING__ALARM:
00466                 if (!set  &&  !noSetNextTime)
00467                     mNextMainDateTime = alTime;
00468                 // fall through to MAIN__ALARM
00469             case KAAlarm::MAIN__ALARM:
00470                 // Ensure that the basic fields are set up even if there is no main
00471                 // alarm in the event (if it has expired and then been deferred)
00472                 if (!set)
00473                 {
00474                     mActionType = data.action;
00475                     mText = (mActionType == T_COMMAND) ? data.cleanText.stripWhiteSpace() : data.cleanText;
00476                     switch (data.action)
00477                     {
00478                         case T_MESSAGE:
00479                             mFont        = data.font;
00480                             mDefaultFont = data.defaultFont;
00481                             if (data.isEmailText)
00482                                 isEmailText = true;
00483                             // fall through to T_FILE
00484                         case T_FILE:
00485                             mBgColour    = data.bgColour;
00486                             mFgColour    = data.fgColour;
00487                             break;
00488                         case T_COMMAND:
00489                             mCommandScript = data.commandScript;
00490                             break;
00491                         case T_EMAIL:
00492                             mEmailFromIdentity = data.emailFromId;
00493                             mEmailAddresses    = data.emailAddresses;
00494                             mEmailSubject      = data.emailSubject;
00495                             mEmailAttachments  = data.emailAttachments;
00496                             break;
00497                         default:
00498                             break;
00499                     }
00500                     set = true;
00501                 }
00502                 if (data.action == T_FILE  &&  mActionType == T_MESSAGE)
00503                     mActionType = T_FILE;
00504                 ++mAlarmCount;
00505                 break;
00506             case KAAlarm::AUDIO__ALARM:
00507             case KAAlarm::PRE_ACTION__ALARM:
00508             case KAAlarm::POST_ACTION__ALARM:
00509             case KAAlarm::INVALID__ALARM:
00510             default:
00511                 break;
00512         }
00513     }
00514     if (!isEmailText)
00515         mKMailSerialNumber = 0;
00516     if (mRepeatAtLogin)
00517         mArchiveRepeatAtLogin = false;
00518 
00519     Recurrence* recur = event.recurrence();
00520     if (recur  &&  recur->doesRecur())
00521     {
00522         int nextRepeat = mNextRepeat;    // setRecurrence() clears mNextRepeat
00523         setRecurrence(*recur);
00524         if (nextRepeat <= mRepeatCount)
00525             mNextRepeat = nextRepeat;
00526     }
00527     else
00528         checkRepetition();
00529 
00530     if (mMainExpired  &&  deferralOffset.asSeconds()  &&  checkRecur() != KARecurrence::NO_RECUR)
00531     {
00532         // Adjust the deferral time for an expired recurrence, since the
00533         // offset is relative to the first actual occurrence.
00534         DateTime dt = mRecurrence->getNextDateTime(mStartDateTime.dateTime().addDays(-1));
00535         dt.setDateOnly(mStartDateTime.isDateOnly());
00536         if (mDeferralTime.isDateOnly())
00537         {
00538             mDeferralTime = dt.addSecs(deferralOffset.asSeconds());
00539             mDeferralTime.setDateOnly(true);
00540         }
00541         else
00542             mDeferralTime = deferralOffset.end(dt.dateTime());
00543     }
00544     if (mDeferral)
00545     {
00546         if (mNextMainDateTime == mDeferralTime)
00547             mDeferral = CANCEL_DEFERRAL;     // it's a cancelled deferral
00548         if (setDeferralTime)
00549             mNextMainDateTime = mDeferralTime;
00550     }
00551 
00552     mUpdated = false;
00553 }
00554 
00555 /******************************************************************************
00556 * Fetch the start and next date/time for a KCal::Event.
00557 * Reply = next main date/time.
00558 */
00559 DateTime KAEvent::readDateTime(const Event& event, bool dateOnly, DateTime& start)
00560 {
00561     start.set(event.dtStart(), dateOnly);
00562     DateTime next = start;
00563     QString prop = event.customProperty(APPNAME, NEXT_RECUR_PROPERTY);
00564     if (prop.length() >= 8)
00565     {
00566         // The next due recurrence time is specified
00567         QDate d(prop.left(4).toInt(), prop.mid(4,2).toInt(), prop.mid(6,2).toInt());
00568         if (d.isValid())
00569         {
00570             if (dateOnly  &&  prop.length() == 8)
00571                 next = d;
00572             else if (!dateOnly  &&  prop.length() == 15  &&  prop[8] == QChar('T'))
00573             {
00574                 QTime t(prop.mid(9,2).toInt(), prop.mid(11,2).toInt(), prop.mid(13,2).toInt());
00575                 if (t.isValid())
00576                     next = QDateTime(d, t);
00577             }
00578         }
00579     }
00580     return next;
00581 }
00582 
00583 /******************************************************************************
00584  * Parse the alarms for a KCal::Event.
00585  * Reply = map of alarm data, indexed by KAAlarm::Type
00586  */
00587 void KAEvent::readAlarms(const Event& event, void* almap)
00588 {
00589     AlarmMap* alarmMap = (AlarmMap*)almap;
00590     Alarm::List alarms = event.alarms();
00591     for (Alarm::List::ConstIterator it = alarms.begin();  it != alarms.end();  ++it)
00592     {
00593         // Parse the next alarm's text
00594         AlarmData data;
00595         readAlarm(**it, data);
00596         if (data.type != KAAlarm::INVALID__ALARM)
00597             alarmMap->insert(data.type, data);
00598     }
00599 }
00600 
00601 /******************************************************************************
00602  * Parse a KCal::Alarm.
00603  * Reply = alarm ID (sequence number)
00604  */
00605 void KAEvent::readAlarm(const Alarm& alarm, AlarmData& data)
00606 {
00607     // Parse the next alarm's text
00608     data.alarm           = &alarm;
00609     data.startOffsetSecs = alarm.startOffset().asSeconds();    // can have start offset but no valid date/time (e.g. reminder in template)
00610     data.displayingFlags = 0;
00611     data.isEmailText     = false;
00612     data.nextRepeat      = 0;
00613     data.repeatInterval  = alarm.snoozeTime();
00614     data.repeatCount     = alarm.repeatCount();
00615     if (data.repeatCount)
00616     {
00617         bool ok;
00618         QString property = alarm.customProperty(APPNAME, NEXT_REPEAT_PROPERTY);
00619         int n = static_cast<int>(property.toUInt(&ok));
00620         if (ok)
00621             data.nextRepeat = n;
00622     }
00623     switch (alarm.type())
00624     {
00625         case Alarm::Procedure:
00626             data.action        = T_COMMAND;
00627             data.cleanText     = alarm.programFile();
00628             data.commandScript = data.cleanText.isEmpty();   // blank command indicates a script
00629             if (!alarm.programArguments().isEmpty())
00630             {
00631                 if (!data.commandScript)
00632                     data.cleanText += ' ';
00633                 data.cleanText += alarm.programArguments();
00634             }
00635             break;
00636         case Alarm::Email:
00637             data.action           = T_EMAIL;
00638             data.emailFromId      = alarm.customProperty(APPNAME, EMAIL_ID_PROPERTY).toUInt();
00639             data.emailAddresses   = alarm.mailAddresses();
00640             data.emailSubject     = alarm.mailSubject();
00641             data.emailAttachments = alarm.mailAttachments();
00642             data.cleanText        = alarm.mailText();
00643             break;
00644         case Alarm::Display:
00645         {
00646             data.action    = T_MESSAGE;
00647             data.cleanText = AlarmText::fromCalendarText(alarm.text(), data.isEmailText);
00648             QString property = alarm.customProperty(APPNAME, FONT_COLOUR_PROPERTY);
00649             QStringList list = QStringList::split(QChar(';'), property, true);
00650             data.bgColour = QColor(255, 255, 255);   // white
00651             data.fgColour = QColor(0, 0, 0);         // black
00652             int n = list.count();
00653             if (n > 0)
00654             {
00655                 if (!list[0].isEmpty())
00656                 {
00657                     QColor c(list[0]);
00658                     if (c.isValid())
00659                         data.bgColour = c;
00660                 }
00661                 if (n > 1  &&  !list[1].isEmpty())
00662                 {
00663                     QColor c(list[1]);
00664                     if (c.isValid())
00665                         data.fgColour = c;
00666                 }
00667             }
00668             data.defaultFont = (n <= 2 || list[2].isEmpty());
00669             if (!data.defaultFont)
00670                 data.font.fromString(list[2]);
00671             break;
00672         }
00673         case Alarm::Audio:
00674         {
00675             data.action      = T_AUDIO;
00676             data.cleanText   = alarm.audioFile();
00677             data.type        = KAAlarm::AUDIO__ALARM;
00678             data.soundVolume = -1;
00679             data.fadeVolume  = -1;
00680             data.fadeSeconds = 0;
00681             data.speak       = !alarm.customProperty(APPNAME, SPEAK_PROPERTY).isNull();
00682             QString property = alarm.customProperty(APPNAME, VOLUME_PROPERTY);
00683             if (!property.isEmpty())
00684             {
00685                 bool ok;
00686                 float fadeVolume;
00687                 int   fadeSecs = 0;
00688                 QStringList list = QStringList::split(QChar(';'), property, true);
00689                 data.soundVolume = list[0].toFloat(&ok);
00690                 if (!ok)
00691                     data.soundVolume = -1;
00692                 if (data.soundVolume >= 0  &&  list.count() >= 3)
00693                 {
00694                     fadeVolume = list[1].toFloat(&ok);
00695                     if (ok)
00696                         fadeSecs = static_cast<int>(list[2].toUInt(&ok));
00697                     if (ok  &&  fadeVolume >= 0  &&  fadeSecs > 0)
00698                     {
00699                         data.fadeVolume  = fadeVolume;
00700                         data.fadeSeconds = fadeSecs;
00701                     }
00702                 }
00703             }
00704             return;
00705         }
00706         case Alarm::Invalid:
00707             data.type = KAAlarm::INVALID__ALARM;
00708             return;
00709     }
00710 
00711     bool atLogin          = false;
00712     bool reminder         = false;
00713     bool deferral         = false;
00714     bool dateDeferral     = false;
00715     data.reminderOnceOnly = false;
00716     data.type = KAAlarm::MAIN__ALARM;
00717     QString property = alarm.customProperty(APPNAME, TYPE_PROPERTY);
00718     QStringList types = QStringList::split(QChar(','), property);
00719     for (unsigned int i = 0;  i < types.count();  ++i)
00720     {
00721         QString type = types[i];
00722         if (type == AT_LOGIN_TYPE)
00723             atLogin = true;
00724         else if (type == FILE_TYPE  &&  data.action == T_MESSAGE)
00725             data.action = T_FILE;
00726         else if (type == REMINDER_TYPE)
00727             reminder = true;
00728         else if (type == REMINDER_ONCE_TYPE)
00729             reminder = data.reminderOnceOnly = true;
00730         else if (type == TIME_DEFERRAL_TYPE)
00731             deferral = true;
00732         else if (type == DATE_DEFERRAL_TYPE)
00733             dateDeferral = deferral = true;
00734         else if (type == DISPLAYING_TYPE)
00735             data.type = KAAlarm::DISPLAYING__ALARM;
00736         else if (type == PRE_ACTION_TYPE  &&  data.action == T_COMMAND)
00737             data.type = KAAlarm::PRE_ACTION__ALARM;
00738         else if (type == POST_ACTION_TYPE  &&  data.action == T_COMMAND)
00739             data.type = KAAlarm::POST_ACTION__ALARM;
00740     }
00741 
00742     if (reminder)
00743     {
00744         if (data.type == KAAlarm::MAIN__ALARM)
00745             data.type = dateDeferral ? KAAlarm::DEFERRED_REMINDER_DATE__ALARM
00746                       : deferral ? KAAlarm::DEFERRED_REMINDER_TIME__ALARM : KAAlarm::REMINDER__ALARM;
00747         else if (data.type == KAAlarm::DISPLAYING__ALARM)
00748             data.displayingFlags = dateDeferral ? REMINDER | DATE_DEFERRAL
00749                                  : deferral ? REMINDER | TIME_DEFERRAL : REMINDER;
00750     }
00751     else if (deferral)
00752     {
00753         if (data.type == KAAlarm::MAIN__ALARM)
00754             data.type = dateDeferral ? KAAlarm::DEFERRED_DATE__ALARM : KAAlarm::DEFERRED_TIME__ALARM;
00755         else if (data.type == KAAlarm::DISPLAYING__ALARM)
00756             data.displayingFlags = dateDeferral ? DATE_DEFERRAL : TIME_DEFERRAL;
00757     }
00758     if (atLogin)
00759     {
00760         if (data.type == KAAlarm::MAIN__ALARM)
00761             data.type = KAAlarm::AT_LOGIN__ALARM;
00762         else if (data.type == KAAlarm::DISPLAYING__ALARM)
00763             data.displayingFlags = REPEAT_AT_LOGIN;
00764     }
00765 //kdDebug(5950)<<"ReadAlarm(): text="<<alarm.text()<<", time="<<alarm.time().toString()<<", valid time="<<alarm.time().isValid()<<endl;
00766 }
00767 
00768 /******************************************************************************
00769  * Initialise the KAEvent with the specified parameters.
00770  */
00771 void KAEvent::set(const QDateTime& dateTime, const QString& text, const QColor& bg, const QColor& fg,
00772                   const QFont& font, Action action, int lateCancel, int flags)
00773 {
00774     clearRecur();
00775     mStartDateTime.set(dateTime, flags & ANY_TIME);
00776     mNextMainDateTime = mStartDateTime;
00777     switch (action)
00778     {
00779         case MESSAGE:
00780         case FILE:
00781         case COMMAND:
00782         case EMAIL:
00783             mActionType = (KAAlarmEventBase::Type)action;
00784             break;
00785         default:
00786             mActionType = T_MESSAGE;
00787             break;
00788     }
00789     mText                   = (mActionType == T_COMMAND) ? text.stripWhiteSpace() : text;
00790     mEventID                = QString::null;
00791     mTemplateName           = QString::null;
00792     mPreAction              = QString::null;
00793     mPostAction             = QString::null;
00794     mAudioFile              = "";
00795     mSoundVolume            = -1;
00796     mFadeVolume             = -1;
00797     mTemplateAfterTime      = -1;
00798     mFadeSeconds            = 0;
00799     mBgColour               = bg;
00800     mFgColour               = fg;
00801     mFont                   = font;
00802     mAlarmCount             = 1;
00803     mLateCancel             = lateCancel;     // do this before setting flags
00804     mDeferral               = NO_DEFERRAL;    // do this before setting flags
00805 
00806     KAAlarmEventBase::set(flags & ~READ_ONLY_FLAGS);
00807     mStartDateTime.setDateOnly(flags & ANY_TIME);
00808     set_deferral((flags & DEFERRAL) ? NORMAL_DEFERRAL : NO_DEFERRAL);
00809     mCommandXterm           = flags & EXEC_IN_XTERM;
00810     mCopyToKOrganizer       = flags & COPY_KORGANIZER;
00811     mEnabled                = !(flags & DISABLED);
00812 
00813     mKMailSerialNumber      = 0;
00814     mReminderMinutes        = 0;
00815     mArchiveReminderMinutes = 0;
00816     mDeferDefaultMinutes    = 0;
00817     mArchiveRepeatAtLogin   = false;
00818     mReminderOnceOnly       = false;
00819     mDisplaying             = false;
00820     mMainExpired            = false;
00821     mArchive                = false;
00822     mUpdated                = false;
00823 }
00824 
00825 /******************************************************************************
00826  * Initialise a command KAEvent.
00827  */
00828 void KAEvent::setCommand(const QDate& d, const QString& command, int lateCancel, int flags, const QString& logfile)
00829 {
00830     if (!logfile.isEmpty())
00831         flags &= ~EXEC_IN_XTERM;
00832     set(d, command, QColor(), QColor(), QFont(), COMMAND, lateCancel, flags | ANY_TIME);
00833     mLogFile = logfile;
00834 }
00835 
00836 void KAEvent::setCommand(const QDateTime& dt, const QString& command, int lateCancel, int flags, const QString& logfile)
00837 {
00838     if (!logfile.isEmpty())
00839         flags &= ~EXEC_IN_XTERM;
00840     set(dt, command, QColor(), QColor(), QFont(), COMMAND, lateCancel, flags);
00841     mLogFile = logfile;
00842 }
00843 
00844 void KAEvent::setLogFile(const QString& logfile)
00845 {
00846     mLogFile = logfile;
00847     if (!logfile.isEmpty())
00848         mCommandXterm = false;
00849 }
00850 
00851 /******************************************************************************
00852  * Initialise an email KAEvent.
00853  */
00854 void KAEvent::setEmail(const QDate& d, uint from, const EmailAddressList& addresses, const QString& subject,
00855                 const QString& message, const QStringList& attachments, int lateCancel, int flags)
00856 {
00857     set(d, message, QColor(), QColor(), QFont(), EMAIL, lateCancel, flags | ANY_TIME);
00858     mEmailFromIdentity = from;
00859     mEmailAddresses    = addresses;
00860     mEmailSubject      = subject;
00861     mEmailAttachments  = attachments;
00862 }
00863 
00864 void KAEvent::setEmail(const QDateTime& dt, uint from, const EmailAddressList& addresses, const QString& subject,
00865                 const QString& message, const QStringList& attachments, int lateCancel, int flags)
00866 {
00867     set(dt, message, QColor(), QColor(), QFont(), EMAIL, lateCancel, flags);
00868     mEmailFromIdentity = from;
00869     mEmailAddresses    = addresses;
00870     mEmailSubject      = subject;
00871     mEmailAttachments  = attachments;
00872 }
00873 
00874 void KAEvent::setEmail(uint from, const EmailAddressList& addresses, const QString& subject, const QStringList& attachments)
00875 {
00876     mEmailFromIdentity = from;
00877     mEmailAddresses    = addresses;
00878     mEmailSubject      = subject;
00879     mEmailAttachments  = attachments;
00880 }
00881 
00882 void KAEvent::setAudioFile(const QString& filename, float volume, float fadeVolume, int fadeSeconds)
00883 {
00884     mAudioFile = filename;
00885     mSoundVolume = filename.isEmpty() ? -1 : volume;
00886     if (mSoundVolume >= 0)
00887     {
00888         mFadeVolume  = (fadeSeconds > 0) ? fadeVolume : -1;
00889         mFadeSeconds = (mFadeVolume >= 0) ? fadeSeconds : 0;
00890     }
00891     else
00892     {
00893         mFadeVolume  = -1;
00894         mFadeSeconds = 0;
00895     }
00896     mUpdated = true;
00897 }
00898 
00899 void KAEvent::setReminder(int minutes, bool onceOnly)
00900 {
00901     set_reminder(minutes);
00902     mReminderOnceOnly = onceOnly;
00903     mUpdated          = true;
00904 }
00905 
00906 /******************************************************************************
00907  * Return the time of the next scheduled occurrence of the event.
00908  * Reminders and deferred reminders can optionally be ignored.
00909  */
00910 DateTime KAEvent::displayDateTime() const
00911 {
00912     DateTime dt = mainDateTime(true);
00913     if (mDeferral > 0  &&  mDeferral != REMINDER_DEFERRAL)
00914     {
00915         if (mMainExpired)
00916             return mDeferralTime;
00917         return QMIN(mDeferralTime, dt);
00918     }
00919     return dt;
00920 }
00921 
00922 /******************************************************************************
00923  * Convert a unique ID to indicate that the event is in a specified calendar file.
00924  */
00925 QString KAEvent::uid(const QString& id, Status status)
00926 {
00927     QString result = id;
00928     Status oldStatus;
00929     int i, len;
00930     if ((i = result.find(EXPIRED_UID)) > 0)
00931     {
00932         oldStatus = EXPIRED;
00933         len = EXPIRED_UID.length();
00934     }
00935     else if ((i = result.find(DISPLAYING_UID)) > 0)
00936     {
00937         oldStatus = DISPLAYING;
00938         len = DISPLAYING_UID.length();
00939     }
00940     else if ((i = result.find(TEMPLATE_UID)) > 0)
00941     {
00942         oldStatus = TEMPLATE;
00943         len = TEMPLATE_UID.length();
00944     }
00945     else if ((i = result.find(KORGANIZER_UID)) > 0)
00946     {
00947         oldStatus = KORGANIZER;
00948         len = KORGANIZER_UID.length();
00949     }
00950     else
00951     {
00952         oldStatus = ACTIVE;
00953         i = result.findRev('-');
00954         len = 1;
00955     }
00956     if (status != oldStatus  &&  i > 0)
00957     {
00958         QString part;
00959         switch (status)
00960         {
00961             case ACTIVE:      part = "-";  break;
00962             case EXPIRED:     part = EXPIRED_UID;  break;
00963             case DISPLAYING:  part = DISPLAYING_UID;  break;
00964             case TEMPLATE:    part = TEMPLATE_UID;  break;
00965             case KORGANIZER:  part = KORGANIZER_UID;  break;
00966         }
00967         result.replace(i, len, part);
00968     }
00969     return result;
00970 }
00971 
00972 /******************************************************************************
00973  * Get the calendar type for a unique ID.
00974  */
00975 KAEvent::Status KAEvent::uidStatus(const QString& uid)
00976 {
00977     if (uid.find(EXPIRED_UID) > 0)
00978         return EXPIRED;
00979     if (uid.find(DISPLAYING_UID) > 0)
00980         return DISPLAYING;
00981     if (uid.find(TEMPLATE_UID) > 0)
00982         return TEMPLATE;
00983     if (uid.find(KORGANIZER_UID) > 0)
00984         return KORGANIZER;
00985     return ACTIVE;
00986 }
00987 
00988 int KAEvent::flags() const
00989 {
00990     return KAAlarmEventBase::flags()
00991          | (mStartDateTime.isDateOnly() ? ANY_TIME : 0)
00992          | (mDeferral > 0               ? DEFERRAL : 0)
00993          | (mCommandXterm               ? EXEC_IN_XTERM : 0)
00994          | (mCopyToKOrganizer           ? COPY_KORGANIZER : 0)
00995          | (mEnabled                    ? 0 : DISABLED);
00996 }
00997 
00998 /******************************************************************************
00999  * Create a new Event from the KAEvent data.
01000  */
01001 Event* KAEvent::event() const
01002 {
01003     KCal::Event* ev = new KCal::Event;
01004     ev->setUid(mEventID);
01005     updateKCalEvent(*ev, false);
01006     return ev;
01007 }
01008 
01009 /******************************************************************************
01010  * Update an existing KCal::Event with the KAEvent data.
01011  * If 'original' is true, the event start date/time is adjusted to its original
01012  * value instead of its next occurrence, and the expired main alarm is
01013  * reinstated.
01014  */
01015 bool KAEvent::updateKCalEvent(Event& ev, bool checkUid, bool original, bool cancelCancelledDefer) const
01016 {
01017     if (checkUid  &&  !mEventID.isEmpty()  &&  mEventID != ev.uid()
01018     ||  !mAlarmCount  &&  (!original || !mMainExpired))
01019         return false;
01020 
01021     checkRecur();     // ensure recurrence/repetition data is consistent
01022     bool readOnly = ev.isReadOnly();
01023     ev.setReadOnly(false);
01024     ev.setTransparency(Event::Transparent);
01025 
01026     // Set up event-specific data
01027 
01028     // Set up custom properties.
01029     ev.removeCustomProperty(APPNAME, NEXT_RECUR_PROPERTY);
01030     ev.removeCustomProperty(APPNAME, REPEAT_PROPERTY);
01031 
01032     QStringList cats;
01033     if (mStartDateTime.isDateOnly())
01034         cats.append(DATE_ONLY_CATEGORY);
01035     if (mConfirmAck)
01036         cats.append(CONFIRM_ACK_CATEGORY);
01037     if (mEmailBcc)
01038         cats.append(EMAIL_BCC_CATEGORY);
01039     if (mKMailSerialNumber)
01040         cats.append(QString("%1%2").arg(KMAIL_SERNUM_CATEGORY).arg(mKMailSerialNumber));
01041     if (mCopyToKOrganizer)
01042         cats.append(KORGANIZER_CATEGORY);
01043     if (mCommandXterm)
01044         cats.append(LOG_CATEGORY + xtermURL);
01045     else if (!mLogFile.isEmpty())
01046         cats.append(LOG_CATEGORY + mLogFile);
01047     if (mLateCancel)
01048         cats.append(QString("%1%2").arg(mAutoClose ? AUTO_CLOSE_CATEGORY : LATE_CANCEL_CATEGORY).arg(mLateCancel));
01049     if (mDeferDefaultMinutes)
01050         cats.append(QString("%1%2").arg(DEFER_CATEGORY).arg(mDeferDefaultMinutes));
01051     if (!mTemplateName.isEmpty()  &&  mTemplateAfterTime >= 0)
01052         cats.append(QString("%1%2").arg(TEMPL_AFTER_TIME_CATEGORY).arg(mTemplateAfterTime));
01053     if (mArchive  &&  !original)
01054     {
01055         QStringList params;
01056         if (mArchiveReminderMinutes)
01057         {
01058             if (mReminderOnceOnly)
01059                 params += ARCHIVE_REMINDER_ONCE_TYPE;
01060             char unit = 'M';
01061             int count = mArchiveReminderMinutes;
01062             if (count % 1440 == 0)
01063             {
01064                 unit = 'D';
01065                 count /= 1440;
01066             }
01067             else if (count % 60 == 0)
01068             {
01069                 unit = 'H';
01070                 count /= 60;
01071             }
01072             params += QString("%1%2").arg(count).arg(unit);
01073         }
01074         if (mArchiveRepeatAtLogin)
01075             params += AT_LOGIN_TYPE;
01076         if (params.count() > 0)
01077         {
01078             QString cat = ARCHIVE_CATEGORIES;
01079             cat += params.join(QString::fromLatin1(";"));
01080             cats.append(cat);
01081         }
01082         else
01083             cats.append(ARCHIVE_CATEGORY);
01084     }
01085     ev.setCategories(cats);
01086     ev.setCustomStatus(mEnabled ? QString::null : DISABLED_STATUS);
01087     ev.setRevision(mRevision);
01088     ev.clearAlarms();
01089 
01090     // Always set DTSTART as date/time, since alarm times can only be specified
01091     // in local time (instead of UTC) if they are relative to a DTSTART or DTEND
01092     // which is also specified in local time. Instead of calling setFloats() to
01093     // indicate a date-only event, the category "DATE" is included.
01094     ev.setDtStart(mStartDateTime.dateTime());
01095     ev.setFloats(false);
01096     ev.setHasEndDate(false);
01097 
01098     DateTime dtMain = original ? mStartDateTime : mNextMainDateTime;
01099     int      ancillaryType = 0;   // 0 = invalid, 1 = time, 2 = offset
01100     DateTime ancillaryTime;       // time for ancillary alarms (audio, pre-action, etc)
01101     int      ancillaryOffset = 0; // start offset for ancillary alarms
01102     if (!mMainExpired  ||  original)
01103     {
01104         /* The alarm offset must always be zero for the main alarm. To determine
01105          * which recurrence is due, the property X-KDE-KALARM_NEXTRECUR is used.
01106          * If the alarm offset was non-zero, exception dates and rules would not
01107          * work since they apply to the event time, not the alarm time.
01108          */
01109         if (!original  &&  checkRecur() != KARecurrence::NO_RECUR)
01110         {
01111             QDateTime dt = mNextMainDateTime.dateTime();
01112             ev.setCustomProperty(APPNAME, NEXT_RECUR_PROPERTY,
01113                                  dt.toString(mNextMainDateTime.isDateOnly() ? "yyyyMMdd" : "yyyyMMddThhmmss"));
01114         }
01115         // Add the main alarm
01116         initKCalAlarm(ev, 0, QStringList(), KAAlarm::MAIN_ALARM);
01117         ancillaryOffset = 0;
01118         ancillaryType = dtMain.isValid() ? 2 : 0;
01119     }
01120     else if (mRepeatCount  &&  mRepeatInterval)
01121     {
01122         // Alarm repetition is normally held in the main alarm, but since
01123         // the main alarm has expired, store in a custom property.
01124         QString param = QString("%1:%2").arg(mRepeatInterval).arg(mRepeatCount);
01125         ev.setCustomProperty(APPNAME, REPEAT_PROPERTY, param);
01126     }
01127 
01128     // Add subsidiary alarms
01129     if (mRepeatAtLogin  ||  mArchiveRepeatAtLogin && original)
01130     {
01131         DateTime dtl;
01132         if (mArchiveRepeatAtLogin)
01133             dtl = mStartDateTime.dateTime().addDays(-1);
01134         else if (mAtLoginDateTime.isValid())
01135             dtl = mAtLoginDateTime;
01136         else if (mStartDateTime.isDateOnly())
01137             dtl = QDate::currentDate().addDays(-1);
01138         else
01139             dtl = QDateTime::currentDateTime();
01140         initKCalAlarm(ev, dtl, AT_LOGIN_TYPE);
01141         if (!ancillaryType  &&  dtl.isValid())
01142         {
01143             ancillaryTime = dtl;
01144             ancillaryType = 1;
01145         }
01146     }
01147     if (mReminderMinutes  ||  mArchiveReminderMinutes && original)
01148     {
01149         int minutes = mReminderMinutes ? mReminderMinutes : mArchiveReminderMinutes;
01150         initKCalAlarm(ev, -minutes * 60, QStringList(mReminderOnceOnly ? REMINDER_ONCE_TYPE : REMINDER_TYPE));
01151         if (!ancillaryType)
01152         {
01153             ancillaryOffset = -minutes * 60;
01154             ancillaryType = 2;
01155         }
01156     }
01157     if (mDeferral > 0  ||  mDeferral == CANCEL_DEFERRAL && !cancelCancelledDefer)
01158     {
01159         DateTime nextDateTime = mNextMainDateTime;
01160         if (mMainExpired)
01161         {
01162             if (checkRecur() == KARecurrence::NO_RECUR)
01163                 nextDateTime = mStartDateTime;
01164             else if (!original)
01165             {
01166                 // It's a deferral of an expired recurrence.
01167                 // Need to ensure that the alarm offset is to an occurrence
01168                 // which isn't excluded by an exception - otherwise, it will
01169                 // never be triggered. So choose the first recurrence which
01170                 // isn't an exception.
01171                 nextDateTime = mRecurrence->getNextDateTime(mStartDateTime.dateTime().addDays(-1));
01172                 nextDateTime.setDateOnly(mStartDateTime.isDateOnly());
01173             }
01174         }
01175         int startOffset;
01176         QStringList list;
01177         if (mDeferralTime.isDateOnly())
01178         {
01179             startOffset = nextDateTime.secsTo(mDeferralTime.dateTime());
01180             list += DATE_DEFERRAL_TYPE;
01181         }
01182         else
01183         {
01184             startOffset = nextDateTime.dateTime().secsTo(mDeferralTime.dateTime());
01185             list += TIME_DEFERRAL_TYPE;
01186         }
01187         if (mDeferral == REMINDER_DEFERRAL)
01188             list += mReminderOnceOnly ? REMINDER_ONCE_TYPE : REMINDER_TYPE;
01189         initKCalAlarm(ev, startOffset, list);
01190         if (!ancillaryType  &&  mDeferralTime.isValid())
01191         {
01192             ancillaryOffset = startOffset;
01193             ancillaryType = 2;
01194         }
01195     }
01196     if (!mTemplateName.isEmpty())
01197         ev.setSummary(mTemplateName);
01198     else if (mDisplaying)
01199     {
01200         QStringList list(DISPLAYING_TYPE);
01201         if (mDisplayingFlags & REPEAT_AT_LOGIN)
01202             list += AT_LOGIN_TYPE;
01203         else if (mDisplayingFlags & DEFERRAL)
01204         {
01205             if (mDisplayingFlags & TIMED_FLAG)
01206                 list += TIME_DEFERRAL_TYPE;
01207             else
01208                 list += DATE_DEFERRAL_TYPE;
01209         }
01210         if (mDisplayingFlags & REMINDER)
01211             list += mReminderOnceOnly ? REMINDER_ONCE_TYPE : REMINDER_TYPE;
01212         initKCalAlarm(ev, mDisplayingTime, list);
01213         if (!ancillaryType  &&  mDisplayingTime.isValid())
01214         {
01215             ancillaryTime = mDisplayingTime;
01216             ancillaryType = 1;
01217         }
01218     }
01219     if (mBeep  ||  mSpeak  ||  !mAudioFile.isEmpty())
01220     {
01221         // A sound is specified
01222         if (ancillaryType == 2)
01223             initKCalAlarm(ev, ancillaryOffset, QStringList(), KAAlarm::AUDIO_ALARM);
01224         else
01225             initKCalAlarm(ev, ancillaryTime, QStringList(), KAAlarm::AUDIO_ALARM);
01226     }
01227     if (!mPreAction.isEmpty())
01228     {
01229         // A pre-display action is specified
01230         if (ancillaryType == 2)
01231             initKCalAlarm(ev, ancillaryOffset, QStringList(PRE_ACTION_TYPE), KAAlarm::PRE_ACTION_ALARM);
01232         else
01233             initKCalAlarm(ev, ancillaryTime, QStringList(PRE_ACTION_TYPE), KAAlarm::PRE_ACTION_ALARM);
01234     }
01235     if (!mPostAction.isEmpty())
01236     {
01237         // A post-display action is specified
01238         if (ancillaryType == 2)
01239             initKCalAlarm(ev, ancillaryOffset, QStringList(POST_ACTION_TYPE), KAAlarm::POST_ACTION_ALARM);
01240         else
01241             initKCalAlarm(ev, ancillaryTime, QStringList(POST_ACTION_TYPE), KAAlarm::POST_ACTION_ALARM);
01242     }
01243 
01244     if (mRecurrence)
01245         mRecurrence->writeRecurrence(*ev.recurrence());
01246     else
01247         ev.clearRecurrence();
01248     if (mSaveDateTime.isValid())
01249         ev.setCreated(mSaveDateTime);
01250     ev.setReadOnly(readOnly);
01251     return true;
01252 }
01253 
01254 /******************************************************************************
01255  * Create a new alarm for a libkcal event, and initialise it according to the
01256  * alarm action. If 'types' is non-null, it is appended to the X-KDE-KALARM-TYPE
01257  * property value list.
01258  */
01259 Alarm* KAEvent::initKCalAlarm(Event& event, const DateTime& dt, const QStringList& types, KAAlarm::Type type) const
01260 {
01261     int startOffset = dt.isDateOnly() ? mStartDateTime.secsTo(dt)
01262                                       : mStartDateTime.dateTime().secsTo(dt.dateTime());
01263     return initKCalAlarm(event, startOffset, types, type);
01264 }
01265 
01266 Alarm* KAEvent::initKCalAlarm(Event& event, int startOffsetSecs, const QStringList& types, KAAlarm::Type type) const
01267 {
01268     QStringList alltypes;
01269     Alarm* alarm = event.newAlarm();
01270     alarm->setEnabled(true);
01271     if (type != KAAlarm::MAIN_ALARM)
01272     {
01273         // RFC2445 specifies that absolute alarm times must be stored as UTC.
01274         // So, in order to store local times, set the alarm time as an offset to DTSTART.
01275         alarm->setStartOffset(startOffsetSecs);
01276     }
01277 
01278     switch (type)
01279     {
01280         case KAAlarm::AUDIO_ALARM:
01281             alarm->setAudioAlarm(mAudioFile);  // empty for a beep or for speaking
01282             if (mSpeak)
01283                 alarm->setCustomProperty(APPNAME, SPEAK_PROPERTY, QString::fromLatin1("Y"));
01284             if (mRepeatSound)
01285             {
01286                 alarm->setRepeatCount(-1);
01287                 alarm->setSnoozeTime(0);
01288             }
01289             if (!mAudioFile.isEmpty()  &&  mSoundVolume >= 0)
01290                 alarm->setCustomProperty(APPNAME, VOLUME_PROPERTY,
01291                               QString::fromLatin1("%1;%2;%3").arg(QString::number(mSoundVolume, 'f', 2))
01292                                                              .arg(QString::number(mFadeVolume, 'f', 2))
01293                                                              .arg(mFadeSeconds));
01294             break;
01295         case KAAlarm::PRE_ACTION_ALARM:
01296             setProcedureAlarm(alarm, mPreAction);
01297             break;
01298         case KAAlarm::POST_ACTION_ALARM:
01299             setProcedureAlarm(alarm, mPostAction);
01300             break;
01301         case KAAlarm::MAIN_ALARM:
01302             alarm->setSnoozeTime(mRepeatInterval);
01303             alarm->setRepeatCount(mRepeatCount);
01304             if (mRepeatCount)
01305                 alarm->setCustomProperty(APPNAME, NEXT_REPEAT_PROPERTY,
01306                                          QString::number(mNextRepeat));
01307             // fall through to INVALID_ALARM
01308         case KAAlarm::INVALID_ALARM:
01309             switch (mActionType)
01310             {
01311                 case T_FILE:
01312                     alltypes += FILE_TYPE;
01313                     // fall through to T_MESSAGE
01314                 case T_MESSAGE:
01315                     alarm->setDisplayAlarm(AlarmText::toCalendarText(mText));
01316                     alarm->setCustomProperty(APPNAME, FONT_COLOUR_PROPERTY,
01317                               QString::fromLatin1("%1;%2;%3").arg(mBgColour.name())
01318                                              .arg(mFgColour.name())
01319                                              .arg(mDefaultFont ? QString::null : mFont.toString()));
01320                     break;
01321                 case T_COMMAND:
01322                     if (mCommandScript)
01323                         alarm->setProcedureAlarm("", mText);
01324                     else
01325                         setProcedureAlarm(alarm, mText);
01326                     break;
01327                 case T_EMAIL:
01328                     alarm->setEmailAlarm(mEmailSubject, mText, mEmailAddresses, mEmailAttachments);
01329                     if (mEmailFromIdentity)
01330                         alarm->setCustomProperty(APPNAME, EMAIL_ID_PROPERTY, QString::number(mEmailFromIdentity));
01331                     break;
01332                 case T_AUDIO:
01333                     break;
01334             }
01335             break;
01336         case KAAlarm::REMINDER_ALARM:
01337         case KAAlarm::DEFERRED_ALARM:
01338         case KAAlarm::DEFERRED_REMINDER_ALARM:
01339         case KAAlarm::AT_LOGIN_ALARM:
01340         case KAAlarm::DISPLAYING_ALARM:
01341             break;
01342     }
01343     alltypes += types;
01344     if (alltypes.count() > 0)
01345         alarm->setCustomProperty(APPNAME, TYPE_PROPERTY, alltypes.join(","));
01346     return alarm;
01347 }
01348 
01349 /******************************************************************************
01350  * Return the alarm of the specified type.
01351  */
01352 KAAlarm KAEvent::alarm(KAAlarm::Type type) const
01353 {
01354     checkRecur();     // ensure recurrence/repetition data is consistent
01355     KAAlarm al;       // this sets type to INVALID_ALARM
01356     if (mAlarmCount)
01357     {
01358         al.mEventID        = mEventID;
01359         al.mActionType     = mActionType;
01360         al.mText           = mText;
01361         al.mBgColour       = mBgColour;
01362         al.mFgColour       = mFgColour;
01363         al.mFont           = mFont;
01364         al.mDefaultFont    = mDefaultFont;
01365         al.mBeep           = mBeep;
01366         al.mSpeak          = mSpeak;
01367         al.mSoundVolume    = mSoundVolume;
01368         al.mFadeVolume     = mFadeVolume;
01369         al.mFadeSeconds    = mFadeSeconds;
01370         al.mRepeatSound    = mRepeatSound;
01371         al.mConfirmAck     = mConfirmAck;
01372         al.mRepeatCount    = 0;
01373         al.mRepeatInterval = 0;
01374         al.mRepeatAtLogin  = false;
01375         al.mDeferred       = false;
01376         al.mLateCancel     = mLateCancel;
01377         al.mAutoClose      = mAutoClose;
01378         al.mEmailBcc       = mEmailBcc;
01379         al.mCommandScript  = mCommandScript;
01380         if (mActionType == T_EMAIL)
01381         {
01382             al.mEmailFromIdentity = mEmailFromIdentity;
01383             al.mEmailAddresses    = mEmailAddresses;
01384             al.mEmailSubject      = mEmailSubject;
01385             al.mEmailAttachments  = mEmailAttachments;
01386         }
01387         switch (type)
01388         {
01389             case KAAlarm::MAIN_ALARM:
01390                 if (!mMainExpired)
01391                 {
01392                     al.mType             = KAAlarm::MAIN__ALARM;
01393                     al.mNextMainDateTime = mNextMainDateTime;
01394                     al.mRepeatCount      = mRepeatCount;
01395                     al.mRepeatInterval   = mRepeatInterval;
01396                     al.mNextRepeat       = mNextRepeat;
01397                 }
01398                 break;
01399             case KAAlarm::REMINDER_ALARM:
01400                 if (mReminderMinutes)
01401                 {
01402                     al.mType = KAAlarm::REMINDER__ALARM;
01403                     if (mReminderOnceOnly)
01404                         al.mNextMainDateTime = mStartDateTime.addMins(-mReminderMinutes);
01405                     else
01406                         al.mNextMainDateTime = mNextMainDateTime.addMins(-mReminderMinutes);
01407                 }
01408                 break;
01409             case KAAlarm::DEFERRED_REMINDER_ALARM:
01410                 if (mDeferral != REMINDER_DEFERRAL)
01411                     break;
01412                 // fall through to DEFERRED_ALARM
01413             case KAAlarm::DEFERRED_ALARM:
01414                 if (mDeferral > 0)
01415                 {
01416                     al.mType = static_cast<KAAlarm::SubType>((mDeferral == REMINDER_DEFERRAL ? KAAlarm::DEFERRED_REMINDER_ALARM : KAAlarm::DEFERRED_ALARM)
01417                                                              | (mDeferralTime.isDateOnly() ? 0 : KAAlarm::TIMED_DEFERRAL_FLAG));
01418                     al.mNextMainDateTime = mDeferralTime;
01419                     al.mDeferred         = true;
01420                 }
01421                 break;
01422             case KAAlarm::AT_LOGIN_ALARM:
01423                 if (mRepeatAtLogin)
01424                 {
01425                     al.mType             = KAAlarm::AT_LOGIN__ALARM;
01426                     al.mNextMainDateTime = mAtLoginDateTime;
01427                     al.mRepeatAtLogin    = true;
01428                     al.mLateCancel       = 0;
01429                     al.mAutoClose        = false;
01430                 }
01431                 break;
01432             case KAAlarm::DISPLAYING_ALARM:
01433                 if (mDisplaying)
01434                 {
01435                     al.mType             = KAAlarm::DISPLAYING__ALARM;
01436                     al.mNextMainDateTime = mDisplayingTime;
01437                     al.mDisplaying       = true;
01438                 }
01439                 break;
01440             case KAAlarm::AUDIO_ALARM:
01441             case KAAlarm::PRE_ACTION_ALARM:
01442             case KAAlarm::POST_ACTION_ALARM:
01443             case KAAlarm::INVALID_ALARM:
01444             default:
01445                 break;
01446         }
01447     }
01448     return al;
01449 }
01450 
01451 /******************************************************************************
01452  * Return the main alarm for the event.
01453  * If the main alarm does not exist, one of the subsidiary ones is returned if
01454  * possible.
01455  * N.B. a repeat-at-login alarm can only be returned if it has been read from/
01456  * written to the calendar file.
01457  */
01458 KAAlarm KAEvent::firstAlarm() const
01459 {
01460     if (mAlarmCount)
01461     {
01462         if (!mMainExpired)
01463             return alarm(KAAlarm::MAIN_ALARM);
01464         return nextAlarm(KAAlarm::MAIN_ALARM);
01465     }
01466     return KAAlarm();
01467 }
01468 
01469 /******************************************************************************
01470  * Return the next alarm for the event, after the specified alarm.
01471  * N.B. a repeat-at-login alarm can only be returned if it has been read from/
01472  * written to the calendar file.
01473  */
01474 KAAlarm KAEvent::nextAlarm(KAAlarm::Type prevType) const
01475 {
01476     switch (prevType)
01477     {
01478         case KAAlarm::MAIN_ALARM:
01479             if (mReminderMinutes)
01480                 return alarm(KAAlarm::REMINDER_ALARM);
01481             // fall through to REMINDER_ALARM
01482         case KAAlarm::REMINDER_ALARM:
01483             // There can only be one deferral alarm
01484             if (mDeferral == REMINDER_DEFERRAL)
01485                 return alarm(KAAlarm::DEFERRED_REMINDER_ALARM);
01486             if (mDeferral == NORMAL_DEFERRAL)
01487                 return alarm(KAAlarm::DEFERRED_ALARM);
01488             // fall through to DEFERRED_ALARM
01489         case KAAlarm::DEFERRED_REMINDER_ALARM:
01490         case KAAlarm::DEFERRED_ALARM:
01491             if (mRepeatAtLogin)
01492                 return alarm(KAAlarm::AT_LOGIN_ALARM);
01493             // fall through to AT_LOGIN_ALARM
01494         case KAAlarm::AT_LOGIN_ALARM:
01495             if (mDisplaying)
01496                 return alarm(KAAlarm::DISPLAYING_ALARM);
01497             // fall through to DISPLAYING_ALARM
01498         case KAAlarm::DISPLAYING_ALARM:
01499             // fall through to default
01500         case KAAlarm::AUDIO_ALARM:
01501         case KAAlarm::PRE_ACTION_ALARM:
01502         case KAAlarm::POST_ACTION_ALARM:
01503         case KAAlarm::INVALID_ALARM:
01504         default:
01505             break;
01506     }
01507     return KAAlarm();
01508 }
01509 
01510 /******************************************************************************
01511  * Remove the alarm of the specified type from the event.
01512  * This must only be called to remove an alarm which has expired, not to
01513  * reconfigure the event.
01514  */
01515 void KAEvent::removeExpiredAlarm(KAAlarm::Type type)
01516 {
01517     int count = mAlarmCount;
01518     switch (type)
01519     {
01520         case KAAlarm::MAIN_ALARM:
01521             mAlarmCount = 0;    // removing main alarm - also remove subsidiary alarms
01522             break;
01523         case KAAlarm::AT_LOGIN_ALARM:
01524             if (mRepeatAtLogin)
01525             {
01526                 // Remove the at-login alarm, but keep a note of it for archiving purposes
01527                 mArchiveRepeatAtLogin = true;
01528                 mRepeatAtLogin = false;
01529                 --mAlarmCount;
01530             }
01531             break;
01532         case KAAlarm::REMINDER_ALARM:
01533             // Remove any reminder alarm, but keep a note of it for archiving purposes
01534             set_archiveReminder();
01535             break;
01536         case KAAlarm::DEFERRED_REMINDER_ALARM:
01537         case KAAlarm::DEFERRED_ALARM:
01538             set_deferral(NO_DEFERRAL);
01539             break;
01540         case KAAlarm::DISPLAYING_ALARM:
01541             if (mDisplaying)
01542             {
01543                 mDisplaying = false;
01544                 --mAlarmCount;
01545             }
01546             break;
01547         case KAAlarm::AUDIO_ALARM:
01548         case KAAlarm::PRE_ACTION_ALARM:
01549         case KAAlarm::POST_ACTION_ALARM:
01550         case KAAlarm::INVALID_ALARM:
01551         default:
01552             break;
01553     }
01554     if (mAlarmCount != count)
01555         mUpdated = true;
01556 }
01557 
01558 /******************************************************************************
01559  * Defer the event to the specified time.
01560  * If the main alarm time has passed, the main alarm is marked as expired.
01561  * If 'adjustRecurrence' is true, ensure that the next scheduled recurrence is
01562  * after the current time.
01563  * Reply = true if a repetition has been deferred.
01564  */
01565 bool KAEvent::defer(const DateTime& dateTime, bool reminder, bool adjustRecurrence)
01566 {
01567     bool result = false;
01568     bool setNextRepetition = false;
01569     bool checkRepetition = false;
01570     cancelCancelledDeferral();
01571     if (checkRecur() == KARecurrence::NO_RECUR)
01572     {
01573         if (mReminderMinutes  ||  mDeferral == REMINDER_DEFERRAL  ||  mArchiveReminderMinutes)
01574         {
01575             if (dateTime < mNextMainDateTime.dateTime())
01576             {
01577                 set_deferral(REMINDER_DEFERRAL);   // defer reminder alarm
01578                 mDeferralTime = dateTime;
01579             }
01580             else
01581             {
01582                 // Deferring past the main alarm time, so adjust any existing deferral
01583                 if (mReminderMinutes  ||  mDeferral == REMINDER_DEFERRAL)
01584                     set_deferral(NO_DEFERRAL);
01585             }
01586             // Remove any reminder alarm, but keep a note of it for archiving purposes
01587             if (mReminderMinutes)
01588                 set_archiveReminder();
01589         }
01590         if (mDeferral != REMINDER_DEFERRAL)
01591         {
01592             // We're deferring the main alarm, not a reminder
01593             if (mRepeatCount && mRepeatInterval  &&  dateTime < mainEndRepeatTime())
01594             {
01595                 // The alarm is repeated, and we're deferring to a time before the last repetition
01596                 set_deferral(NORMAL_DEFERRAL);
01597                 mDeferralTime = dateTime;
01598                 result = true;
01599                 setNextRepetition = true;
01600             }
01601             else
01602             {
01603                 // Main alarm has now expired
01604                 mNextMainDateTime = mDeferralTime = dateTime;
01605                 set_deferral(NORMAL_DEFERRAL);
01606                 if (!mMainExpired)
01607                 {
01608                     // Mark the alarm as expired now
01609                     mMainExpired = true;
01610                     --mAlarmCount;
01611                     if (mRepeatAtLogin)
01612                     {
01613                         // Remove the repeat-at-login alarm, but keep a note of it for archiving purposes
01614                         mArchiveRepeatAtLogin = true;
01615                         mRepeatAtLogin = false;
01616                         --mAlarmCount;
01617                     }
01618                 }
01619             }
01620         }
01621     }
01622     else if (reminder)
01623     {
01624         // Deferring a reminder for a recurring alarm
01625         if (dateTime >= mNextMainDateTime.dateTime())
01626             set_deferral(NO_DEFERRAL);    // (error)
01627         else
01628         {
01629             set_deferral(REMINDER_DEFERRAL);
01630             mDeferralTime = dateTime;
01631             checkRepetition = true;
01632         }
01633     }
01634     else
01635     {
01636         mDeferralTime = dateTime;
01637         if (mDeferral <= 0)
01638             set_deferral(NORMAL_DEFERRAL);
01639         if (adjustRecurrence)
01640         {
01641             QDateTime now = QDateTime::currentDateTime();
01642             if (mainEndRepeatTime() < now)
01643             {
01644                 // The last repetition (if any) of the current recurrence has already passed.
01645                 // Adjust to the next scheduled recurrence after now.
01646                 if (!mMainExpired  &&  setNextOccurrence(now) == NO_OCCURRENCE)
01647                 {
01648                     mMainExpired = true;
01649                     --mAlarmCount;
01650                 }
01651             }
01652             else
01653                 setNextRepetition = (mRepeatCount && mRepeatInterval);
01654         }
01655         else
01656             checkRepetition = true;
01657     }
01658     if (checkRepetition)
01659         setNextRepetition = (mRepeatCount && mRepeatInterval  &&  mDeferralTime < mainEndRepeatTime());
01660     if (setNextRepetition)
01661     {
01662         // The alarm is repeated, and we're deferring to a time before the last repetition.
01663         // Set the next scheduled repetition to the one after the deferral.
01664         mNextRepeat = (mNextMainDateTime < mDeferralTime)
01665                     ? mNextMainDateTime.secsTo(mDeferralTime) / (mRepeatInterval * 60) + 1 : 0;
01666     }
01667     mUpdated = true;
01668     return result;
01669 }
01670 
01671 /******************************************************************************
01672  * Cancel any deferral alarm.
01673  */
01674 void KAEvent::cancelDefer()
01675 {
01676     if (mDeferral > 0)
01677     {
01678         // Set the deferral time to be the same as the next recurrence/repetition.
01679         // This prevents an immediate retriggering of the alarm.
01680         if (mMainExpired
01681         ||  nextOccurrence(QDateTime::currentDateTime(), mDeferralTime, RETURN_REPETITION) == NO_OCCURRENCE)
01682         {
01683             // The main alarm has expired, so simply delete the deferral
01684             mDeferralTime = DateTime();
01685             set_deferral(NO_DEFERRAL);
01686         }
01687         else
01688             set_deferral(CANCEL_DEFERRAL);
01689         mUpdated = true;
01690     }
01691 }
01692 
01693 /******************************************************************************
01694  * Cancel any cancelled deferral alarm.
01695  */
01696 void KAEvent::cancelCancelledDeferral()
01697 {
01698     if (mDeferral == CANCEL_DEFERRAL)
01699     {
01700         mDeferralTime = DateTime();
01701         set_deferral(NO_DEFERRAL);
01702     }
01703 }
01704 
01705 /******************************************************************************
01706 *  Find the latest time which the alarm can currently be deferred to.
01707 */
01708 DateTime KAEvent::deferralLimit(KAEvent::DeferLimitType* limitType) const
01709 {
01710     DeferLimitType ltype;
01711     DateTime endTime;
01712     bool recurs = (checkRecur() != KARecurrence::NO_RECUR);
01713     if (recurs  ||  mRepeatCount)
01714     {
01715         // It's a repeated alarm. Don't allow it to be deferred past its
01716         // next occurrence or repetition.
01717         DateTime reminderTime;
01718         QDateTime now = QDateTime::currentDateTime();
01719         OccurType type = nextOccurrence(now, endTime, RETURN_REPETITION);
01720         if (type & OCCURRENCE_REPEAT)
01721             ltype = LIMIT_REPETITION;
01722         else if (type == NO_OCCURRENCE)
01723             ltype = LIMIT_NONE;
01724         else if (mReminderMinutes  &&  (now < (reminderTime = endTime.addMins(-mReminderMinutes))))
01725         {
01726             endTime = reminderTime;
01727             ltype = LIMIT_REMINDER;
01728         }
01729         else if (type == FIRST_OR_ONLY_OCCURRENCE  &&  !recurs)
01730             ltype = LIMIT_REPETITION;
01731         else
01732             ltype = LIMIT_RECURRENCE;
01733     }
01734     else if ((mReminderMinutes  ||  mDeferral == REMINDER_DEFERRAL  ||  mArchiveReminderMinutes)
01735          &&  QDateTime::currentDateTime() < mNextMainDateTime.dateTime())
01736     {
01737         // It's an reminder alarm. Don't allow it to be deferred past its main alarm time.
01738         endTime = mNextMainDateTime;
01739         ltype = LIMIT_REMINDER;
01740     }
01741     else
01742         ltype = LIMIT_NONE;
01743     if (ltype != LIMIT_NONE)
01744         endTime = endTime.addMins(-1);
01745     if (limitType)
01746         *limitType = ltype;
01747     return endTime;
01748 }
01749 
01750 /******************************************************************************
01751  * Set the event to be a copy of the specified event, making the specified
01752  * alarm the 'displaying' alarm.
01753  * The purpose of setting up a 'displaying' alarm is to be able to reinstate
01754  * the alarm message in case of a crash, or to reinstate it should the user
01755  * choose to defer the alarm. Note that even repeat-at-login alarms need to be
01756  * saved in case their end time expires before the next login.
01757  * Reply = true if successful, false if alarm was not copied.
01758  */
01759 bool KAEvent::setDisplaying(const KAEvent& event, KAAlarm::Type alarmType, const QDateTime& repeatAtLoginTime)
01760 {
01761     if (!mDisplaying
01762     &&  (alarmType == KAAlarm::MAIN_ALARM
01763       || alarmType == KAAlarm::REMINDER_ALARM
01764       || alarmType == KAAlarm::DEFERRED_REMINDER_ALARM
01765       || alarmType == KAAlarm::DEFERRED_ALARM
01766       || alarmType == KAAlarm::AT_LOGIN_ALARM))
01767     {
01768 //kdDebug(5950)<<"KAEvent::setDisplaying("<<event.id()<<", "<<(alarmType==KAAlarm::MAIN_ALARM?"MAIN":alarmType==KAAlarm::REMINDER_ALARM?"REMINDER":alarmType==KAAlarm::DEFERRED_REMINDER_ALARM?"REMINDER_DEFERRAL":alarmType==KAAlarm::DEFERRED_ALARM?"DEFERRAL":"LOGIN")<<"): time="<<repeatAtLoginTime.toString()<<endl;
01769         KAAlarm al = event.alarm(alarmType);
01770         if (al.valid())
01771         {
01772             *this = event;
01773             setUid(DISPLAYING);
01774             mDisplaying     = true;
01775             mDisplayingTime = (alarmType == KAAlarm::AT_LOGIN_ALARM) ? repeatAtLoginTime : al.dateTime();
01776             switch (al.type())
01777             {
01778                 case KAAlarm::AT_LOGIN__ALARM:                mDisplayingFlags = REPEAT_AT_LOGIN;  break;
01779                 case KAAlarm::REMINDER__ALARM:                mDisplayingFlags = REMINDER;  break;
01780                 case KAAlarm::DEFERRED_REMINDER_TIME__ALARM:  mDisplayingFlags = REMINDER | TIME_DEFERRAL;  break;
01781                 case KAAlarm::DEFERRED_REMINDER_DATE__ALARM:  mDisplayingFlags = REMINDER | DATE_DEFERRAL;  break;
01782                 case KAAlarm::DEFERRED_TIME__ALARM:           mDisplayingFlags = TIME_DEFERRAL;  break;
01783                 case KAAlarm::DEFERRED_DATE__ALARM:           mDisplayingFlags = DATE_DEFERRAL;  break;
01784                 default:                                      mDisplayingFlags = 0;  break;
01785             }
01786             ++mAlarmCount;
01787             mUpdated = true;
01788             return true;
01789         }
01790     }
01791     return false;
01792 }
01793 
01794 /******************************************************************************
01795  * Return the original alarm which the displaying alarm refers to.
01796  */
01797 KAAlarm KAEvent::convertDisplayingAlarm() const
01798 {
01799     KAAlarm al;
01800     if (mDisplaying)
01801     {
01802         al = alarm(KAAlarm::DISPLAYING_ALARM);
01803         if (mDisplayingFlags & REPEAT_AT_LOGIN)
01804         {
01805             al.mRepeatAtLogin = true;
01806             al.mType = KAAlarm::AT_LOGIN__ALARM;
01807         }
01808         else if (mDisplayingFlags & DEFERRAL)
01809         {
01810             al.mDeferred = true;
01811             al.mType = (mDisplayingFlags == (REMINDER | DATE_DEFERRAL)) ? KAAlarm::DEFERRED_REMINDER_DATE__ALARM
01812                      : (mDisplayingFlags == (REMINDER | TIME_DEFERRAL)) ? KAAlarm::DEFERRED_REMINDER_TIME__ALARM
01813                      : (mDisplayingFlags == DATE_DEFERRAL) ? KAAlarm::DEFERRED_DATE__ALARM
01814                      : KAAlarm::DEFERRED_TIME__ALARM;
01815         }
01816         else if (mDisplayingFlags & REMINDER)
01817             al.mType = KAAlarm::REMINDER__ALARM;
01818         else
01819             al.mType = KAAlarm::MAIN__ALARM;
01820     }
01821     return al;
01822 }
01823 
01824 /******************************************************************************
01825  * Reinstate the original event from the 'displaying' event.
01826  */
01827 void KAEvent::reinstateFromDisplaying(const KAEvent& dispEvent)
01828 {
01829     if (dispEvent.mDisplaying)
01830     {
01831         *this = dispEvent;
01832         setUid(ACTIVE);
01833         mDisplaying = false;
01834         --mAlarmCount;
01835         mUpdated = true;
01836     }
01837 }
01838 
01839 /******************************************************************************
01840  * Determine whether the event will occur after the specified date/time.
01841  * If 'includeRepetitions' is true and the alarm has a sub-repetition, it
01842  * returns true if any repetitions occur after the specified date/time.
01843  */
01844 bool KAEvent::occursAfter(const QDateTime& preDateTime, bool includeRepetitions) const
01845 {
01846     QDateTime dt;
01847     if (checkRecur() != KARecurrence::NO_RECUR)
01848     {
01849         if (mRecurrence->duration() < 0)
01850             return true;    // infinite recurrence
01851         dt = mRecurrence->endDateTime();
01852     }
01853     else
01854         dt = mNextMainDateTime.dateTime();
01855     if (mStartDateTime.isDateOnly())
01856     {
01857         QDate pre = preDateTime.date();
01858         if (preDateTime.time() < Preferences::startOfDay())
01859             pre = pre.addDays(-1);    // today's recurrence (if today recurs) is still to come
01860         if (pre < dt.date())
01861             return true;
01862     }
01863     else if (preDateTime < dt)
01864         return true;
01865 
01866     if (includeRepetitions  &&  mRepeatCount)
01867     {
01868         if (preDateTime < dt.addSecs(mRepeatCount * mRepeatInterval * 60))
01869             return true;
01870     }
01871     return false;
01872 }
01873 
01874 /******************************************************************************
01875  * Get the date/time of the next occurrence of the event, after the specified
01876  * date/time.
01877  * 'result' = date/time of next occurrence, or invalid date/time if none.
01878  */
01879 KAEvent::OccurType KAEvent::nextOccurrence(const QDateTime& preDateTime, DateTime& result,
01880                                            KAEvent::OccurOption includeRepetitions) const
01881 {
01882     int repeatSecs = 0;
01883     QDateTime pre = preDateTime;
01884     if (includeRepetitions != IGNORE_REPETITION)
01885     {
01886         if (!mRepeatCount  ||  !mRepeatInterval)
01887             includeRepetitions = IGNORE_REPETITION;
01888         else
01889         {
01890             repeatSecs = mRepeatInterval * 60;
01891             pre = preDateTime.addSecs(-mRepeatCount * repeatSecs);
01892         }
01893     }
01894 
01895     OccurType type;
01896     bool recurs = (checkRecur() != KARecurrence::NO_RECUR);
01897     if (recurs)
01898         type = nextRecurrence(pre, result);
01899     else if (pre < mNextMainDateTime.dateTime())
01900     {
01901         result = mNextMainDateTime;
01902         type = FIRST_OR_ONLY_OCCURRENCE;
01903     }
01904     else
01905     {
01906         result = DateTime();
01907         type = NO_OCCURRENCE;
01908     }
01909 
01910     if (type != NO_OCCURRENCE  &&  result <= preDateTime  &&  includeRepetitions != IGNORE_REPETITION)
01911     {
01912         // The next occurrence is a sub-repetition
01913         int repetition = result.secsTo(preDateTime) / repeatSecs + 1;
01914         DateTime repeatDT = result.addSecs(repetition * repeatSecs);
01915         if (recurs)
01916         {
01917             // We've found a recurrence before the specified date/time, which has
01918             // a sub-repetition after the date/time.
01919             // However, if the intervals between recurrences vary, we could possibly
01920             // have missed a later recurrence, which fits the criterion, so check again.
01921             DateTime dt;
01922             OccurType newType = previousOccurrence(repeatDT.dateTime(), dt, false);
01923             if (dt > result)
01924             {
01925                 type = newType;
01926                 result = dt;
01927                 if (includeRepetitions == RETURN_REPETITION  &&  result <= preDateTime)
01928                 {
01929                     // The next occurrence is a sub-repetition
01930                     int repetition = result.secsTo(preDateTime) / repeatSecs + 1;
01931                     result = result.addSecs(repetition * repeatSecs);
01932                     type = static_cast<OccurType>(type | OCCURRENCE_REPEAT);
01933                 }
01934                 return type;
01935             }
01936         }
01937         if (includeRepetitions == RETURN_REPETITION)
01938         {
01939             // The next occurrence is a sub-repetition
01940             result = repeatDT;
01941             type = static_cast<OccurType>(type | OCCURRENCE_REPEAT);
01942         }
01943     }
01944     return type;
01945 }
01946 
01947 /******************************************************************************
01948  * Get the date/time of the last previous occurrence of the event, before the
01949  * specified date/time.
01950  * If 'includeRepetitions' is true and the alarm has a sub-repetition, the
01951  * last previous repetition is returned if appropriate.
01952  * 'result' = date/time of previous occurrence, or invalid date/time if none.
01953  */
01954 KAEvent::OccurType KAEvent::previousOccurrence(const QDateTime& afterDateTime, DateTime& result, bool includeRepetitions) const
01955 {
01956     if (mStartDateTime >= afterDateTime)
01957     {
01958         result = QDateTime();
01959         return NO_OCCURRENCE;     // the event starts after the specified date/time
01960     }
01961 
01962     // Find the latest recurrence of the event
01963     OccurType type;
01964     if (checkRecur() == KARecurrence::NO_RECUR)
01965     {
01966         result = mStartDateTime;
01967         type = FIRST_OR_ONLY_OCCURRENCE;
01968     }
01969     else
01970     {
01971         QDateTime recurStart = mRecurrence->startDateTime();
01972         QDateTime after = afterDateTime;
01973         if (mStartDateTime.isDateOnly()  &&  afterDateTime.time() > Preferences::startOfDay())
01974             after = after.addDays(1);    // today's recurrence (if today recurs) has passed
01975         QDateTime dt = mRecurrence->getPreviousDateTime(after);
01976         result.set(dt, mStartDateTime.isDateOnly());
01977         if (!dt.isValid())
01978             return NO_OCCURRENCE;
01979         if (dt == recurStart)
01980             type = FIRST_OR_ONLY_OCCURRENCE;
01981         else if (mRecurrence->getNextDateTime(dt).isValid())
01982             type = result.isDateOnly() ? RECURRENCE_DATE : RECURRENCE_DATE_TIME;
01983         else
01984             type = LAST_RECURRENCE;
01985     }
01986 
01987     if (includeRepetitions  &&  mRepeatCount)
01988     {
01989         // Find the latest repetition which is before the specified time.
01990         // N.B. This is coded to avoid 32-bit integer overflow which occurs
01991         //      in QDateTime::secsTo() for large enough time differences.
01992         int repeatSecs = mRepeatInterval * 60;
01993         DateTime lastRepetition = result.addSecs(mRepeatCount * repeatSecs);
01994         if (lastRepetition < afterDateTime)
01995         {
01996             result = lastRepetition;
01997             return static_cast<OccurType>(type | OCCURRENCE_REPEAT);
01998         }
01999         int repetition = (result.dateTime().secsTo(afterDateTime) - 1) / repeatSecs;
02000         if (repetition > 0)
02001         {
02002             result = result.addSecs(repetition * repeatSecs);
02003             return static_cast<OccurType>(type | OCCURRENCE_REPEAT);
02004         }
02005     }
02006     return type;
02007 }
02008 
02009 /******************************************************************************
02010  * Set the date/time of the event to the next scheduled occurrence after the
02011  * specified date/time, provided that this is later than its current date/time.
02012  * Any reminder alarm is adjusted accordingly.
02013  * If the alarm has a sub-repetition, and a repetition of a previous
02014  * recurrence occurs after the specified date/time, that repetition is set as
02015  * the next occurrence.
02016  */
02017 KAEvent::OccurType KAEvent::setNextOccurrence(const QDateTime& preDateTime)
02018 {
02019     if (preDateTime < mNextMainDateTime.dateTime())
02020         return FIRST_OR_ONLY_OCCURRENCE;    // it might not be the first recurrence - tant pis
02021     QDateTime pre = preDateTime;
02022     // If there are repetitions, adjust the comparison date/time so that
02023     // we find the earliest recurrence which has a repetition falling after
02024     // the specified preDateTime.
02025     if (mRepeatCount  &&  mRepeatInterval)
02026         pre = preDateTime.addSecs(-mRepeatCount * mRepeatInterval * 60);
02027 
02028     DateTime dt;
02029     OccurType type;
02030     if (pre < mNextMainDateTime.dateTime())
02031     {
02032         dt = mNextMainDateTime;
02033         type = FIRST_OR_ONLY_OCCURRENCE;   // may not actually be the first occurrence
02034     }
02035     else if (checkRecur() != KARecurrence::NO_RECUR)
02036     {
02037         type = nextRecurrence(pre, dt);
02038         if (type == NO_OCCURRENCE)
02039             return NO_OCCURRENCE;
02040         if (type != FIRST_OR_ONLY_OCCURRENCE  &&  dt != mNextMainDateTime)
02041         {
02042             // Need to reschedule the next trigger date/time
02043             mNextMainDateTime = dt;
02044             // Reinstate the reminder (if any) for the rescheduled recurrence
02045             if (mDeferral == REMINDER_DEFERRAL  ||  mArchiveReminderMinutes)
02046             {
02047                 if (mReminderOnceOnly)
02048                 {
02049                     if (mReminderMinutes)
02050                         set_archiveReminder();
02051                 }
02052                 else
02053                     set_reminder(mArchiveReminderMinutes);
02054             }
02055             if (mDeferral == REMINDER_DEFERRAL)
02056                 set_deferral(NO_DEFERRAL);
02057             mUpdated = true;
02058         }
02059     }
02060     else
02061         return NO_OCCURRENCE;
02062 
02063     if (mRepeatCount  &&  mRepeatInterval)
02064     {
02065         int secs = dt.dateTime().secsTo(preDateTime);
02066         if (secs >= 0)
02067         {
02068             // The next occurrence is a sub-repetition.
02069             type = static_cast<OccurType>(type | OCCURRENCE_REPEAT);
02070             mNextRepeat = (secs / (60 * mRepeatInterval)) + 1;
02071             // Repetitions can't have a reminder, so remove any.
02072             if (mReminderMinutes)
02073                 set_archiveReminder();
02074             if (mDeferral == REMINDER_DEFERRAL)
02075                 set_deferral(NO_DEFERRAL);
02076             mUpdated = true;
02077         }
02078         else if (mNextRepeat)
02079         {
02080             // The next occurrence is the main occurrence, not a repetition
02081             mNextRepeat = 0;
02082             mUpdated = true;
02083         }
02084     }
02085     return type;
02086 }
02087 
02088 /******************************************************************************
02089  * Get the date/time of the next recurrence of the event, after the specified
02090  * date/time.
02091  * 'result' = date/time of next occurrence, or invalid date/time if none.
02092  */
02093 KAEvent::OccurType KAEvent::nextRecurrence(const QDateTime& preDateTime, DateTime& result) const
02094 {
02095     QDateTime recurStart = mRecurrence->startDateTime();
02096     QDateTime pre = preDateTime;
02097     if (mStartDateTime.isDateOnly()  &&  preDateTime.time() < Preferences::startOfDay())
02098     {
02099         pre = pre.addDays(-1);    // today's recurrence (if today recurs) is still to come
02100         pre.setTime(Preferences::startOfDay());
02101     }
02102     QDateTime dt = mRecurrence->getNextDateTime(pre);
02103     result.set(dt, mStartDateTime.isDateOnly());
02104     if (!dt.isValid())
02105         return NO_OCCURRENCE;
02106     if (dt == recurStart)
02107         return FIRST_OR_ONLY_OCCURRENCE;
02108     if (mRecurrence->duration() >= 0  &&  dt == mRecurrence->endDateTime())
02109         return LAST_RECURRENCE;
02110     return result.isDateOnly() ? RECURRENCE_DATE : RECURRENCE_DATE_TIME;
02111 }
02112 
02113 /******************************************************************************
02114  * Return the recurrence interval as text suitable for display.
02115  */
02116 QString KAEvent::recurrenceText(bool brief) const
02117 {
02118     if (mRepeatAtLogin)
02119         return brief ? i18n("Brief form of 'At Login'", "Login") : i18n("At login");
02120     if (mRecurrence)
02121     {
02122         int frequency = mRecurrence->frequency();
02123         switch (mRecurrence->defaultRRuleConst()->recurrenceType())
02124         {
02125             case RecurrenceRule::rMinutely:
02126                 if (frequency < 60)
02127                     return i18n("1 Minute", "%n Minutes", frequency);
02128                 else if (frequency % 60 == 0)
02129                     return i18n("1 Hour", "%n Hours", frequency/60);
02130                 else
02131                 {
02132                     QString mins;
02133                     return i18n("Hours and Minutes", "%1H %2M").arg(QString::number(frequency/60)).arg(mins.sprintf("%02d", frequency%60));
02134                 }
02135             case RecurrenceRule::rDaily:
02136                 return i18n("1 Day", "%n Days", frequency);
02137             case RecurrenceRule::rWeekly:
02138                 return i18n("1 Week", "%n Weeks", frequency);
02139             case RecurrenceRule::rMonthly:
02140                 return i18n("1 Month", "%n Months", frequency);
02141             case RecurrenceRule::rYearly:
02142                 return i18n("1 Year", "%n Years", frequency);
02143             case RecurrenceRule::rNone:
02144             default:
02145                 break;
02146         }
02147     }
02148     return brief ? QString::null : i18n("None");
02149 }
02150 
02151 /******************************************************************************
02152  * Return the repetition interval as text suitable for display.
02153  */
02154 QString KAEvent::repetitionText(bool brief) const
02155 {
02156     if (mRepeatCount)
02157     {
02158         if (mRepeatInterval % 1440)
02159         {
02160             if (mRepeatInterval < 60)
02161                 return i18n("1 Minute", "%n Minutes", mRepeatInterval);
02162             if (mRepeatInterval % 60 == 0)
02163                 return i18n("1 Hour", "%n Hours", mRepeatInterval/60);
02164             QString mins;
02165             return i18n("Hours and Minutes", "%1H %2M").arg(QString::number(mRepeatInterval/60)).arg(mins.sprintf("%02d", mRepeatInterval%60));
02166         }
02167         if (mRepeatInterval % (7*1440))
02168             return i18n("1 Day", "%n Days", mRepeatInterval/1440);
02169         return i18n("1 Week", "%n Weeks", mRepeatInterval/(7*1440));
02170     }
02171     return brief ? QString::null : i18n("None");
02172 }
02173 
02174 /******************************************************************************
02175  * Adjust the event date/time to the first recurrence of the event, on or after
02176  * start date/time. The event start date may not be a recurrence date, in which
02177  * case a later date will be set.
02178  */
02179 void KAEvent::setFirstRecurrence()
02180 {
02181     switch (checkRecur())
02182     {
02183         case KARecurrence::NO_RECUR:
02184         case KARecurrence::MINUTELY:
02185             return;
02186         case KARecurrence::ANNUAL_DATE:
02187         case KARecurrence::ANNUAL_POS:
02188             if (mRecurrence->yearMonths().isEmpty())
02189                 return;    // (presumably it's a template)
02190             break;
02191         case KARecurrence::DAILY:
02192         case KARecurrence::WEEKLY:
02193         case KARecurrence::MONTHLY_POS:
02194         case KARecurrence::MONTHLY_DAY:
02195             break;
02196     }
02197     QDateTime recurStart = mRecurrence->startDateTime();
02198     if (mRecurrence->recursOn(recurStart.date()))
02199         return;           // it already recurs on the start date
02200 
02201     // Set the frequency to 1 to find the first possible occurrence
02202     int frequency = mRecurrence->frequency();
02203     mRecurrence->setFrequency(1);
02204     DateTime next;
02205     nextRecurrence(mNextMainDateTime.dateTime(), next);
02206     if (!next.isValid())
02207         mRecurrence->setStartDateTime(recurStart);   // reinstate the old value
02208     else
02209     {
02210         mRecurrence->setStartDateTime(next.dateTime());
02211         mStartDateTime = mNextMainDateTime = next;
02212         mUpdated = true;
02213     }
02214     mRecurrence->setFrequency(frequency);    // restore the frequency
02215 }
02216 
02217 /******************************************************************************
02218 *  Initialise the event's recurrence from a KCal::Recurrence.
02219 *  The event's start date/time is not changed.
02220 */
02221 void KAEvent::setRecurrence(const KARecurrence& recurrence)
02222 {
02223     mUpdated = true;
02224     delete mRecurrence;
02225     if (recurrence.doesRecur())
02226     {
02227         mRecurrence = new KARecurrence(recurrence);
02228         mRecurrence->setStartDateTime(mStartDateTime.dateTime());
02229         mRecurrence->setFloats(mStartDateTime.isDateOnly());
02230     }
02231     else
02232         mRecurrence = 0;
02233 
02234     // Adjust sub-repetition values to fit the recurrence
02235     setRepetition(mRepeatInterval, mRepeatCount);
02236 }
02237 
02238 /******************************************************************************
02239 *  Initialise the event's sub-repetition.
02240 *  The repetition length is adjusted if necessary to fit any recurrence interval.
02241 *  Reply = false if a non-daily interval was specified for a date-only recurrence.
02242 */
02243 bool KAEvent::setRepetition(int interval, int count)
02244 {
02245     mUpdated        = true;
02246     mRepeatInterval = 0;
02247     mRepeatCount    = 0;
02248     mNextRepeat     = 0;
02249     if (interval > 0  &&  count > 0  &&  !mRepeatAtLogin)
02250     {
02251         Q_ASSERT(checkRecur() != KARecurrence::NO_RECUR);
02252         if (interval % 1440  &&  mStartDateTime.isDateOnly())
02253             return false;    // interval must be in units of days for date-only alarms
02254         if (checkRecur() != KARecurrence::NO_RECUR)
02255         {
02256             int longestInterval = mRecurrence->longestInterval() - 1;
02257             if (interval * count > longestInterval)
02258                 count = longestInterval / interval;
02259         }
02260         mRepeatInterval = interval;
02261         mRepeatCount    = count;
02262     }
02263     return true;
02264 }
02265 
02266 /******************************************************************************
02267  * Set the recurrence to recur at a minutes interval.
02268  * Parameters:
02269  *    freq  = how many minutes between recurrences.
02270  *    count = number of occurrences, including first and last.
02271  *          = -1 to recur indefinitely.
02272  *          = 0 to use 'end' instead.
02273  *    end   = end date/time (invalid to use 'count' instead).
02274  * Reply = false if no recurrence was set up.
02275  */
02276 bool KAEvent::setRecurMinutely(int freq, int count, const QDateTime& end)
02277 {
02278     return setRecur(RecurrenceRule::rMinutely, freq, count, end);
02279 }
02280 
02281 /******************************************************************************
02282  * Set the recurrence to recur daily.
02283  * Parameters:
02284  *    freq  = how many days between recurrences.
02285  *    days  = which days of the week alarms are allowed to occur on.
02286  *    count = number of occurrences, including first and last.
02287  *          = -1 to recur indefinitely.
02288  *          = 0 to use 'end' instead.
02289  *    end   = end date (invalid to use 'count' instead).
02290  * Reply = false if no recurrence was set up.
02291  */
02292 bool KAEvent::setRecurDaily(int freq, const QBitArray& days, int count, const QDate& end)
02293 {
02294     if (!setRecur(RecurrenceRule::rDaily, freq, count, end))
02295         return false;
02296     int n = 0;
02297     for (int i = 0;  i < 7;  ++i)
02298     {
02299         if (days.testBit(i))
02300             ++n;
02301     }
02302     if (n < 7)
02303         mRecurrence->addWeeklyDays(days);
02304     return true;
02305 }
02306 
02307 /******************************************************************************
02308  * Set the recurrence to recur weekly, on the specified weekdays.
02309  * Parameters:
02310  *    freq  = how many weeks between recurrences.
02311  *    days  = which days of the week alarms should occur on.
02312  *    count = number of occurrences, including first and last.
02313  *          = -1 to recur indefinitely.
02314  *          = 0 to use 'end' instead.
02315  *    end   = end date (invalid to use 'count' instead).
02316  * Reply = false if no recurrence was set up.
02317  */
02318 bool KAEvent::setRecurWeekly(int freq, const QBitArray& days, int count, const QDate& end)
02319 {
02320     if (!setRecur(RecurrenceRule::rWeekly, freq, count, end))
02321         return false;
02322     mRecurrence->addWeeklyDays(days);
02323     return true;
02324 }
02325 
02326 /******************************************************************************
02327  * Set the recurrence to recur monthly, on the specified days within the month.
02328  * Parameters:
02329  *    freq  = how many months between recurrences.
02330  *    days  = which days of the month alarms should occur on.
02331  *    count = number of occurrences, including first and last.
02332  *          = -1 to recur indefinitely.
02333  *          = 0 to use 'end' instead.
02334  *    end   = end date (invalid to use 'count' instead).
02335  * Reply = false if no recurrence was set up.
02336  */
02337 bool KAEvent::setRecurMonthlyByDate(int freq, const QValueList<int>& days, int count, const QDate& end)
02338 {
02339     if (!setRecur(RecurrenceRule::rMonthly, freq, count, end))
02340         return false;
02341     for (QValueListConstIterator<int> it = days.begin();  it != days.end();  ++it)
02342         mRecurrence->addMonthlyDate(*it);
02343     return true;
02344 }
02345 
02346 /******************************************************************************
02347  * Set the recurrence to recur monthly, on the specified weekdays in the
02348  * specified weeks of the month.
02349  * Parameters:
02350  *    freq  = how many months between recurrences.
02351  *    posns = which days of the week/weeks of the month alarms should occur on.
02352  *    count = number of occurrences, including first and last.
02353  *          = -1 to recur indefinitely.
02354  *          = 0 to use 'end' instead.
02355  *    end   = end date (invalid to use 'count' instead).
02356  * Reply = false if no recurrence was set up.
02357  */
02358 bool KAEvent::setRecurMonthlyByPos(int freq, const QValueList<MonthPos>& posns, int count, const QDate& end)
02359 {
02360     if (!setRecur(RecurrenceRule::rMonthly, freq, count, end))
02361         return false;
02362     for (QValueListConstIterator<MonthPos> it = posns.begin();  it != posns.end();  ++it)
02363         mRecurrence->addMonthlyPos((*it).weeknum, (*it).days);
02364     return true;
02365 }
02366 
02367 /******************************************************************************
02368  * Set the recurrence to recur annually, on the specified start date in each
02369  * of the specified months.
02370  * Parameters:
02371  *    freq   = how many years between recurrences.
02372  *    months = which months of the year alarms should occur on.
02373  *    day    = day of month, or 0 to use start date
02374  *    feb29  = when February 29th should recur in non-leap years.
02375  *    count  = number of occurrences, including first and last.
02376  *           = -1 to recur indefinitely.
02377  *           = 0 to use 'end' instead.
02378  *    end    = end date (invalid to use 'count' instead).
02379  * Reply = false if no recurrence was set up.
02380  */
02381 bool KAEvent::setRecurAnnualByDate(int freq, const QValueList<int>& months, int day, KARecurrence::Feb29Type feb29, int count, const QDate& end)
02382 {
02383     if (!setRecur(RecurrenceRule::rYearly, freq, count, end, feb29))
02384         return false;
02385     for (QValueListConstIterator<int> it = months.begin();  it != months.end();  ++it)
02386         mRecurrence->addYearlyMonth(*it);
02387     if (day)
02388         mRecurrence->addMonthlyDate(day);
02389     return true;
02390 }
02391 
02392 /******************************************************************************
02393  * Set the recurrence to recur annually, on the specified weekdays in the
02394  * specified weeks of the specified months.
02395  * Parameters:
02396  *    freq   = how many years between recurrences.
02397  *    posns  = which days of the week/weeks of the month alarms should occur on.
02398  *    months = which months of the year alarms should occur on.
02399  *    count  = number of occurrences, including first and last.
02400  *           = -1 to recur indefinitely.
02401  *           = 0 to use 'end' instead.
02402  *    end    = end date (invalid to use 'count' instead).
02403  * Reply = false if no recurrence was set up.
02404  */
02405 bool KAEvent::setRecurAnnualByPos(int freq, const QValueList<MonthPos>& posns, const QValueList<int>& months, int count, const QDate& end)
02406 {
02407     if (!setRecur(RecurrenceRule::rYearly, freq, count, end))
02408         return false;
02409     for (QValueListConstIterator<int> it = months.begin();  it != months.end();  ++it)
02410         mRecurrence->addYearlyMonth(*it);
02411     for (QValueListConstIterator<MonthPos> it = posns.begin();  it != posns.end();  ++it)
02412         mRecurrence->addYearlyPos((*it).weeknum, (*it).days);
02413     return true;
02414 }
02415 
02416 /******************************************************************************
02417  * Initialise the event's recurrence data.
02418  * Parameters:
02419  *    freq  = how many intervals between recurrences.
02420  *    count = number of occurrences, including first and last.
02421  *          = -1 to recur indefinitely.
02422  *          = 0 to use 'end' instead.
02423  *    end   = end date/time (invalid to use 'count' instead).
02424  * Reply = false if no recurrence was set up.
02425  */
02426 bool KAEvent::setRecur(RecurrenceRule::PeriodType recurType, int freq, int count, const QDateTime& end, KARecurrence::Feb29Type feb29)
02427 {
02428     if (count >= -1  &&  (count || end.date().isValid()))
02429     {
02430         if (!mRecurrence)
02431             mRecurrence = new KARecurrence;
02432         if (mRecurrence->init(recurType, freq, count, mNextMainDateTime, end, feb29))
02433         {
02434             mUpdated = true;
02435             return true;
02436         }
02437     }
02438     clearRecur();
02439     return false;
02440 }
02441 
02442 /******************************************************************************
02443  * Clear the event's recurrence and alarm repetition data.
02444  */
02445 void KAEvent::clearRecur()
02446 {
02447     delete mRecurrence;
02448     mRecurrence     = 0;
02449     mRepeatInterval = 0;
02450     mRepeatCount    = 0;
02451     mNextRepeat     = 0;
02452     mUpdated        = true;
02453 }
02454 
02455 /******************************************************************************
02456 * Validate the event's recurrence data, correcting any inconsistencies (which
02457 * should never occur!).
02458 * Reply = true if a recurrence (as opposed to a login repetition) exists.
02459 */
02460 KARecurrence::Type KAEvent::checkRecur() const
02461 {
02462     if (mRecurrence)
02463     {
02464         KARecurrence::Type type = mRecurrence->type();
02465         switch (type)
02466         {
02467             case KARecurrence::MINUTELY:     // hourly      
02468             case KARecurrence::DAILY:        // daily
02469             case KARecurrence::WEEKLY:       // weekly on multiple days of week
02470             case KARecurrence::MONTHLY_DAY:  // monthly on multiple dates in month
02471             case KARecurrence::MONTHLY_POS:  // monthly on multiple nth day of week
02472             case KARecurrence::ANNUAL_DATE:  // annually on multiple months (day of month = start date)
02473             case KARecurrence::ANNUAL_POS:   // annually on multiple nth day of week in multiple months
02474                 return type;
02475             default:
02476                 if (mRecurrence)
02477                     const_cast<KAEvent*>(this)->clearRecur();  // recurrence shouldn't exist!!
02478                 break;
02479         }
02480     }
02481     return KARecurrence::NO_RECUR;
02482 }
02483 
02484 
02485 /******************************************************************************
02486  * Return the recurrence interval in units of the recurrence period type.
02487  */
02488 int KAEvent::recurInterval() const
02489 {
02490     if (mRecurrence)
02491     {
02492         switch (mRecurrence->type())
02493         {
02494             case KARecurrence::MINUTELY:
02495             case KARecurrence::DAILY:
02496             case KARecurrence::WEEKLY:
02497             case KARecurrence::MONTHLY_DAY:
02498             case KARecurrence::MONTHLY_POS:
02499             case KARecurrence::ANNUAL_DATE:
02500             case KARecurrence::ANNUAL_POS:
02501                 return mRecurrence->frequency();
02502             default:
02503                 break;
02504         }
02505     }
02506     return 0;
02507 }
02508 
02509 /******************************************************************************
02510 * Validate the event's alarm sub-repetition data, correcting any
02511 * inconsistencies (which should never occur!).
02512 */
02513 void KAEvent::checkRepetition() const
02514 {
02515     if (mRepeatCount  &&  !mRepeatInterval)
02516         const_cast<KAEvent*>(this)->mRepeatCount = 0;
02517     if (!mRepeatCount  &&  mRepeatInterval)
02518         const_cast<KAEvent*>(this)->mRepeatInterval = 0;
02519 }
02520 
02521 #if 0
02522 /******************************************************************************
02523  * Convert a QValueList<WDayPos> to QValueList<MonthPos>.
02524  */
02525 QValueList<KAEvent::MonthPos> KAEvent::convRecurPos(const QValueList<KCal::RecurrenceRule::WDayPos>& wdaypos)
02526 {
02527     QValueList<MonthPos> mposns;
02528     for (QValueList<KCal::RecurrenceRule::WDayPos>::ConstIterator it = wdaypos.begin();  it != wdaypos.end();  ++it)
02529     {
02530         int daybit  = (*it).day() - 1;
02531         int weeknum = (*it).pos();
02532         bool found = false;
02533         for (QValueList<MonthPos>::Iterator mit = mposns.begin();  mit != mposns.end();  ++mit)
02534         {
02535             if ((*mit).weeknum == weeknum)
02536             {
02537                 (*mit).days.setBit(daybit);
02538                 found = true;
02539                 break;
02540             }
02541         }
02542         if (!found)
02543         {
02544             MonthPos mpos;
02545             mpos.days.fill(false);
02546             mpos.days.setBit(daybit);
02547             mpos.weeknum = weeknum;
02548             mposns.append(mpos);
02549         }
02550     }
02551     return mposns;
02552 }
02553 #endif
02554 
02555 /******************************************************************************
02556  * Find the alarm template with the specified name.
02557  * Reply = invalid event if not found.
02558  */
02559 KAEvent KAEvent::findTemplateName(AlarmCalendar& calendar, const QString& name)
02560 {
02561     KAEvent event;
02562     Event::List events = calendar.events();
02563     for (Event::List::ConstIterator evit = events.begin();  evit != events.end();  ++evit)
02564     {
02565         Event* ev = *evit;
02566         if (ev->summary() == name)
02567         {
02568             event.set(*ev);
02569             if (!event.isTemplate())
02570                 return KAEvent();    // this shouldn't ever happen
02571             break;
02572         }
02573     }
02574     return event;
02575 }
02576 
02577 /******************************************************************************
02578  * Adjust the time at which date-only events will occur for each of the events
02579  * in a list. Events for which both date and time are specified are left
02580  * unchanged.
02581  * Reply = true if any events have been updated.
02582  */
02583 bool KAEvent::adjustStartOfDay(const Event::List& events)
02584 {
02585     bool changed = false;
02586     QTime startOfDay = Preferences::startOfDay();
02587     for (Event::List::ConstIterator evit = events.begin();  evit != events.end();  ++evit)
02588     {
02589         Event* event = *evit;
02590         const QStringList cats = event->categories();
02591         if (cats.find(DATE_ONLY_CATEGORY) != cats.end())
02592         {
02593             // It's an untimed event, so fix it
02594             QTime oldTime = event->dtStart().time();
02595             int adjustment = oldTime.secsTo(startOfDay);
02596             if (adjustment)
02597             {
02598                 event->setDtStart(QDateTime(event->dtStart().date(), startOfDay));
02599                 Alarm::List alarms = event->alarms();
02600                 int deferralOffset = 0;
02601                 for (Alarm::List::ConstIterator alit = alarms.begin();  alit != alarms.end();  ++alit)
02602                 {
02603                     // Parse the next alarm's text
02604                     Alarm& alarm = **alit;
02605                     AlarmData data;
02606                     readAlarm(alarm, data);
02607                     if (data.type & KAAlarm::TIMED_DEFERRAL_FLAG)
02608                     {
02609                         // Timed deferral alarm, so adjust the offset
02610                         deferralOffset = alarm.startOffset().asSeconds();
02611                         alarm.setStartOffset(deferralOffset - adjustment);
02612                     }
02613                     else if (data.type == KAAlarm::AUDIO__ALARM
02614                     &&       alarm.startOffset().asSeconds() == deferralOffset)
02615                     {
02616                         // Audio alarm is set for the same time as the deferral alarm
02617                         alarm.setStartOffset(deferralOffset - adjustment);
02618                     }
02619                 }
02620                 changed = true;
02621             }
02622         }
02623         else
02624         {
02625             // It's a timed event. Fix any untimed alarms.
02626             int deferralOffset = 0;
02627             int newDeferralOffset = 0;
02628             DateTime start;
02629             QDateTime nextMainDateTime = readDateTime(*event, false, start).rawDateTime();
02630             AlarmMap alarmMap;
02631             readAlarms(*event, &alarmMap);
02632             for (AlarmMap::Iterator it = alarmMap.begin();  it != alarmMap.end();  ++it)
02633             {
02634                 const AlarmData& data = it.data();
02635                 if (!data.alarm->hasStartOffset())
02636                     continue;
02637                 if ((data.type & KAAlarm::DEFERRED_ALARM)
02638                 &&  !(data.type & KAAlarm::TIMED_DEFERRAL_FLAG))
02639                 {
02640                     // Date-only deferral alarm, so adjust its time
02641                     QDateTime altime = nextMainDateTime.addSecs(data.alarm->startOffset().asSeconds());
02642                     altime.setTime(startOfDay);
02643                     deferralOffset = data.alarm->startOffset().asSeconds();
02644                     newDeferralOffset = event->dtStart().secsTo(altime);
02645                     const_cast<Alarm*>(data.alarm)->setStartOffset(newDeferralOffset);
02646                     changed = true;
02647                 }
02648                 else if (data.type == KAAlarm::AUDIO__ALARM
02649                 &&       data.alarm->startOffset().asSeconds() == deferralOffset)
02650                 {
02651                     // Audio alarm is set for the same time as the deferral alarm
02652                     const_cast<Alarm*>(data.alarm)->setStartOffset(newDeferralOffset);
02653                     changed = true;
02654                 }
02655             }
02656         }
02657     }
02658     return changed;
02659 }
02660 
02661 /******************************************************************************
02662  * If the calendar was written by a previous version of KAlarm, do any
02663  * necessary format conversions on the events to ensure that when the calendar
02664  * is saved, no information is lost or corrupted.
02665  */
02666 void KAEvent::convertKCalEvents(KCal::Calendar& calendar, int version, bool adjustSummerTime)
02667 {
02668     // KAlarm pre-0.9 codes held in the alarm's DESCRIPTION property
02669     static const QChar   SEPARATOR        = ';';
02670     static const QChar   LATE_CANCEL_CODE = 'C';
02671     static const QChar   AT_LOGIN_CODE    = 'L';   // subsidiary alarm at every login
02672     static const QChar   DEFERRAL_CODE    = 'D';   // extra deferred alarm
02673     static const QString TEXT_PREFIX      = QString::fromLatin1("TEXT:");
02674     static const QString FILE_PREFIX      = QString::fromLatin1("FILE:");
02675     static const QString COMMAND_PREFIX   = QString::fromLatin1("CMD:");
02676 
02677     // KAlarm pre-0.9.2 codes held in the event's CATEGORY property
02678     static const QString BEEP_CATEGORY    = QString::fromLatin1("BEEP");
02679 
02680     // KAlarm pre-1.1.1 LATECANCEL category with no parameter
02681     static const QString LATE_CANCEL_CAT = QString::fromLatin1("LATECANCEL");
02682 
02683     // KAlarm pre-1.3.0 TMPLDEFTIME category with no parameter
02684     static const QString TEMPL_DEF_TIME_CAT = QString::fromLatin1("TMPLDEFTIME");
02685 
02686     // KAlarm pre-1.3.1 XTERM category
02687     static const QString EXEC_IN_XTERM_CAT  = QString::fromLatin1("XTERM");
02688 
02689     // KAlarm pre-1.4.22 properties
02690     static const QCString KMAIL_ID_PROPERTY("KMAILID");    // X-KDE-KALARM-KMAILID property
02691 
02692     if (version >= calVersion())
02693         return;
02694 
02695     kdDebug(5950) << "KAEvent::convertKCalEvents(): adjusting version " << version << endl;
02696     bool pre_0_7   = (version < KAlarm::Version(0,7,0));
02697     bool pre_0_9   = (version < KAlarm::Version(0,9,0));
02698     bool pre_0_9_2 = (version < KAlarm::Version(0,9,2));
02699     bool pre_1_1_1 = (version < KAlarm::Version(1,1,1));
02700     bool pre_1_2_1 = (version < KAlarm::Version(1,2,1));
02701     bool pre_1_3_0 = (version < KAlarm::Version(1,3,0));
02702     bool pre_1_3_1 = (version < KAlarm::Version(1,3,1));
02703     bool pre_1_4_14 = (version < KAlarm::Version(1,4,14));
02704     bool pre_1_5_0 = (version < KAlarm::Version(1,5,0));
02705     Q_ASSERT(calVersion() == KAlarm::Version(1,5,0));
02706 
02707     QDateTime dt0(QDate(1970,1,1), QTime(0,0,0));
02708     QTime startOfDay = Preferences::startOfDay();
02709 
02710     Event::List events = calendar.rawEvents();
02711     for (Event::List::ConstIterator evit = events.begin();  evit != events.end();  ++evit)
02712     {
02713         Event* event = *evit;
02714         Alarm::List alarms = event->alarms();
02715         if (alarms.isEmpty())
02716             continue;    // KAlarm isn't interested in events without alarms
02717         QStringList cats = event->categories();
02718         bool addLateCancel = false;
02719 
02720         if (pre_0_7  &&  event->doesFloat())
02721         {
02722             // It's a KAlarm pre-0.7 calendar file.
02723             // Ensure that when the calendar is saved, the alarm time isn't lost.
02724             event->setFloats(false);
02725         }
02726 
02727         if (pre_0_9)
02728         {
02729             /*
02730              * It's a KAlarm pre-0.9 calendar file.
02731              * All alarms were of type DISPLAY. Instead of the X-KDE-KALARM-TYPE
02732              * alarm property, characteristics were stored as a prefix to the
02733              * alarm DESCRIPTION property, as follows:
02734              *   SEQNO;[FLAGS];TYPE:TEXT
02735              * where
02736              *   SEQNO = sequence number of alarm within the event
02737              *   FLAGS = C for late-cancel, L for repeat-at-login, D for deferral
02738              *   TYPE = TEXT or FILE or CMD
02739              *   TEXT = message text, file name/URL or command
02740              */
02741             for (Alarm::List::ConstIterator alit = alarms.begin();  alit != alarms.end();  ++alit)
02742             {
02743                 Alarm* alarm = *alit;
02744                 bool atLogin    = false;
02745                 bool deferral   = false;
02746                 bool lateCancel = false;
02747                 KAAlarmEventBase::Type action = T_MESSAGE;
02748                 QString txt = alarm->text();
02749                 int length = txt.length();
02750                 int i = 0;
02751                 if (txt[0].isDigit())
02752                 {
02753                     while (++i < length  &&  txt[i].isDigit()) ;
02754                     if (i < length  &&  txt[i++] == SEPARATOR)
02755                     {
02756                         while (i < length)
02757                         {
02758                             QChar ch = txt[i++];
02759                             if (ch == SEPARATOR)
02760                                 break;
02761                             if (ch == LATE_CANCEL_CODE)
02762                                 lateCancel = true;
02763                             else if (ch == AT_LOGIN_CODE)
02764                                 atLogin = true;
02765                             else if (ch == DEFERRAL_CODE)
02766                                 deferral = true;
02767                         }
02768                     }
02769                     else
02770                         i = 0;     // invalid prefix
02771                 }
02772                 if (txt.find(TEXT_PREFIX, i) == i)
02773                     i += TEXT_PREFIX.length();
02774                 else if (txt.find(FILE_PREFIX, i) == i)
02775                 {
02776                     action = T_FILE;
02777                     i += FILE_PREFIX.length();
02778                 }
02779                 else if (txt.find(COMMAND_PREFIX, i) == i)
02780                 {
02781                     action = T_COMMAND;
02782                     i += COMMAND_PREFIX.length();
02783                 }
02784                 else
02785                     i = 0;
02786                 txt = txt.mid(i);
02787 
02788                 QStringList types;
02789                 switch (action)
02790                 {
02791                     case T_FILE:
02792                         types += FILE_TYPE;
02793                         // fall through to T_MESSAGE
02794                     case T_MESSAGE:
02795                         alarm->setDisplayAlarm(txt);
02796                         break;
02797                     case T_COMMAND:
02798                         setProcedureAlarm(alarm, txt);
02799                         break;
02800                     case T_EMAIL:     // email alarms were introduced in KAlarm 0.9
02801                     case T_AUDIO:     // never occurs in this context
02802                         break;
02803                 }
02804                 if (atLogin)
02805                 {
02806                     types += AT_LOGIN_TYPE;
02807                     lateCancel = false;
02808                 }
02809                 else if (deferral)
02810                     types += TIME_DEFERRAL_TYPE;
02811                 if (lateCancel)
02812                     addLateCancel = true;
02813                 if (types.count() > 0)
02814                     alarm->setCustomProperty(APPNAME, TYPE_PROPERTY, types.join(","));
02815 
02816                 if (pre_0_7  &&  alarm->repeatCount() > 0  &&  alarm->snoozeTime() > 0)
02817                 {
02818                     // It's a KAlarm pre-0.7 calendar file.
02819                     // Minutely recurrences were stored differently.
02820                     Recurrence* recur = event->recurrence();
02821                     if (recur  &&  recur->doesRecur())
02822                     {
02823                         recur->setMinutely(alarm->snoozeTime());
02824                         recur->setDuration(alarm->repeatCount() + 1);
02825                         alarm->setRepeatCount(0);
02826                         alarm->setSnoozeTime(0);
02827                     }
02828                 }
02829 
02830                 if (adjustSummerTime)
02831                 {
02832                     // The calendar file was written by the KDE 3.0.0 version of KAlarm 0.5.7.
02833                     // Summer time was ignored when converting to UTC.
02834                     QDateTime dt = alarm->time();
02835                     time_t t = dt0.secsTo(dt);
02836                     struct tm* dtm = localtime(&t);
02837                     if (dtm->tm_isdst)
02838                     {
02839                         dt = dt.addSecs(-3600);
02840                         alarm->setTime(dt);
02841                     }
02842                 }
02843             }
02844         }
02845 
02846         if (pre_0_9_2)
02847         {
02848             /*
02849              * It's a KAlarm pre-0.9.2 calendar file.
02850              * For the expired calendar, set the CREATED time to the DTEND value.
02851              * Convert date-only DTSTART to date/time, and add category "DATE".
02852              * Set the DTEND time to the DTSTART time.
02853              * Convert all alarm times to DTSTART offsets.
02854              * For display alarms, convert the first unlabelled category to an
02855              * X-KDE-KALARM-FONTCOLOUR property.
02856              * Convert BEEP category into an audio alarm with no audio file.
02857              */
02858             if (uidStatus(event->uid()) == EXPIRED)
02859                 event->setCreated(event->dtEnd());
02860             QDateTime start = event->dtStart();
02861             if (event->doesFloat())
02862             {
02863                 event->setFloats(false);
02864                 start.setTime(startOfDay);
02865                 cats.append(DATE_ONLY_CATEGORY);
02866             }
02867             event->setHasEndDate(false);
02868 
02869             Alarm::List::ConstIterator alit;
02870             for (alit = alarms.begin();  alit != alarms.end();  ++alit)
02871             {
02872                 Alarm* alarm = *alit;
02873                 QDateTime dt = alarm->time();
02874                 alarm->setStartOffset(start.secsTo(dt));
02875             }
02876 
02877             if (cats.count() > 0)
02878             {
02879                 for (alit = alarms.begin();  alit != alarms.end();  ++alit)
02880                 {
02881                     Alarm* alarm = *alit;
02882                     if (alarm->type() == Alarm::Display)
02883                         alarm->setCustomProperty(APPNAME, FONT_COLOUR_PROPERTY,
02884                                                  QString::fromLatin1("%1;;").arg(cats[0]));
02885                 }
02886                 cats.remove(cats.begin());
02887             }
02888 
02889             for (QStringList::Iterator it = cats.begin();  it != cats.end();  ++it)
02890             {
02891                 if (*it == BEEP_CATEGORY)
02892                 {
02893                     cats.remove(it);
02894 
02895                     Alarm* alarm = event->newAlarm();
02896                     alarm->setEnabled(true);
02897                     alarm->setAudioAlarm();
02898                     QDateTime dt = event->dtStart();    // default
02899 
02900                     // Parse and order the alarms to know which one's date/time to use
02901                     AlarmMap alarmMap;
02902                     readAlarms(*event, &alarmMap);
02903                     AlarmMap::ConstIterator it = alarmMap.begin();
02904                     if (it != alarmMap.end())
02905                     {
02906                         dt = it.data().alarm->time();
02907                         break;
02908                     }
02909                     alarm->setStartOffset(start.secsTo(dt));
02910                     break;
02911                 }
02912             }
02913         }
02914 
02915         if (pre_1_1_1)
02916         {
02917             /*
02918              * It's a KAlarm pre-1.1.1 calendar file.
02919              * Convert simple LATECANCEL category to LATECANCEL:n where n = minutes late.
02920              */
02921             QStringList::Iterator it;
02922             while ((it = cats.find(LATE_CANCEL_CAT)) != cats.end())
02923             {
02924                 cats.remove(it);
02925                 addLateCancel = true;
02926             }
02927         }
02928 
02929         if (pre_1_2_1)
02930         {
02931             /*
02932              * It's a KAlarm pre-1.2.1 calendar file.
02933              * Convert email display alarms from translated to untranslated header prefixes.
02934              */
02935             for (Alarm::List::ConstIterator alit = alarms.begin();  alit != alarms.end();  ++alit)
02936             {
02937                 Alarm* alarm = *alit;
02938                 if (alarm->type() == Alarm::Display)
02939                 {
02940                     QString oldtext = alarm->text();
02941                     QString newtext = AlarmText::toCalendarText(oldtext);
02942                     if (oldtext != newtext)
02943                         alarm->setDisplayAlarm(newtext);
02944                 }
02945             }
02946         }
02947 
02948         if (pre_1_3_0)
02949         {
02950             /*
02951              * It's a KAlarm pre-1.3.0 calendar file.
02952              * Convert simple TMPLDEFTIME category to TMPLAFTTIME:n where n = minutes after.
02953              */
02954             QStringList::Iterator it;
02955             while ((it = cats.find(TEMPL_DEF_TIME_CAT)) != cats.end())
02956             {
02957                 cats.remove(it);
02958                 cats.append(QString("%1%2").arg(TEMPL_AFTER_TIME_CATEGORY).arg(0));
02959             }
02960         }
02961 
02962         if (pre_1_3_1)
02963         {
02964             /*
02965              * It's a KAlarm pre-1.3.1 calendar file.
02966              * Convert simple XTERM category to LOG:xterm:
02967              */
02968             QStringList::Iterator it;
02969             while ((it = cats.find(EXEC_IN_XTERM_CAT)) != cats.end())
02970             {
02971                 cats.remove(it);
02972                 cats.append(LOG_CATEGORY + xtermURL);
02973             }
02974         }
02975 
02976         if (addLateCancel)
02977             cats.append(QString("%1%2").arg(LATE_CANCEL_CATEGORY).arg(1));
02978 
02979         event->setCategories(cats);
02980 
02981 
02982         if (pre_1_4_14
02983         &&  event->recurrence()  &&  event->recurrence()->doesRecur())
02984         {
02985             /*
02986              * It's a KAlarm pre-1.4.14 calendar file.
02987              * For recurring events, convert the main alarm offset to an absolute
02988              * time in the X-KDE-KALARM-NEXTRECUR property, and convert main
02989              * alarm offsets to zero and deferral alarm offsets to be relative to
02990              * the next recurrence.
02991              */
02992             bool dateOnly = (cats.find(DATE_ONLY_CATEGORY) != cats.end());
02993             DateTime startDateTime(event->dtStart(), dateOnly);
02994             // Convert the main alarm and get the next main trigger time from it
02995             DateTime nextMainDateTime;
02996             bool mainExpired = true;
02997             Alarm::List::ConstIterator alit;
02998             for (alit = alarms.begin();  alit != alarms.end();  ++alit)
02999             {
03000                 Alarm* alarm = *alit;
03001                 if (!alarm->hasStartOffset())
03002                     continue;
03003                 bool mainAlarm = true;
03004                 QString property = alarm->customProperty(APPNAME, TYPE_PROPERTY);
03005                 QStringList types = QStringList::split(QChar(','), property);
03006                 for (unsigned int i = 0;  i < types.count();  ++i)
03007                 {
03008                     QString type = types[i];
03009                     if (type == AT_LOGIN_TYPE
03010                     ||  type == TIME_DEFERRAL_TYPE
03011                     ||  type == DATE_DEFERRAL_TYPE
03012                     ||  type == REMINDER_TYPE
03013                     ||  type == REMINDER_ONCE_TYPE
03014                     ||  type == DISPLAYING_TYPE
03015                     ||  type == PRE_ACTION_TYPE
03016                     ||  type == POST_ACTION_TYPE)
03017                         mainAlarm = false;
03018                 }
03019                 if (mainAlarm)
03020                 {
03021                     mainExpired = false;
03022                     nextMainDateTime = alarm->time();
03023                     nextMainDateTime.setDateOnly(dateOnly);
03024                     if (nextMainDateTime != startDateTime)
03025                     {
03026                         QDateTime dt = nextMainDateTime.dateTime();
03027                         event->setCustomProperty(APPNAME, NEXT_RECUR_PROPERTY,
03028                                                  dt.toString(dateOnly ? "yyyyMMdd" : "yyyyMMddThhmmss"));
03029                     }
03030                     alarm->setStartOffset(0);
03031                 }
03032             }
03033             int adjustment;
03034             if (mainExpired)
03035             {
03036                 // It's an expired recurrence.
03037                 // Set the alarm offset relative to the first actual occurrence
03038                 // (taking account of possible exceptions).
03039                 DateTime dt = event->recurrence()->getNextDateTime(startDateTime.dateTime().addDays(-1));
03040                 dt.setDateOnly(dateOnly);
03041                 adjustment = startDateTime.secsTo(dt);
03042             }
03043             else
03044                 adjustment = startDateTime.secsTo(nextMainDateTime);
03045             if (adjustment)
03046             {
03047                 // Convert deferred alarms
03048                 for (alit = alarms.begin();  alit != alarms.end();  ++alit)
03049                 {
03050                     Alarm* alarm = *alit;
03051                     if (!alarm->hasStartOffset())
03052                         continue;
03053                     QString property = alarm->customProperty(APPNAME, TYPE_PROPERTY);
03054                     QStringList types = QStringList::split(QChar(','), property);
03055                     for (unsigned int i = 0;  i < types.count();  ++i)
03056                     {
03057                         QString type = types[i];
03058                         if (type == TIME_DEFERRAL_TYPE
03059                         ||  type == DATE_DEFERRAL_TYPE)
03060                         {
03061                             alarm->setStartOffset(alarm->startOffset().asSeconds() - adjustment);
03062                             break;
03063                         }
03064                     }
03065                 }
03066             }
03067         }
03068         
03069         if (pre_1_5_0)
03070         {
03071             /*
03072              * It's a KAlarm pre-1.5.0 calendar file.
03073              * Convert email identity names to uoids.
03074              * Convert simple repetitions without a recurrence, to a recurrence.
03075              */
03076             for (Alarm::List::ConstIterator alit = alarms.begin();  alit != alarms.end();  ++alit)
03077             {
03078                 Alarm* alarm = *alit;
03079                 QString name = alarm->customProperty(APPNAME, KMAIL_ID_PROPERTY);
03080                 if (name.isEmpty())
03081                     continue;
03082                 uint id = KAMail::identityUoid(name);
03083                 if (id)
03084                     alarm->setCustomProperty(APPNAME, EMAIL_ID_PROPERTY, QString::number(id));
03085                 alarm->removeCustomProperty(APPNAME, KMAIL_ID_PROPERTY);
03086             }
03087             convertRepetition(event);
03088         }
03089     }
03090 }
03091 
03092 /******************************************************************************
03093 * If the calendar was written by a pre-1.4.22 version of KAlarm, or another
03094 * program, convert simple repetitions in events without a recurrence, to a
03095 * recurrence.
03096 * Reply = true if any conversions were done.
03097 */
03098 void KAEvent::convertRepetitions(KCal::CalendarLocal& calendar)
03099 {
03100 
03101     Event::List events = calendar.rawEvents();
03102     for (Event::List::ConstIterator ev = events.begin();  ev != events.end();  ++ev)
03103         convertRepetition(*ev);
03104 }
03105 
03106 /******************************************************************************
03107 * Convert simple repetitions in an event without a recurrence, to a
03108 * recurrence. Repetitions which are an exact multiple of 24 hours are converted
03109 * to daily recurrences; else they are converted to minutely recurrences. Note
03110 * that daily and minutely recurrences produce different results when they span
03111 * a daylight saving time change.
03112 * Reply = true if any conversions were done.
03113 */
03114 bool KAEvent::convertRepetition(KCal::Event* event)
03115 {
03116     Alarm::List alarms = event->alarms();
03117     if (alarms.isEmpty())
03118         return false;
03119     Recurrence* recur = event->recurrence();   // guaranteed to return non-null
03120     if (!recur->doesRecur())
03121         return false;
03122     bool converted = false;
03123     bool readOnly = event->isReadOnly();
03124     for (Alarm::List::ConstIterator alit = alarms.begin();  alit != alarms.end();  ++alit)
03125     {
03126         Alarm* alarm = *alit;
03127         if (alarm->repeatCount() > 0  &&  alarm->snoozeTime() > 0)
03128         {
03129             if (!converted)
03130             {
03131                 if (readOnly)
03132                     event->setReadOnly(false);
03133                 if (alarm->snoozeTime() % (24*3600))
03134                     recur->setMinutely(alarm->snoozeTime());
03135                 else
03136                     recur->setDaily(alarm->snoozeTime() / (24*3600));
03137                 recur->setDuration(alarm->repeatCount() + 1);
03138                 converted = true;
03139             }
03140             alarm->setRepeatCount(0);
03141             alarm->setSnoozeTime(0);
03142         }
03143     }
03144     if (converted)
03145     {
03146         if (readOnly)
03147             event->setReadOnly(true);
03148     }
03149     return converted;
03150 }
03151 
03152 #ifndef NDEBUG
03153 void KAEvent::dumpDebug() const
03154 {
03155     kdDebug(5950) << "KAEvent dump:\n";
03156     KAAlarmEventBase::dumpDebug();
03157     if (!mTemplateName.isEmpty())
03158     {
03159         kdDebug(5950) << "-- mTemplateName:" << mTemplateName << ":\n";
03160         kdDebug(5950) << "-- mTemplateAfterTime:" << mTemplateAfterTime << ":\n";
03161     }
03162     if (mActionType == T_MESSAGE  ||  mActionType == T_FILE)
03163     {
03164         kdDebug(5950) << "-- mAudioFile:" << mAudioFile << ":\n";
03165         kdDebug(5950) << "-- mPreAction:" << mPreAction << ":\n";
03166         kdDebug(5950) << "-- mPostAction:" << mPostAction << ":\n";
03167     }
03168     else if (mActionType == T_COMMAND)
03169     {
03170         kdDebug(5950) << "-- mCommandXterm:" << (mCommandXterm ? "true" : "false") << ":\n";
03171         kdDebug(5950) << "-- mLogFile:" << mLogFile << ":\n";
03172     }
03173     kdDebug(5950) << "-- mKMailSerialNumber:" << mKMailSerialNumber << ":\n";
03174     kdDebug(5950) << "-- mCopyToKOrganizer:" << (mCopyToKOrganizer ? "true" : "false") << ":\n";
03175     kdDebug(5950) << "-- mStartDateTime:" << mStartDateTime.toString() << ":\n";
03176     kdDebug(5950) << "-- mSaveDateTime:" << mSaveDateTime.toString() << ":\n";
03177     if (mRepeatAtLogin)
03178         kdDebug(5950) << "-- mAtLoginDateTime:" << mAtLoginDateTime.toString() << ":\n";
03179     kdDebug(5950) << "-- mArchiveRepeatAtLogin:" << (mArchiveRepeatAtLogin ? "true" : "false") << ":\n";
03180     kdDebug(5950) << "-- mEnabled:" << (mEnabled ? "true" : "false") << ":\n";
03181     if (mReminderMinutes)
03182         kdDebug(5950) << "-- mReminderMinutes:" << mReminderMinutes << ":\n";
03183     if (mArchiveReminderMinutes)
03184         kdDebug(5950) << "-- mArchiveReminderMinutes:" << mArchiveReminderMinutes << ":\n";
03185     if (mReminderMinutes  ||  mArchiveReminderMinutes)
03186         kdDebug(5950) << "-- mReminderOnceOnly:" << mReminderOnceOnly << ":\n";
03187     else if (mDeferral > 0)
03188     {
03189         kdDebug(5950) << "-- mDeferral:" << (mDeferral == NORMAL_DEFERRAL ? "normal" : "reminder") << ":\n";
03190         kdDebug(5950) << "-- mDeferralTime:" << mDeferralTime.toString() << ":\n";
03191     }
03192     else if (mDeferral == CANCEL_DEFERRAL)
03193         kdDebug(5950) << "-- mDeferral:cancel:\n";
03194     kdDebug(5950) << "-- mDeferDefaultMinutes:" << mDeferDefaultMinutes << ":\n";
03195     if (mDisplaying)
03196     {
03197         kdDebug(5950) << "-- mDisplayingTime:" << mDisplayingTime.toString() << ":\n";
03198         kdDebug(5950) << "-- mDisplayingFlags:" << mDisplayingFlags << ":\n";
03199     }
03200     kdDebug(5950) << "-- mRevision:" << mRevision << ":\n";
03201     kdDebug(5950) << "-- mRecurrence:" << (mRecurrence ? "true" : "false") << ":\n";
03202     kdDebug(5950) << "-- mAlarmCount:" << mAlarmCount << ":\n";
03203     kdDebug(5950) << "-- mMainExpired:" << (mMainExpired ? "true" : "false") << ":\n";
03204     kdDebug(5950) << "KAEvent dump end\n";
03205 }
03206 #endif
03207 
03208 
03209 /*=============================================================================
03210 = Class KAAlarm
03211 = Corresponds to a single KCal::Alarm instance.
03212 =============================================================================*/
03213 
03214 KAAlarm::KAAlarm(const KAAlarm& alarm)
03215     : KAAlarmEventBase(alarm),
03216       mType(alarm.mType),
03217       mRecurs(alarm.mRecurs),
03218       mDeferred(alarm.mDeferred)
03219 { }
03220 
03221 
03222 int KAAlarm::flags() const
03223 {
03224     return KAAlarmEventBase::flags()
03225          | (mDeferred ? KAEvent::DEFERRAL : 0);
03226 
03227 }
03228 
03229 #ifndef NDEBUG
03230 void KAAlarm::dumpDebug() const
03231 {
03232     kdDebug(5950) << "KAAlarm dump:\n";
03233     KAAlarmEventBase::dumpDebug();
03234     const char* altype = 0;
03235     switch (mType)
03236     {
03237         case MAIN__ALARM:                    altype = "MAIN";  break;
03238         case REMINDER__ALARM:                altype = "REMINDER";  break;
03239         case DEFERRED_DATE__ALARM:           altype = "DEFERRED(DATE)";  break;
03240         case DEFERRED_TIME__ALARM:           altype = "DEFERRED(TIME)";  break;
03241         case DEFERRED_REMINDER_DATE__ALARM:  altype = "DEFERRED_REMINDER(DATE)";  break;
03242         case DEFERRED_REMINDER_TIME__ALARM:  altype = "DEFERRED_REMINDER(TIME)";  break;
03243         case AT_LOGIN__ALARM:                altype = "LOGIN";  break;
03244         case DISPLAYING__ALARM:              altype = "DISPLAYING";  break;
03245         case AUDIO__ALARM:                   altype = "AUDIO";  break;
03246         case PRE_ACTION__ALARM:              altype = "PRE_ACTION";  break;
03247         case POST_ACTION__ALARM:             altype = "POST_ACTION";  break;
03248         default:                             altype = "INVALID";  break;
03249     }
03250     kdDebug(5950) << "-- mType:" << altype << ":\n";
03251     kdDebug(5950) << "-- mRecurs:" << (mRecurs ? "true" : "false") << ":\n";
03252     kdDebug(5950) << "-- mDeferred:" << (mDeferred ? "true" : "false") << ":\n";
03253     kdDebug(5950) << "KAAlarm dump end\n";
03254 }
03255 
03256 const char* KAAlarm::debugType(Type type)
03257 {
03258     switch (type)
03259     {
03260         case MAIN_ALARM:               return "MAIN";
03261         case REMINDER_ALARM:           return "REMINDER";
03262         case DEFERRED_ALARM:           return "DEFERRED";
03263         case DEFERRED_REMINDER_ALARM:  return "DEFERRED_REMINDER";
03264         case AT_LOGIN_ALARM:           return "LOGIN";
03265         case DISPLAYING_ALARM:         return "DISPLAYING";
03266         case AUDIO_ALARM:              return "AUDIO";
03267         case PRE_ACTION_ALARM:         return "PRE_ACTION";
03268         case POST_ACTION_ALARM:        return "POST_ACTION";
03269         default:                       return "INVALID";
03270     }
03271 }
03272 #endif
03273 
03274 
03275 /*=============================================================================
03276 = Class KAAlarmEventBase
03277 =============================================================================*/
03278 
03279 void KAAlarmEventBase::copy(const KAAlarmEventBase& rhs)
03280 {
03281     mEventID           = rhs.mEventID;
03282     mText              = rhs.mText;
03283     mNextMainDateTime  = rhs.mNextMainDateTime;
03284     mBgColour          = rhs.mBgColour;
03285     mFgColour          = rhs.mFgColour;
03286     mFont              = rhs.mFont;
03287     mEmailFromIdentity = rhs.mEmailFromIdentity;
03288     mEmailAddresses    = rhs.mEmailAddresses;
03289     mEmailSubject      = rhs.mEmailSubject;
03290     mEmailAttachments  = rhs.mEmailAttachments;
03291     mSoundVolume       = rhs.mSoundVolume;
03292     mFadeVolume        = rhs.mFadeVolume;
03293     mFadeSeconds       = rhs.mFadeSeconds;
03294     mActionType        = rhs.mActionType;
03295     mCommandScript     = rhs.mCommandScript;
03296     mRepeatCount       = rhs.mRepeatCount;
03297     mRepeatInterval    = rhs.mRepeatInterval;
03298     mNextRepeat        = rhs.mNextRepeat;
03299     mBeep              = rhs.mBeep;
03300     mSpeak             = rhs.mSpeak;
03301     mRepeatSound       = rhs.mRepeatSound;
03302     mRepeatAtLogin     = rhs.mRepeatAtLogin;
03303     mDisplaying        = rhs.mDisplaying;
03304     mLateCancel        = rhs.mLateCancel;
03305     mAutoClose         = rhs.mAutoClose;
03306     mEmailBcc          = rhs.mEmailBcc;
03307     mConfirmAck        = rhs.mConfirmAck;
03308     mDefaultFont       = rhs.mDefaultFont;
03309 }
03310 
03311 void KAAlarmEventBase::set(int flags)
03312 {
03313     mSpeak         = flags & KAEvent::SPEAK;
03314     mBeep          = (flags & KAEvent::BEEP) && !mSpeak;
03315     mRepeatSound   = flags & KAEvent::REPEAT_SOUND;
03316     mRepeatAtLogin = flags & KAEvent::REPEAT_AT_LOGIN;
03317     mAutoClose     = (flags & KAEvent::AUTO_CLOSE) && mLateCancel;
03318     mEmailBcc      = flags & KAEvent::EMAIL_BCC;
03319     mConfirmAck    = flags & KAEvent::CONFIRM_ACK;
03320     mDisplaying    = flags & KAEvent::DISPLAYING_;
03321     mDefaultFont   = flags & KAEvent::DEFAULT_FONT;
03322     mCommandScript = flags & KAEvent::SCRIPT;
03323 }
03324 
03325 int KAAlarmEventBase::flags() const
03326 {
03327     return (mBeep && !mSpeak ? KAEvent::BEEP : 0)
03328          | (mSpeak           ? KAEvent::SPEAK : 0)
03329          | (mRepeatSound     ? KAEvent::REPEAT_SOUND : 0)
03330          | (mRepeatAtLogin   ? KAEvent::REPEAT_AT_LOGIN : 0)
03331          | (mAutoClose       ? KAEvent::AUTO_CLOSE : 0)
03332          | (mEmailBcc        ? KAEvent::EMAIL_BCC : 0)
03333          | (mConfirmAck      ? KAEvent::CONFIRM_ACK : 0)
03334          | (mDisplaying      ? KAEvent::DISPLAYING_ : 0)
03335          | (mDefaultFont     ? KAEvent::DEFAULT_FONT : 0)
03336          | (mCommandScript   ? KAEvent::SCRIPT : 0);
03337 }
03338 
03339 const QFont& KAAlarmEventBase::font() const
03340 {
03341     return mDefaultFont ? Preferences::messageFont() : mFont;
03342 }
03343 
03344 #ifndef NDEBUG
03345 void KAAlarmEventBase::dumpDebug() const
03346 {
03347     kdDebug(5950) << "-- mEventID:" << mEventID << ":\n";
03348     kdDebug(5950) << "-- mActionType:" << (mActionType == T_MESSAGE ? "MESSAGE" : mActionType == T_FILE ? "FILE" : mActionType == T_COMMAND ? "COMMAND" : mActionType == T_EMAIL ? "EMAIL" : mActionType == T_AUDIO ? "AUDIO" : "??") << ":\n";
03349     kdDebug(5950) << "-- mText:" << mText << ":\n";
03350     if (mActionType == T_COMMAND)
03351         kdDebug(5950) << "-- mCommandScript:" << (mCommandScript ? "true" : "false") << ":\n";
03352     kdDebug(5950) << "-- mNextMainDateTime:" << mNextMainDateTime.toString() << ":\n";
03353     if (mActionType == T_EMAIL)
03354     {
03355         kdDebug(5950) << "-- mEmail: FromKMail:" << mEmailFromIdentity << ":\n";
03356         kdDebug(5950) << "--         Addresses:" << mEmailAddresses.join(", ") << ":\n";
03357         kdDebug(5950) << "--         Subject:" << mEmailSubject << ":\n";
03358         kdDebug(5950) << "--         Attachments:" << mEmailAttachments.join(", ") << ":\n";
03359         kdDebug(5950) << "--         Bcc:" << (mEmailBcc ? "true" : "false") << ":\n";
03360     }
03361     kdDebug(5950) << "-- mBgColour:" << mBgColour.name() << ":\n";
03362     kdDebug(5950) << "-- mFgColour:" << mFgColour.name() << ":\n";
03363     kdDebug(5950) << "-- mDefaultFont:" << (mDefaultFont ? "true" : "false") << ":\n";
03364     if (!mDefaultFont)
03365         kdDebug(5950) << "-- mFont:" << mFont.toString() << ":\n";
03366     kdDebug(5950) << "-- mBeep:" << (mBeep ? "true" : "false") << ":\n";
03367     kdDebug(5950) << "-- mSpeak:" << (mSpeak ? "true" : "false") << ":\n";
03368     if (mActionType == T_AUDIO)
03369     {
03370         if (mSoundVolume >= 0)
03371         {
03372             kdDebug(5950) << "-- mSoundVolume:" << mSoundVolume << ":\n";
03373             if (mFadeVolume >= 0)
03374             {
03375                 kdDebug(5950) << "-- mFadeVolume:" << mFadeVolume << ":\n";
03376                 kdDebug(5950) << "-- mFadeSeconds:" << mFadeSeconds << ":\n";
03377             }
03378             else
03379                 kdDebug(5950) << "-- mFadeVolume:-:\n";
03380         }
03381         else
03382             kdDebug(5950) << "-- mSoundVolume:-:\n";
03383         kdDebug(5950) << "-- mRepeatSound:" << (mRepeatSound ? "true" : "false") << ":\n";
03384     }
03385     kdDebug(5950) << "-- mConfirmAck:" << (mConfirmAck ? "true" : "false") << ":\n";
03386     kdDebug(5950) << "-- mRepeatAtLogin:" << (mRepeatAtLogin ? "true" : "false") << ":\n";
03387     kdDebug(5950) << "-- mRepeatCount:" << mRepeatCount << ":\n";
03388     kdDebug(5950) << "-- mRepeatInterval:" << mRepeatInterval << ":\n";
03389     kdDebug(5950) << "-- mNextRepeat:" << mNextRepeat << ":\n";
03390     kdDebug(5950) << "-- mDisplaying:" << (mDisplaying ? "true" : "false") << ":\n";
03391     kdDebug(5950) << "-- mLateCancel:" << mLateCancel << ":\n";
03392     kdDebug(5950) << "-- mAutoClose:" << (mAutoClose ? "true" : "false") << ":\n";
03393 }
03394 #endif
03395 
03396 
03397 /*=============================================================================
03398 = Class EmailAddressList
03399 =============================================================================*/
03400 
03401 /******************************************************************************
03402  * Sets the list of email addresses, removing any empty addresses.
03403  * Reply = false if empty addresses were found.
03404  */
03405 EmailAddressList& EmailAddressList::operator=(const QValueList<Person>& addresses)
03406 {
03407     clear();
03408     for (QValueList<Person>::ConstIterator it = addresses.begin();  it != addresses.end();  ++it)
03409     {
03410         if (!(*it).email().isEmpty())
03411             append(*it);
03412     }
03413     return *this;
03414 }
03415 
03416 /******************************************************************************
03417  * Return the email address list as a string, each address being delimited by
03418  * the specified separator string.
03419  */
03420 QString EmailAddressList::join(const QString& separator) const
03421 {
03422     QString result;
03423     bool first = true;
03424     for (QValueList<Person>::ConstIterator it = begin();  it != end();  ++it)
03425     {
03426         if (first)
03427             first = false;
03428         else
03429             result += separator;
03430 
03431         bool quote = false;
03432         QString name = (*it).name();
03433         if (!name.isEmpty())
03434         {
03435             // Need to enclose the name in quotes if it has any special characters
03436             int len = name.length();
03437             for (int i = 0;  i < len;  ++i)
03438             {
03439                 QChar ch = name[i];
03440                 if (!ch.isLetterOrNumber())
03441                 {
03442                     quote = true;
03443                     result += '\"';
03444                     break;
03445                 }
03446             }
03447             result += (*it).name();
03448             result += (quote ? "\" <" : " <");
03449             quote = true;    // need angle brackets round email address
03450         }
03451 
03452         result += (*it).email();
03453         if (quote)
03454             result += '>';
03455     }
03456     return result;
03457 }
03458 
03459 
03460 /*=============================================================================
03461 = Static functions
03462 =============================================================================*/
03463 
03464 /******************************************************************************
03465  * Set the specified alarm to be a procedure alarm with the given command line.
03466  * The command line is first split into its program file and arguments before
03467  * initialising the alarm.
03468  */
03469 static void setProcedureAlarm(Alarm* alarm, const QString& commandLine)
03470 {
03471     QString command   = QString::null;
03472     QString arguments = QString::null;
03473     QChar quoteChar;
03474     bool quoted = false;
03475     uint posMax = commandLine.length();
03476     uint pos;
03477     for (pos = 0;  pos < posMax;  ++pos)
03478     {
03479         QChar ch = commandLine[pos];
03480         if (quoted)
03481         {
03482             if (ch == quoteChar)
03483             {
03484                 ++pos;    // omit the quote character
03485                 break;
03486             }
03487             command += ch;
03488         }
03489         else
03490         {
03491             bool done = false;
03492             switch (ch)
03493             {
03494                 case ' ':
03495                 case ';':
03496                 case '|':
03497                 case '<':
03498                 case '>':
03499                     done = !command.isEmpty();
03500                     break;
03501                 case '\'':
03502                 case '"':
03503                     if (command.isEmpty())
03504                     {
03505                         // Start of a quoted string. Omit the quote character.
03506                         quoted = true;
03507                         quoteChar = ch;
03508                         break;
03509                     }
03510                     // fall through to default
03511                 default:
03512                     command += ch;
03513                     break;
03514             }
03515             if (done)
03516                 break;
03517         }
03518     }
03519 
03520     // Skip any spaces after the command
03521     for ( ;  pos < posMax  &&  commandLine[pos] == ' ';  ++pos) ;
03522     arguments = commandLine.mid(pos);
03523 
03524     alarm->setProcedureAlarm(command, arguments);
03525 }
KDE Home | KDE Accessibility Home | Description of Access Keys