/* * alarmcalendar.cpp - KAlarm calendar file access * Program: kalarm * Copyright © 2001-2006,2009 by David Jarvie * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include } #include #include #include #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 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() { TDEConfig* 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, TDEConfig* 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 (!TDEIO::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 (!TDEIO::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 TDEIO::NetAccess::removeTempFile(tmpFile); TDEIO::UDSEntry uds; TDEIO::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()) TDEIO::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 (!TDEIO::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()) { TDEConfig* 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()) { TDEIO::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 (!TDEStandardDirs::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 (!TDEIO::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) TDEIO::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()) { TDEConfig* 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, TQ_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; }