kalarm

alarmcalendar.cpp

00001 /*
00002  *  alarmcalendar.cpp  -  KAlarm calendar file access
00003  *  Program:  kalarm
00004  *  Copyright (C) 2001 - 2005 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 #include <unistd.h>
00023 #include <time.h>
00024 
00025 #include <qfile.h>
00026 #include <qtextstream.h>
00027 #include <qregexp.h>
00028 #include <qtimer.h>
00029 
00030 #include <klocale.h>
00031 #include <kmessagebox.h>
00032 #include <kstandarddirs.h>
00033 #include <kstaticdeleter.h>
00034 #include <kconfig.h>
00035 #include <kaboutdata.h>
00036 #include <kio/netaccess.h>
00037 #include <kfileitem.h>
00038 #include <ktempfile.h>
00039 #include <dcopclient.h>
00040 #include <kdebug.h>
00041 
00042 extern "C" {
00043 #include <libical/ical.h>
00044 }
00045 
00046 #include <libkcal/vcaldrag.h>
00047 #include <libkcal/vcalformat.h>
00048 #include <libkcal/icalformat.h>
00049 
00050 #include "calendarcompat.h"
00051 #include "kalarmapp.h"
00052 #include "mainwindow.h"
00053 #include "preferences.h"
00054 #include "startdaytimer.h"
00055 #include "alarmcalendar.moc"
00056 
00057 using namespace KCal;
00058 
00059 static const KAEvent::Status eventTypes[AlarmCalendar::NCALS] = {
00060     KAEvent::ACTIVE, KAEvent::EXPIRED, KAEvent::DISPLAYING, KAEvent::TEMPLATE
00061 };
00062 static const QString calendarNames[AlarmCalendar::NCALS] = {
00063     QString::fromLatin1("calendar.ics"),
00064     QString::fromLatin1("expired.ics"),
00065     QString::fromLatin1("displaying.ics"),
00066     QString::fromLatin1("template.ics")
00067 };
00068 static KStaticDeleter<AlarmCalendar> calendarDeleter[AlarmCalendar::NCALS];    // ensure that the calendar destructors are called
00069 
00070 AlarmCalendar* AlarmCalendar::mCalendars[NCALS] = { 0, 0, 0, 0 };
00071 
00072 
00073 /******************************************************************************
00074 * Initialise the alarm calendars, and ensure that their file names are different.
00075 * There are 4 calendars:
00076 *  1) A user-independent one containing the active alarms;
00077 *  2) A historical one containing expired alarms;
00078 *  3) A user-specific one which contains details of alarms which are currently
00079 *     being displayed to that user and which have not yet been acknowledged;
00080 *  4) One containing alarm templates.
00081 * Reply = true if success, false if calendar name error.
00082 */
00083 bool AlarmCalendar::initialiseCalendars()
00084 {
00085     KConfig* config = kapp->config();
00086     config->setGroup(QString::fromLatin1("General"));
00087     QString activeKey   = QString::fromLatin1("Calendar");
00088     QString expiredKey  = QString::fromLatin1("ExpiredCalendar");
00089     QString templateKey = QString::fromLatin1("TemplateCalendar");
00090     QString displayCal, activeCal, expiredCal, templateCal;
00091     calendarDeleter[ACTIVE].setObject(mCalendars[ACTIVE], createCalendar(ACTIVE, config, activeCal, activeKey));
00092     calendarDeleter[EXPIRED].setObject(mCalendars[EXPIRED], createCalendar(EXPIRED, config, expiredCal, expiredKey));
00093     calendarDeleter[DISPLAY].setObject(mCalendars[DISPLAY], createCalendar(DISPLAY, config, displayCal));
00094     calendarDeleter[TEMPLATE].setObject(mCalendars[TEMPLATE], createCalendar(TEMPLATE, config, templateCal, templateKey));
00095 
00096     QString errorKey1, errorKey2;
00097     if (activeCal == displayCal)
00098         errorKey1 = activeKey;
00099     else if (expiredCal == displayCal)
00100         errorKey1 = expiredKey;
00101     else if (templateCal == displayCal)
00102         errorKey1 = templateKey;
00103     if (!errorKey1.isNull())
00104     {
00105         kdError(5950) << "AlarmCalendar::initialiseCalendars(): '" << errorKey1 << "' calendar name = display calendar name\n";
00106         QString file = config->readPathEntry(errorKey1);
00107         KAlarmApp::displayFatalError(i18n("%1: file name not permitted: %2").arg(errorKey1).arg(file));
00108         return false;
00109     }
00110     if (activeCal == expiredCal)
00111     {
00112         errorKey1 = activeKey;
00113         errorKey2 = expiredKey;
00114     }
00115     else if (activeCal == templateCal)
00116     {
00117         errorKey1 = activeKey;
00118         errorKey2 = templateKey;
00119     }
00120     else if (expiredCal == templateCal)
00121     {
00122         errorKey1 = expiredKey;
00123         errorKey2 = templateKey;
00124     }
00125     if (!errorKey1.isNull())
00126     {
00127         kdError(5950) << "AlarmCalendar::initialiseCalendars(): calendar names clash: " << errorKey1 << ", " << errorKey2 << endl;
00128         KAlarmApp::displayFatalError(i18n("%1, %2: file names must be different").arg(errorKey1).arg(errorKey2));
00129         return false;
00130     }
00131     if (!mCalendars[ACTIVE]->valid())
00132     {
00133         QString path = mCalendars[ACTIVE]->path();
00134         kdError(5950) << "AlarmCalendar::initialiseCalendars(): invalid name: " << path << endl;
00135         KAlarmApp::displayFatalError(i18n("Invalid calendar file name: %1").arg(path));
00136         return false;
00137     }
00138     return true;
00139 }
00140 
00141 /******************************************************************************
00142 * Create an alarm calendar instance.
00143 * If 'configKey' is non-null, the calendar will be converted to ICal format.
00144 */
00145 AlarmCalendar* AlarmCalendar::createCalendar(CalID type, KConfig* config, QString& writePath, const QString& configKey)
00146 {
00147     static QRegExp vcsRegExp(QString::fromLatin1("\\.vcs$"));
00148     static QString ical = QString::fromLatin1(".ics");
00149 
00150     if (configKey.isNull())
00151     {
00152         writePath = locateLocal("appdata", calendarNames[type]);
00153         return new AlarmCalendar(writePath, type);
00154     }
00155     else
00156     {
00157         QString readPath = config->readPathEntry(configKey, locateLocal("appdata", calendarNames[type]));
00158         writePath = readPath;
00159         writePath.replace(vcsRegExp, ical);
00160         return new AlarmCalendar(readPath, type, writePath, configKey);
00161     }
00162 }
00163 
00164 /******************************************************************************
00165 * Terminate access to all calendars.
00166 */
00167 void AlarmCalendar::terminateCalendars()
00168 {
00169     for (int i = 0;  i < NCALS;  ++i)
00170     {
00171         calendarDeleter[i].destructObject();
00172         mCalendars[i] = 0;
00173     }
00174 }
00175 
00176 /******************************************************************************
00177 * Return a calendar, opening it first if not already open.
00178 * Reply = calendar instance
00179 *       = 0 if calendar could not be opened.
00180 */
00181 AlarmCalendar* AlarmCalendar::calendarOpen(CalID id)
00182 {
00183     AlarmCalendar* cal = mCalendars[id];
00184     if (!cal->mPurgeDays)
00185         return 0;     // all events are automatically purged from the calendar
00186     if (cal->open())
00187         return cal;
00188     kdError(5950) << "AlarmCalendar::calendarOpen(" << calendarNames[id] << "): open error\n";
00189     return 0;
00190 }
00191 
00192 /******************************************************************************
00193 * Find and return the event with the specified ID.
00194 * The calendar searched is determined by the calendar identifier in the ID.
00195 */
00196 const KCal::Event* AlarmCalendar::getEvent(const QString& uniqueID)
00197 {
00198     if (uniqueID.isEmpty())
00199         return 0;
00200     CalID calID;
00201     switch (KAEvent::uidStatus(uniqueID))
00202     {
00203         case KAEvent::ACTIVE:      calID = ACTIVE;  break;
00204         case KAEvent::TEMPLATE:    calID = TEMPLATE;  break;
00205         case KAEvent::EXPIRED:     calID = EXPIRED;  break;
00206         case KAEvent::DISPLAYING:  calID = DISPLAY;  break;
00207         default:
00208             return 0;
00209     }
00210     AlarmCalendar* cal = calendarOpen(calID);
00211     if (!cal)
00212         return 0;
00213     return cal->event(uniqueID);
00214 }
00215 
00216 
00217 /******************************************************************************
00218 * Constructor.
00219 * If 'icalPath' is non-null, the file will be always be saved in ICal format.
00220 * If 'configKey' is also non-null, that config file entry will be updated when
00221 * the file is saved in ICal format.
00222 */
00223 AlarmCalendar::AlarmCalendar(const QString& path, CalID type, const QString& icalPath,
00224                              const QString& configKey)
00225     : mCalendar(0),
00226       mConfigKey(icalPath.isNull() ? QString::null : configKey),
00227       mType(eventTypes[type]),
00228       mPurgeDays(-1),      // default to not purging
00229       mOpen(false),
00230       mPurgeDaysQueued(-1),
00231       mUpdateCount(0),
00232       mUpdateSave(false)
00233 {
00234     mUrl.setPath(path);       // N.B. constructor mUrl(path) doesn't work with UNIX paths
00235     mICalUrl.setPath(icalPath.isNull() ? path : icalPath);
00236     mVCal = (icalPath.isNull() || path != icalPath);    // is the calendar in ICal or VCal format?
00237 }
00238 
00239 AlarmCalendar::~AlarmCalendar()
00240 {
00241     close();
00242 }
00243 
00244 /******************************************************************************
00245 * Open the calendar file if not already open, and load it into memory.
00246 */
00247 bool AlarmCalendar::open()
00248 {
00249     if (mOpen)
00250         return true;
00251     if (!mUrl.isValid())
00252         return false;
00253 
00254     kdDebug(5950) << "AlarmCalendar::open(" << mUrl.prettyURL() << ")\n";
00255     if (!mCalendar)
00256         mCalendar = new CalendarLocal(QString::fromLatin1("UTC"));
00257     mCalendar->setLocalTime();    // write out using local time (i.e. no time zone)
00258 
00259     // Check for file's existence, assuming that it does exist when uncertain,
00260     // to avoid overwriting it.
00261     if (!KIO::NetAccess::exists(mUrl, true, MainWindow::mainMainWindow()))
00262     {
00263         // The calendar file doesn't yet exist, so create it
00264         if (create())
00265             load();
00266     }
00267     else
00268     {
00269         // Load the existing calendar file
00270         if (load() == 0)
00271         {
00272             if (create())       // zero-length file - create a new one
00273                 load();
00274         }
00275     }
00276     if (!mOpen)
00277     {
00278         delete mCalendar;
00279         mCalendar = 0;
00280     }
00281     return mOpen;
00282 }
00283 
00284 /******************************************************************************
00285 * Private method to create a new calendar file.
00286 * It is always created in iCalendar format.
00287 */
00288 bool AlarmCalendar::create()
00289 {
00290     if (mICalUrl.isLocalFile())
00291         return saveCal(mICalUrl.path());
00292     else
00293     {
00294         KTempFile tmpFile;
00295         return saveCal(tmpFile.name());
00296     }
00297 }
00298 
00299 /******************************************************************************
00300 * Load the calendar file into memory.
00301 * Reply = 1 if success
00302 *       = 0 if zero-length file exists.
00303 *       = -1 if failure to load calendar file
00304 *       = -2 if instance uninitialised.
00305 */
00306 int AlarmCalendar::load()
00307 {
00308     if (!mCalendar)
00309         return -2;
00310 
00311     kdDebug(5950) << "AlarmCalendar::load(): " << mUrl.prettyURL() << endl;
00312     QString tmpFile;
00313     if (!KIO::NetAccess::download(mUrl, tmpFile, MainWindow::mainMainWindow()))
00314     {
00315         kdError(5950) << "AlarmCalendar::load(): Load failure" << endl;
00316         KMessageBox::error(0, i18n("Cannot open calendar:\n%1").arg(mUrl.prettyURL()));
00317         return -1;
00318     }
00319     kdDebug(5950) << "AlarmCalendar::load(): --- Downloaded to " << tmpFile << endl;
00320     mCalendar->setTimeZoneId(QString::null);   // default to the local time zone for reading
00321     bool loaded = mCalendar->load(tmpFile);
00322     mCalendar->setLocalTime();                 // write using local time (i.e. no time zone)
00323     if (!loaded)
00324     {
00325         // Check if the file is zero length
00326         KIO::NetAccess::removeTempFile(tmpFile);
00327         KIO::UDSEntry uds;
00328         KIO::NetAccess::stat(mUrl, uds, MainWindow::mainMainWindow());
00329         KFileItem fi(uds, mUrl);
00330         if (!fi.size())
00331             return 0;     // file is zero length
00332         kdError(5950) << "AlarmCalendar::load(): Error loading calendar file '" << tmpFile << "'" << endl;
00333         KMessageBox::error(0, i18n("Error loading calendar:\n%1\n\nPlease fix or delete the file.").arg(mUrl.prettyURL()));
00334         // load() could have partially populated the calendar, so clear it out
00335         mCalendar->close();
00336         delete mCalendar;
00337         mCalendar = 0;
00338         return -1;
00339     }
00340     if (!mLocalFile.isEmpty())
00341         KIO::NetAccess::removeTempFile(mLocalFile);   // removes it only if it IS a temporary file
00342     mLocalFile = tmpFile;
00343 
00344     CalendarCompat::fix(*mCalendar, mLocalFile);   // convert events to current KAlarm format for when calendar is saved
00345     mOpen = true;
00346     return 1;
00347 }
00348 
00349 /******************************************************************************
00350 * Reload the calendar file into memory.
00351 */
00352 bool AlarmCalendar::reload()
00353 {
00354     if (!mCalendar)
00355         return false;
00356     kdDebug(5950) << "AlarmCalendar::reload(): " << mUrl.prettyURL() << endl;
00357     close();
00358     bool result = open();
00359     return result;
00360 }
00361 
00362 /******************************************************************************
00363 * Save the calendar from memory to file.
00364 * If a filename is specified, create a new calendar file.
00365 */
00366 bool AlarmCalendar::saveCal(const QString& newFile)
00367 {
00368     if (!mCalendar  ||  !mOpen && newFile.isNull())
00369         return false;
00370 
00371     kdDebug(5950) << "AlarmCalendar::saveCal(\"" << newFile << "\", " << mType << ")\n";
00372     QString saveFilename = newFile.isNull() ? mLocalFile : newFile;
00373     if (mVCal  &&  newFile.isNull()  &&  mUrl.isLocalFile())
00374         saveFilename = mICalUrl.path();
00375     if (!mCalendar->save(saveFilename, new ICalFormat))
00376     {
00377         kdError(5950) << "AlarmCalendar::saveCal(" << saveFilename << "): failed.\n";
00378         KMessageBox::error(0, i18n("Failed to save calendar to\n'%1'").arg(mICalUrl.prettyURL()));
00379         return false;
00380     }
00381 
00382     if (!mICalUrl.isLocalFile())
00383     {
00384         if (!KIO::NetAccess::upload(saveFilename, mICalUrl, MainWindow::mainMainWindow()))
00385         {
00386             kdError(5950) << "AlarmCalendar::saveCal(" << saveFilename << "): upload failed.\n";
00387             KMessageBox::error(0, i18n("Cannot upload calendar to\n'%1'").arg(mICalUrl.prettyURL()));
00388             return false;
00389         }
00390     }
00391 
00392     if (mVCal)
00393     {
00394         // The file was in vCalendar format, but has now been saved in iCalendar format.
00395         // Save the change in the config file.
00396         if (!mConfigKey.isNull())
00397         {
00398             KConfig* config = kapp->config();
00399             config->setGroup(QString::fromLatin1("General"));
00400             config->writePathEntry(mConfigKey, mICalUrl.path());
00401             config->sync();
00402         }
00403         mUrl  = mICalUrl;
00404         mVCal = false;
00405     }
00406 
00407     mUpdateSave = false;
00408     emit calendarSaved(this);
00409     return true;
00410 }
00411 
00412 /******************************************************************************
00413 * Delete any temporary file at program exit.
00414 */
00415 void AlarmCalendar::close()
00416 {
00417     if (!mLocalFile.isEmpty())
00418     {
00419         KIO::NetAccess::removeTempFile(mLocalFile);   // removes it only if it IS a temporary file
00420         mLocalFile = "";
00421     }
00422     if (mCalendar)
00423     {
00424         mCalendar->close();
00425         delete mCalendar;
00426         mCalendar = 0;
00427     }
00428     mOpen = false;
00429 }
00430 
00431 /******************************************************************************
00432 * Flag the start of a group of calendar update calls.
00433 * The purpose is to avoid multiple calendar saves during a group of operations.
00434 */
00435 void AlarmCalendar::startUpdate()
00436 {
00437     ++mUpdateCount;
00438 }
00439 
00440 /******************************************************************************
00441 * Flag the end of a group of calendar update calls.
00442 * The calendar is saved if appropriate.
00443 */
00444 void AlarmCalendar::endUpdate()
00445 {
00446     if (mUpdateCount > 0)
00447         --mUpdateCount;
00448     if (!mUpdateCount)
00449     {
00450         if (mUpdateSave)
00451             saveCal();
00452     }
00453 }
00454 
00455 /******************************************************************************
00456 * Save the calendar, or flag it for saving if in a group of calendar update calls.
00457 */
00458 void AlarmCalendar::save()
00459 {
00460     if (mUpdateCount)
00461         mUpdateSave = true;
00462     else
00463         saveCal();
00464 }
00465 
00466 #if 0
00467 /******************************************************************************
00468 * If it is VCal format, convert the calendar URL to ICal and save the new URL
00469 * in the config file.
00470 */
00471 void AlarmCalendar::convertToICal()
00472 {
00473     if (mVCal)
00474     {
00475         if (!mConfigKey.isNull())
00476         {
00477             KConfig* config = kapp->config();
00478             config->setGroup(QString::fromLatin1("General"));
00479             config->writePathEntry(mConfigKey, mICalUrl.path());
00480             config->sync();
00481         }
00482         mUrl  = mICalUrl;
00483         mVCal = false;
00484     }
00485 }
00486 #endif
00487 
00488 /******************************************************************************
00489 * Set the number of days to keep alarms.
00490 * Alarms which are older are purged immediately, and at the start of each day.
00491 */
00492 void AlarmCalendar::setPurgeDays(int days)
00493 {
00494     if (days != mPurgeDays)
00495     {
00496         int oldDays = mPurgeDays;
00497         mPurgeDays = days;
00498         if (mPurgeDays <= 0)
00499             StartOfDayTimer::disconnect(this);
00500         if (oldDays < 0  ||  days >= 0 && days < oldDays)
00501         {
00502             // Alarms are now being kept for less long, so purge them
00503             if (open())
00504                 slotPurge();
00505         }
00506         else if (mPurgeDays > 0)
00507             startPurgeTimer();
00508     }
00509 }
00510 
00511 /******************************************************************************
00512 * Called at the start of each day by the purge timer.
00513 * Purge all events from the calendar whose end time is longer ago than 'mPurgeDays'.
00514 */
00515 void AlarmCalendar::slotPurge()
00516 {
00517     purge(mPurgeDays);
00518     startPurgeTimer();
00519 }
00520 
00521 /******************************************************************************
00522 * Purge all events from the calendar whose end time is longer ago than
00523 * 'daysToKeep'. All events are deleted if 'daysToKeep' is zero.
00524 */
00525 void AlarmCalendar::purge(int daysToKeep)
00526 {
00527     if (mPurgeDaysQueued < 0  ||  daysToKeep < mPurgeDaysQueued)
00528         mPurgeDaysQueued = daysToKeep;
00529 
00530     // Do the purge once any other current operations are completed
00531     theApp()->processQueue();
00532 }
00533 
00534 /******************************************************************************
00535 * This method must only be called from the main KAlarm queue processing loop,
00536 * to prevent asynchronous calendar operations interfering with one another.
00537 *
00538 * Purge all events from the calendar whose end time is longer ago than 'daysToKeep'.
00539 * All events are deleted if 'daysToKeep' is zero.
00540 * The calendar must already be open.
00541 */
00542 void AlarmCalendar::purgeIfQueued()
00543 {
00544     if (mPurgeDaysQueued >= 0)
00545     {
00546         if (open())
00547         {
00548             kdDebug(5950) << "AlarmCalendar::purgeIfQueued(" << mPurgeDaysQueued << ")\n";
00549             bool changed = false;
00550             QDate cutoff = QDate::currentDate().addDays(-mPurgeDaysQueued);
00551             Event::List events = mCalendar->rawEvents();
00552             for (Event::List::ConstIterator it = events.begin();  it != events.end();  ++it)
00553             {
00554                 Event* kcalEvent = *it;
00555                 if (!mPurgeDaysQueued  ||  kcalEvent->created().date() < cutoff)
00556                 {
00557                     mCalendar->deleteEvent(kcalEvent);
00558                     changed = true;
00559                 }
00560             }
00561             if (changed)
00562             {
00563                 saveCal();
00564                 emit purged();
00565             }
00566             mPurgeDaysQueued = -1;
00567         }
00568     }
00569 }
00570 
00571 
00572 /******************************************************************************
00573 * Start the purge timer to expire at the start of the next day (using the user-
00574 * defined start-of-day time).
00575 */
00576 void AlarmCalendar::startPurgeTimer()
00577 {
00578     if (mPurgeDays > 0)
00579         StartOfDayTimer::connect(this, SLOT(slotPurge()));
00580 }
00581 
00582 /******************************************************************************
00583 * Add the specified event to the calendar.
00584 * If it is the active calendar and 'useEventID' is false, a new event ID is
00585 * created. In all other cases, the event ID is taken from 'event'.
00586 * 'event' is updated with the actual event ID.
00587 * Reply = the KCal::Event as written to the calendar.
00588 */
00589 Event* AlarmCalendar::addEvent(KAEvent& event, bool useEventID)
00590 {
00591     if (!mOpen)
00592         return 0;
00593     QString id = event.id();
00594     Event* kcalEvent = new Event;
00595     if (mType == KAEvent::ACTIVE)
00596     {
00597         if (id.isEmpty())
00598             useEventID = false;
00599         if (!useEventID)
00600             event.setEventID(kcalEvent->uid());
00601     }
00602     else
00603     {
00604         if (id.isEmpty())
00605             id = kcalEvent->uid();
00606         useEventID = true;
00607     }
00608     if (useEventID)
00609     {
00610         id = KAEvent::uid(id, mType);
00611         event.setEventID(id);
00612         kcalEvent->setUid(id);
00613     }
00614     event.updateKCalEvent(*kcalEvent, false, (mType == KAEvent::EXPIRED), true);
00615     mCalendar->addEvent(kcalEvent);
00616     event.clearUpdated();
00617     return kcalEvent;
00618 }
00619 
00620 /******************************************************************************
00621 * Update the specified event in the calendar with its new contents.
00622 * The event retains the same ID.
00623 */
00624 void AlarmCalendar::updateEvent(const KAEvent& evnt)
00625 {
00626     if (mOpen)
00627     {
00628         Event* kcalEvent = event(evnt.id());
00629         if (kcalEvent)
00630         {
00631             evnt.updateKCalEvent(*kcalEvent);
00632             evnt.clearUpdated();
00633         }
00634     }
00635 }
00636 
00637 /******************************************************************************
00638 * Delete the specified event from the calendar, if it exists.
00639 * The calendar is then optionally saved.
00640 */
00641 void AlarmCalendar::deleteEvent(const QString& eventID, bool saveit)
00642 {
00643     if (mOpen)
00644     {
00645         Event* kcalEvent = event(eventID);
00646         if (kcalEvent)
00647         {
00648             mCalendar->deleteEvent(kcalEvent);
00649             if (saveit)
00650                 save();
00651         }
00652     }
00653 }
00654 
00655 /******************************************************************************
00656 * Emit a signal to indicate whether the calendar is empty.
00657 */
00658 void AlarmCalendar::emitEmptyStatus()
00659 {
00660     emit emptyStatus(events().isEmpty());
00661 }
00662 
00663 /******************************************************************************
00664 * Return the event with the specified ID.
00665 */
00666 KCal::Event* AlarmCalendar::event(const QString& uniqueID)
00667 {
00668     return mCalendar ?  mCalendar->event(uniqueID) : 0;
00669 }
00670 
00671 /******************************************************************************
00672 * Return all events in the calendar which contain alarms.
00673 */
00674 KCal::Event::List AlarmCalendar::events()
00675 {
00676     if (!mCalendar)
00677         return KCal::Event::List();
00678     KCal::Event::List list = mCalendar->rawEvents();
00679     KCal::Event::List::Iterator it = list.begin();
00680     while (it != list.end())
00681     {
00682         if ((*it)->alarms().isEmpty())
00683             it = list.remove(it);
00684         else
00685             ++it;
00686     }
00687     return list;
00688 }
00689 
00690 /******************************************************************************
00691 * Return all events which have alarms falling within the specified time range.
00692 */
00693 Event::List AlarmCalendar::eventsWithAlarms(const QDateTime& from, const QDateTime& to)
00694 {
00695     kdDebug(5950) << "AlarmCalendar::eventsWithAlarms(" << from.toString() << " - " << to.toString() << ")\n";
00696     Event::List evnts;
00697     if (!mCalendar)
00698         return evnts;
00699     QDateTime dt;
00700     Event::List allEvents = mCalendar->rawEvents();
00701     for (Event::List::ConstIterator it = allEvents.begin();  it != allEvents.end();  ++it)
00702     {
00703         Event* e = *it;
00704         bool recurs = e->doesRecur();
00705         int  endOffset = 0;
00706         bool endOffsetValid = false;
00707         const Alarm::List& alarms = e->alarms();
00708         for (Alarm::List::ConstIterator ait = alarms.begin();  ait != alarms.end();  ++ait)
00709         {
00710             Alarm* alarm = *ait;
00711             if (alarm->enabled())
00712             {
00713                 if (recurs)
00714                 {
00715                     if (alarm->hasTime())
00716                         dt = alarm->time();
00717                     else
00718                     {
00719                         // The alarm time is defined by an offset from the event start or end time.
00720                         // Find the offset from the event start time, which is also used as the
00721                         // offset from the recurrence time.
00722                         int offset = 0;
00723                         if (alarm->hasStartOffset())
00724                             offset = alarm->startOffset().asSeconds();
00725                         else if (alarm->hasEndOffset())
00726                         {
00727                             if (!endOffsetValid)
00728                             {
00729                                 endOffset = e->hasDuration() ? e->duration() : e->hasEndDate() ? e->dtStart().secsTo(e->dtEnd()) : 0;
00730                                 endOffsetValid = true;
00731                             }
00732                             offset = alarm->endOffset().asSeconds() + endOffset;
00733                         }
00734                         // Adjust the 'from' date/time and find the next recurrence at or after it
00735                         QDateTime pre = from.addSecs(-offset - 1);
00736                         if (e->doesFloat()  &&  pre.time() < Preferences::startOfDay())
00737                             pre = pre.addDays(-1);    // today's recurrence (if today recurs) is still to come
00738                         dt = e->recurrence()->getNextDateTime(pre);
00739                         if (!dt.isValid())
00740                             continue;
00741                         dt = dt.addSecs(offset);
00742                     }
00743                 }
00744                 else
00745                     dt = alarm->time();
00746                 if (dt >= from  &&  dt <= to)
00747                 {
00748                     kdDebug(5950) << "AlarmCalendar::events() '" << e->summary()
00749                                   << "': " << dt.toString() << endl;
00750                     evnts.append(e);
00751                     break;
00752                 }
00753             }
00754         }
00755     }
00756     return evnts;
00757 }
KDE Home | KDE Accessibility Home | Description of Access Keys