You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdepim/kalarm/alarmcalendar.cpp

909 lines
27 KiB

/*
* alarmcalendar.cpp - KAlarm calendar file access
* Program: kalarm
* Copyright © 2001-2006,2009 by David Jarvie <djarvie@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kalarm.h"
#include <unistd.h>
#include <time.h>
#include <tqfile.h>
#include <textstream.h>
#include <tqregexp.h>
#include <tqtimer.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kstandarddirs.h>
#include <kstaticdeleter.h>
#include <kconfig.h>
#include <kaboutdata.h>
#include <kio/netaccess.h>
#include <kfileitem.h>
#include <ktempfile.h>
#include <kfiledialog.h>
#include <dcopclient.h>
#include <kdebug.h>
extern "C" {
#include <libical/ical.h>
}
#include <libkcal/vcaldrag.h>
#include <libkcal/vcalformat.h>
#include <libkcal/icalformat.h>
#include "calendarcompat.h"
#include "daemon.h"
#include "functions.h"
#include "kalarmapp.h"
#include "mainwindow.h"
#include "preferences.h"
#include "startdaytimer.h"
#include "alarmcalendar.moc"
using namespace KCal;
TQString AlarmCalendar::icalProductId()
{
return TQString::fromLatin1("-//K Desktop Environment//NONSGML " KALARM_NAME " %1//EN").arg(KAlarm::currentCalendarVersionString());
}
static const KAEvent::Status eventTypes[AlarmCalendar::NCALS] = {
KAEvent::ACTIVE, KAEvent::EXPIRED, KAEvent::DISPLAYING, KAEvent::TEMPLATE
};
static const TQString calendarNames[AlarmCalendar::NCALS] = {
TQString::fromLatin1("calendar.ics"),
TQString::fromLatin1("expired.ics"),
TQString::fromLatin1("displaying.ics"),
TQString::fromLatin1("template.ics")
};
static KStaticDeleter<AlarmCalendar> calendarDeleter[AlarmCalendar::NCALS]; // ensure that the calendar destructors are called
AlarmCalendar* AlarmCalendar::mCalendars[NCALS] = { 0, 0, 0, 0 };
/******************************************************************************
* Initialise the alarm calendars, and ensure that their file names are different.
* There are 4 calendars:
* 1) A user-independent one containing the active alarms;
* 2) A historical one containing expired alarms;
* 3) A user-specific one which contains details of alarms which are currently
* being displayed to that user and which have not yet been acknowledged;
* 4) One containing alarm templates.
* Reply = true if success, false if calendar name error.
*/
bool AlarmCalendar::initialiseCalendars()
{
KConfig* config = kapp->config();
config->setGroup(TQString::fromLatin1("General"));
TQString activeKey = TQString::fromLatin1("Calendar");
TQString expiredKey = TQString::fromLatin1("ExpiredCalendar");
TQString templateKey = TQString::fromLatin1("TemplateCalendar");
TQString displayCal, activeCal, expiredCal, templateCal;
calendarDeleter[ACTIVE].setObject(mCalendars[ACTIVE], createCalendar(ACTIVE, config, activeCal, activeKey));
calendarDeleter[EXPIRED].setObject(mCalendars[EXPIRED], createCalendar(EXPIRED, config, expiredCal, expiredKey));
calendarDeleter[DISPLAY].setObject(mCalendars[DISPLAY], createCalendar(DISPLAY, config, displayCal));
calendarDeleter[TEMPLATE].setObject(mCalendars[TEMPLATE], createCalendar(TEMPLATE, config, templateCal, templateKey));
TQString errorKey1, errorKey2;
if (activeCal == displayCal)
errorKey1 = activeKey;
else if (expiredCal == displayCal)
errorKey1 = expiredKey;
else if (templateCal == displayCal)
errorKey1 = templateKey;
if (!errorKey1.isNull())
{
kdError(5950) << "AlarmCalendar::initialiseCalendars(): '" << errorKey1 << "' calendar name = display calendar name\n";
TQString file = config->readPathEntry(errorKey1);
KAlarmApp::displayFatalError(i18n("%1: file name not permitted: %2").arg(errorKey1).arg(file));
return false;
}
if (activeCal == expiredCal)
{
errorKey1 = activeKey;
errorKey2 = expiredKey;
}
else if (activeCal == templateCal)
{
errorKey1 = activeKey;
errorKey2 = templateKey;
}
else if (expiredCal == templateCal)
{
errorKey1 = expiredKey;
errorKey2 = templateKey;
}
if (!errorKey1.isNull())
{
kdError(5950) << "AlarmCalendar::initialiseCalendars(): calendar names clash: " << errorKey1 << ", " << errorKey2 << endl;
KAlarmApp::displayFatalError(i18n("%1, %2: file names must be different").arg(errorKey1).arg(errorKey2));
return false;
}
if (!mCalendars[ACTIVE]->valid())
{
TQString path = mCalendars[ACTIVE]->path();
kdError(5950) << "AlarmCalendar::initialiseCalendars(): invalid name: " << path << endl;
KAlarmApp::displayFatalError(i18n("Invalid calendar file name: %1").arg(path));
return false;
}
return true;
}
/******************************************************************************
* Create an alarm calendar instance.
* If 'configKey' is non-null, the calendar will be converted to ICal format.
*/
AlarmCalendar* AlarmCalendar::createCalendar(CalID type, KConfig* config, TQString& writePath, const TQString& configKey)
{
static TQRegExp vcsRegExp(TQString::fromLatin1("\\.vcs$"));
static TQString ical = TQString::fromLatin1(".ics");
if (configKey.isNull())
{
writePath = locateLocal("appdata", calendarNames[type]);
return new AlarmCalendar(writePath, type);
}
else
{
TQString readPath = config->readPathEntry(configKey, locateLocal("appdata", calendarNames[type]));
writePath = readPath;
writePath.replace(vcsRegExp, ical);
return new AlarmCalendar(readPath, type, writePath, configKey);
}
}
/******************************************************************************
* Terminate access to all calendars.
*/
void AlarmCalendar::terminateCalendars()
{
for (int i = 0; i < NCALS; ++i)
{
calendarDeleter[i].destructObject();
mCalendars[i] = 0;
}
}
/******************************************************************************
* Return a calendar, opening it first if not already open.
* Reply = calendar instance
* = 0 if calendar could not be opened.
*/
AlarmCalendar* AlarmCalendar::calendarOpen(CalID id)
{
AlarmCalendar* cal = mCalendars[id];
if (!cal->mPurgeDays)
return 0; // all events are automatically purged from the calendar
if (cal->open())
return cal;
kdError(5950) << "AlarmCalendar::calendarOpen(" << calendarNames[id] << "): open error\n";
return 0;
}
/******************************************************************************
* Find and return the event with the specified ID.
* The calendar searched is determined by the calendar identifier in the ID.
*/
const KCal::Event* AlarmCalendar::getEvent(const TQString& uniqueID)
{
if (uniqueID.isEmpty())
return 0;
CalID calID;
switch (KAEvent::uidStatus(uniqueID))
{
case KAEvent::ACTIVE: calID = ACTIVE; break;
case KAEvent::TEMPLATE: calID = TEMPLATE; break;
case KAEvent::EXPIRED: calID = EXPIRED; break;
case KAEvent::DISPLAYING: calID = DISPLAY; break;
default:
return 0;
}
AlarmCalendar* cal = calendarOpen(calID);
if (!cal)
return 0;
return cal->event(uniqueID);
}
/******************************************************************************
* Constructor.
* If 'icalPath' is non-null, the file will be always be saved in ICal format.
* If 'configKey' is also non-null, that config file entry will be updated when
* the file is saved in ICal format.
*/
AlarmCalendar::AlarmCalendar(const TQString& path, CalID type, const TQString& icalPath,
const TQString& configKey)
: mCalendar(0),
mConfigKey(icalPath.isNull() ? TQString() : configKey),
mType(eventTypes[type]),
mPurgeDays(-1), // default to not purging
mOpen(false),
mPurgeDaysQueued(-1),
mUpdateCount(0),
mUpdateSave(false)
{
mUrl.setPath(path); // N.B. constructor mUrl(path) doesn't work with UNIX paths
mICalUrl.setPath(icalPath.isNull() ? path : icalPath);
mVCal = (icalPath.isNull() || path != icalPath); // is the calendar in ICal or VCal format?
}
AlarmCalendar::~AlarmCalendar()
{
close();
}
/******************************************************************************
* Open the calendar file if not already open, and load it into memory.
*/
bool AlarmCalendar::open()
{
if (mOpen)
return true;
if (!mUrl.isValid())
return false;
kdDebug(5950) << "AlarmCalendar::open(" << mUrl.prettyURL() << ")\n";
if (!mCalendar)
mCalendar = new CalendarLocal(TQString::fromLatin1("UTC"));
mCalendar->setLocalTime(); // write out using local time (i.e. no time zone)
// Check for file's existence, assuming that it does exist when uncertain,
// to avoid overwriting it.
if (!KIO::NetAccess::exists(mUrl, true, MainWindow::mainMainWindow()))
{
// The calendar file doesn't yet exist, so create it
if (create())
load();
}
else
{
// Load the existing calendar file
if (load() == 0)
{
if (create()) // zero-length file - create a new one
load();
}
}
if (!mOpen)
{
delete mCalendar;
mCalendar = 0;
}
return mOpen;
}
/******************************************************************************
* Private method to create a new calendar file.
* It is always created in iCalendar format.
*/
bool AlarmCalendar::create()
{
if (mICalUrl.isLocalFile())
return saveCal(mICalUrl.path());
else
{
KTempFile tmpFile;
return saveCal(tmpFile.name());
}
}
/******************************************************************************
* Load the calendar file into memory.
* Reply = 1 if success
* = 0 if zero-length file exists.
* = -1 if failure to load calendar file
* = -2 if instance uninitialised.
*/
int AlarmCalendar::load()
{
if (!mCalendar)
return -2;
kdDebug(5950) << "AlarmCalendar::load(): " << mUrl.prettyURL() << endl;
TQString tmpFile;
if (!KIO::NetAccess::download(mUrl, tmpFile, MainWindow::mainMainWindow()))
{
kdError(5950) << "AlarmCalendar::load(): Load failure" << endl;
KMessageBox::error(0, i18n("Cannot open calendar:\n%1").arg(mUrl.prettyURL()));
return -1;
}
kdDebug(5950) << "AlarmCalendar::load(): --- Downloaded to " << tmpFile << endl;
mCalendar->setTimeZoneId(TQString()); // default to the local time zone for reading
bool loaded = mCalendar->load(tmpFile);
mCalendar->setLocalTime(); // write using local time (i.e. no time zone)
if (!loaded)
{
// Check if the file is zero length
KIO::NetAccess::removeTempFile(tmpFile);
KIO::UDSEntry uds;
KIO::NetAccess::stat(mUrl, uds, MainWindow::mainMainWindow());
KFileItem fi(uds, mUrl);
if (!fi.size())
return 0; // file is zero length
kdError(5950) << "AlarmCalendar::load(): Error loading calendar file '" << tmpFile << "'" << endl;
KMessageBox::error(0, i18n("Error loading calendar:\n%1\n\nPlease fix or delete the file.").arg(mUrl.prettyURL()));
// load() could have partially populated the calendar, so clear it out
mCalendar->close();
delete mCalendar;
mCalendar = 0;
return -1;
}
if (!mLocalFile.isEmpty())
KIO::NetAccess::removeTempFile(mLocalFile); // removes it only if it IS a temporary file
mLocalFile = tmpFile;
CalendarCompat::fix(*mCalendar, mLocalFile); // convert events to current KAlarm format for when calendar is saved
mOpen = true;
return 1;
}
/******************************************************************************
* Reload the calendar file into memory.
*/
bool AlarmCalendar::reload()
{
if (!mCalendar)
return false;
kdDebug(5950) << "AlarmCalendar::reload(): " << mUrl.prettyURL() << endl;
close();
bool result = open();
return result;
}
/******************************************************************************
* Save the calendar from memory to file.
* If a filename is specified, create a new calendar file.
*/
bool AlarmCalendar::saveCal(const TQString& newFile)
{
if (!mCalendar || !mOpen && newFile.isNull())
return false;
kdDebug(5950) << "AlarmCalendar::saveCal(\"" << newFile << "\", " << mType << ")\n";
TQString saveFilename = newFile.isNull() ? mLocalFile : newFile;
if (mVCal && newFile.isNull() && mUrl.isLocalFile())
saveFilename = mICalUrl.path();
if (!mCalendar->save(saveFilename, new ICalFormat))
{
kdError(5950) << "AlarmCalendar::saveCal(" << saveFilename << "): failed.\n";
KMessageBox::error(0, i18n("Failed to save calendar to\n'%1'").arg(mICalUrl.prettyURL()));
return false;
}
if (!mICalUrl.isLocalFile())
{
if (!KIO::NetAccess::upload(saveFilename, mICalUrl, MainWindow::mainMainWindow()))
{
kdError(5950) << "AlarmCalendar::saveCal(" << saveFilename << "): upload failed.\n";
KMessageBox::error(0, i18n("Cannot upload calendar to\n'%1'").arg(mICalUrl.prettyURL()));
return false;
}
}
if (mVCal)
{
// The file was in vCalendar format, but has now been saved in iCalendar format.
// Save the change in the config file.
if (!mConfigKey.isNull())
{
KConfig* config = kapp->config();
config->setGroup(TQString::fromLatin1("General"));
config->writePathEntry(mConfigKey, mICalUrl.path());
config->sync();
}
mUrl = mICalUrl;
mVCal = false;
}
mUpdateSave = false;
emit calendarSaved(this);
return true;
}
/******************************************************************************
* Delete any temporary file at program exit.
*/
void AlarmCalendar::close()
{
if (!mLocalFile.isEmpty())
{
KIO::NetAccess::removeTempFile(mLocalFile); // removes it only if it IS a temporary file
mLocalFile = "";
}
if (mCalendar)
{
mCalendar->close();
delete mCalendar;
mCalendar = 0;
}
mOpen = false;
}
/******************************************************************************
* Import alarms from an external calendar and merge them into KAlarm's calendar.
* The alarms are given new unique event IDs.
* Parameters: parent = parent widget for error message boxes
* Reply = true if all alarms in the calendar were successfully imported
* = false if any alarms failed to be imported.
*/
bool AlarmCalendar::importAlarms(TQWidget* parent)
{
KURL url = KFileDialog::getOpenURL(TQString::fromLatin1(":importalarms"),
TQString::fromLatin1("*.vcs *.ics|%1").arg(i18n("Calendar Files")), parent);
if (url.isEmpty())
{
kdError(5950) << "AlarmCalendar::importAlarms(): Empty URL" << endl;
return false;
}
if (!url.isValid())
{
kdDebug(5950) << "AlarmCalendar::importAlarms(): Invalid URL" << endl;
return false;
}
kdDebug(5950) << "AlarmCalendar::importAlarms(" << url.prettyURL() << ")" << endl;
bool success = true;
TQString filename;
bool local = url.isLocalFile();
if (local)
{
filename = url.path();
if (!KStandardDirs::exists(filename))
{
kdDebug(5950) << "AlarmCalendar::importAlarms(): File '" << url.prettyURL() << "' not found" << endl;
KMessageBox::error(parent, i18n("Could not load calendar '%1'.").arg(url.prettyURL()));
return false;
}
}
else
{
if (!KIO::NetAccess::download(url, filename, MainWindow::mainMainWindow()))
{
kdError(5950) << "AlarmCalendar::importAlarms(): Download failure" << endl;
KMessageBox::error(parent, i18n("Cannot download calendar:\n%1").arg(url.prettyURL()));
return false;
}
kdDebug(5950) << "--- Downloaded to " << filename << endl;
}
// Read the calendar and add its alarms to the current calendars
CalendarLocal cal(TQString::fromLatin1("UTC"));
cal.setLocalTime(); // write out using local time (i.e. no time zone)
success = cal.load(filename);
if (!success)
{
kdDebug(5950) << "AlarmCalendar::importAlarms(): error loading calendar '" << filename << "'" << endl;
KMessageBox::error(parent, i18n("Could not load calendar '%1'.").arg(url.prettyURL()));
}
else
{
CalendarCompat::fix(cal, filename);
bool saveActive = false;
bool saveExpired = false;
bool saveTemplate = false;
AlarmCalendar* active = activeCalendar();
AlarmCalendar* expired = expiredCalendar();
AlarmCalendar* templat = 0;
AlarmCalendar* acal;
Event::List events = cal.rawEvents();
for (Event::List::ConstIterator it = events.begin(); it != events.end(); ++it)
{
const Event* event = *it;
if (event->alarms().isEmpty() || !KAEvent(*event).valid())
continue; // ignore events without alarms, or usable alarms
KAEvent::Status type = KAEvent::uidStatus(event->uid());
switch (type)
{
case KAEvent::ACTIVE:
acal = active;
saveActive = true;
break;
case KAEvent::EXPIRED:
acal = expired;
saveExpired = true;
break;
case KAEvent::TEMPLATE:
if (!templat)
templat = templateCalendarOpen();
acal = templat;
saveTemplate = true;
break;
default:
continue;
}
if (!acal)
continue;
Event* newev = new Event(*event);
// If there is a display alarm without display text, use the event
// summary text instead.
if (type == KAEvent::ACTIVE && !newev->summary().isEmpty())
{
const Alarm::List& alarms = newev->alarms();
for (Alarm::List::ConstIterator ait = alarms.begin(); ait != alarms.end(); ++ait)
{
Alarm* alarm = *ait;
if (alarm->type() == Alarm::Display && alarm->text().isEmpty())
alarm->setText(newev->summary());
}
newev->setSummary(TQString()); // KAlarm only uses summary for template names
}
// Give the event a new ID and add it to the calendar
newev->setUid(KAEvent::uid(CalFormat::createUniqueId(), type));
if (!acal->mCalendar->addEvent(newev))
success = false;
}
// Save any calendars which have been modified
if (saveActive)
active->saveCal();
if (saveExpired)
expired->saveCal();
if (saveTemplate)
templat->saveCal();
}
if (!local)
KIO::NetAccess::removeTempFile(filename);
return success;
}
/******************************************************************************
* Flag the start of a group of calendar update calls.
* The purpose is to avoid multiple calendar saves during a group of operations.
*/
void AlarmCalendar::startUpdate()
{
++mUpdateCount;
}
/******************************************************************************
* Flag the end of a group of calendar update calls.
* The calendar is saved if appropriate.
*/
bool AlarmCalendar::endUpdate()
{
if (mUpdateCount > 0)
--mUpdateCount;
if (!mUpdateCount)
{
if (mUpdateSave)
return saveCal();
}
return true;
}
/******************************************************************************
* Save the calendar, or flag it for saving if in a group of calendar update calls.
*/
bool AlarmCalendar::save()
{
if (mUpdateCount)
{
mUpdateSave = true;
return true;
}
else
return saveCal();
}
#if 0
/******************************************************************************
* If it is VCal format, convert the calendar URL to ICal and save the new URL
* in the config file.
*/
void AlarmCalendar::convertToICal()
{
if (mVCal)
{
if (!mConfigKey.isNull())
{
KConfig* config = kapp->config();
config->setGroup(TQString::fromLatin1("General"));
config->writePathEntry(mConfigKey, mICalUrl.path());
config->sync();
}
mUrl = mICalUrl;
mVCal = false;
}
}
#endif
/******************************************************************************
* Set the number of days to keep alarms.
* Alarms which are older are purged immediately, and at the start of each day.
*/
void AlarmCalendar::setPurgeDays(int days)
{
if (days != mPurgeDays)
{
int oldDays = mPurgeDays;
mPurgeDays = days;
if (mPurgeDays <= 0)
StartOfDayTimer::disconnect(this);
if (oldDays < 0 || days >= 0 && days < oldDays)
{
// Alarms are now being kept for less long, so purge them
if (open())
slotPurge();
}
else if (mPurgeDays > 0)
startPurgeTimer();
}
}
/******************************************************************************
* Called at the start of each day by the purge timer.
* Purge all events from the calendar whose end time is longer ago than 'mPurgeDays'.
*/
void AlarmCalendar::slotPurge()
{
purge(mPurgeDays);
startPurgeTimer();
}
/******************************************************************************
* Purge all events from the calendar whose end time is longer ago than
* 'daysToKeep'. All events are deleted if 'daysToKeep' is zero.
*/
void AlarmCalendar::purge(int daysToKeep)
{
if (mPurgeDaysQueued < 0 || daysToKeep < mPurgeDaysQueued)
mPurgeDaysQueued = daysToKeep;
// Do the purge once any other current operations are completed
theApp()->processQueue();
}
/******************************************************************************
* This method must only be called from the main KAlarm queue processing loop,
* to prevent asynchronous calendar operations interfering with one another.
*
* Purge all events from the calendar whose end time is longer ago than 'daysToKeep'.
* All events are deleted if 'daysToKeep' is zero.
* The calendar must already be open.
*/
void AlarmCalendar::purgeIfQueued()
{
if (mPurgeDaysQueued >= 0)
{
if (open())
{
kdDebug(5950) << "AlarmCalendar::purgeIfQueued(" << mPurgeDaysQueued << ")\n";
bool changed = false;
TQDate cutoff = TQDate::currentDate().addDays(-mPurgeDaysQueued);
Event::List events = mCalendar->rawEvents();
for (Event::List::ConstIterator it = events.begin(); it != events.end(); ++it)
{
Event* kcalEvent = *it;
if (!mPurgeDaysQueued || kcalEvent->created().date() < cutoff)
{
mCalendar->deleteEvent(kcalEvent);
changed = true;
}
}
if (changed)
{
saveCal();
emit purged();
}
mPurgeDaysQueued = -1;
}
}
}
/******************************************************************************
* Start the purge timer to expire at the start of the next day (using the user-
* defined start-of-day time).
*/
void AlarmCalendar::startPurgeTimer()
{
if (mPurgeDays > 0)
StartOfDayTimer::connect(this, TQT_SLOT(slotPurge()));
}
/******************************************************************************
* Add the specified event to the calendar.
* If it is the active calendar and 'useEventID' is false, a new event ID is
* created. In all other cases, the event ID is taken from 'event'.
* 'event' is updated with the actual event ID.
* Reply = the KCal::Event as written to the calendar.
*/
Event* AlarmCalendar::addEvent(KAEvent& event, bool useEventID)
{
if (!mOpen)
return 0;
TQString id = event.id();
Event* kcalEvent = new Event;
if (mType == KAEvent::ACTIVE)
{
if (id.isEmpty())
useEventID = false;
if (!useEventID)
event.setEventID(kcalEvent->uid());
}
else
{
if (id.isEmpty())
id = kcalEvent->uid();
useEventID = true;
}
if (useEventID)
{
id = KAEvent::uid(id, mType);
event.setEventID(id);
kcalEvent->setUid(id);
}
event.updateKCalEvent(*kcalEvent, false, (mType == KAEvent::EXPIRED), true);
mCalendar->addEvent(kcalEvent);
event.clearUpdated();
return kcalEvent;
}
/******************************************************************************
* Update the specified event in the calendar with its new contents.
* The event retains the same ID.
*/
void AlarmCalendar::updateEvent(const KAEvent& evnt)
{
if (mOpen)
{
Event* kcalEvent = event(evnt.id());
if (kcalEvent)
{
evnt.updateKCalEvent(*kcalEvent);
evnt.clearUpdated();
if (mType == KAEvent::ACTIVE)
Daemon::savingEvent(evnt.id());
return;
}
}
if (mType == KAEvent::ACTIVE)
Daemon::eventHandled(evnt.id(), false);
}
/******************************************************************************
* Delete the specified event from the calendar, if it exists.
* The calendar is then optionally saved.
*/
bool AlarmCalendar::deleteEvent(const TQString& eventID, bool saveit)
{
if (mOpen)
{
Event* kcalEvent = event(eventID);
if (kcalEvent)
{
mCalendar->deleteEvent(kcalEvent);
if (mType == KAEvent::ACTIVE)
Daemon::savingEvent(eventID);
if (saveit)
return save();
return true;
}
}
if (mType == KAEvent::ACTIVE)
Daemon::eventHandled(eventID, false);
return false;
}
/******************************************************************************
* Emit a signal to indicate whether the calendar is empty.
*/
void AlarmCalendar::emitEmptyStatus()
{
emit emptyStatus(events().isEmpty());
}
/******************************************************************************
* Return the event with the specified ID.
*/
KCal::Event* AlarmCalendar::event(const TQString& uniqueID)
{
return mCalendar ? mCalendar->event(uniqueID) : 0;
}
/******************************************************************************
* Return all events in the calendar which contain usable alarms.
*/
KCal::Event::List AlarmCalendar::events()
{
if (!mCalendar)
return KCal::Event::List();
KCal::Event::List list = mCalendar->rawEvents();
KCal::Event::List::Iterator it = list.begin();
while (it != list.end())
{
KCal::Event* event = *it;
if (event->alarms().isEmpty() || !KAEvent(*event).valid())
it = list.remove(it);
else
++it;
}
return list;
}
/******************************************************************************
* Return all events which have alarms falling within the specified time range.
*/
Event::List AlarmCalendar::eventsWithAlarms(const TQDateTime& from, const TQDateTime& to)
{
kdDebug(5950) << "AlarmCalendar::eventsWithAlarms(" << from.toString() << " - " << to.toString() << ")\n";
Event::List evnts;
TQDateTime dt;
Event::List allEvents = events(); // ignore events without usable alarms
for (Event::List::ConstIterator it = allEvents.begin(); it != allEvents.end(); ++it)
{
Event* e = *it;
bool recurs = e->doesRecur();
int endOffset = 0;
bool endOffsetValid = false;
const Alarm::List& alarms = e->alarms();
for (Alarm::List::ConstIterator ait = alarms.begin(); ait != alarms.end(); ++ait)
{
Alarm* alarm = *ait;
if (alarm->enabled())
{
if (recurs)
{
if (alarm->hasTime())
dt = alarm->time();
else
{
// The alarm time is defined by an offset from the event start or end time.
// Find the offset from the event start time, which is also used as the
// offset from the recurrence time.
int offset = 0;
if (alarm->hasStartOffset())
offset = alarm->startOffset().asSeconds();
else if (alarm->hasEndOffset())
{
if (!endOffsetValid)
{
endOffset = e->hasDuration() ? e->duration() : e->hasEndDate() ? e->dtStart().secsTo(e->dtEnd()) : 0;
endOffsetValid = true;
}
offset = alarm->endOffset().asSeconds() + endOffset;
}
// Adjust the 'from' date/time and find the next recurrence at or after it
TQDateTime pre = from.addSecs(-offset - 1);
if (e->doesFloat() && pre.time() < Preferences::startOfDay())
pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come
dt = e->recurrence()->getNextDateTime(pre);
if (!dt.isValid())
continue;
dt = dt.addSecs(offset);
}
}
else
dt = alarm->time();
if (dt >= from && dt <= to)
{
kdDebug(5950) << "AlarmCalendar::events() '" << e->summary()
<< "': " << dt.toString() << endl;
evnts.append(e);
break;
}
}
}
}
return evnts;
}