diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 460c52653ab0dcca6f19a4f492ed2c5e4e963ab0 (patch) | |
tree | 67208f7c145782a7e90b123b982ca78d88cc2c87 /kalarm/kalarmapp.cpp | |
download | tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.tar.gz tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kalarm/kalarmapp.cpp')
-rw-r--r-- | kalarm/kalarmapp.cpp | 2187 |
1 files changed, 2187 insertions, 0 deletions
diff --git a/kalarm/kalarmapp.cpp b/kalarm/kalarmapp.cpp new file mode 100644 index 000000000..b222eeece --- /dev/null +++ b/kalarm/kalarmapp.cpp @@ -0,0 +1,2187 @@ +/* + * kalarmapp.cpp - the KAlarm application object + * Program: kalarm + * Copyright © 2001-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 <stdlib.h> +#include <ctype.h> +#include <iostream> + +#include <qobjectlist.h> +#include <qtimer.h> +#include <qregexp.h> +#include <qfile.h> + +#include <kcmdlineargs.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <kconfig.h> +#include <kaboutdata.h> +#include <dcopclient.h> +#include <kprocess.h> +#include <ktempfile.h> +#include <kfileitem.h> +#include <kstdguiitem.h> +#include <ktrader.h> +#include <kstaticdeleter.h> +#include <kdebug.h> + +#include <libkcal/calformat.h> + +#include <kalarmd/clientinfo.h> + +#include "alarmcalendar.h" +#include "alarmlistview.h" +#include "birthdaydlg.h" +#include "editdlg.h" +#include "daemon.h" +#include "dcophandler.h" +#include "functions.h" +#include "kamail.h" +#include "karecurrence.h" +#include "mainwindow.h" +#include "messagebox.h" +#include "messagewin.h" +#include "preferences.h" +#include "prefdlg.h" +#include "shellprocess.h" +#include "traywindow.h" +#include "kalarmapp.moc" + +#include <netwm.h> + + +static bool convWakeTime(const QCString& timeParam, QDateTime&, bool& noTime); +static bool convInterval(const QCString& timeParam, KARecurrence::Type&, int& timeInterval, bool allowMonthYear = false); + +/****************************************************************************** +* Find the maximum number of seconds late which a late-cancel alarm is allowed +* to be. This is calculated as the alarm daemon's check interval, plus a few +* seconds leeway to cater for any timing irregularities. +*/ +static inline int maxLateness(int lateCancel) +{ + static const int LATENESS_LEEWAY = 5; + int lc = (lateCancel >= 1) ? (lateCancel - 1)*60 : 0; + return Daemon::maxTimeSinceCheck() + LATENESS_LEEWAY + lc; +} + + +KAlarmApp* KAlarmApp::theInstance = 0; +int KAlarmApp::mActiveCount = 0; +int KAlarmApp::mFatalError = 0; +QString KAlarmApp::mFatalMessage; + + +/****************************************************************************** +* Construct the application. +*/ +KAlarmApp::KAlarmApp() + : KUniqueApplication(), + mInitialised(false), + mDcopHandler(new DcopHandler()), +#ifdef OLD_DCOP + mDcopHandlerOld(new DcopHandlerOld()), +#endif + mTrayWindow(0), + mPendingQuit(false), + mProcessingQueue(false), + mCheckingSystemTray(false), + mSessionClosingDown(false), + mRefreshExpiredAlarms(false), + mSpeechEnabled(false) +{ + Preferences::initialise(); + Preferences::connect(SIGNAL(preferencesChanged()), this, SLOT(slotPreferencesChanged())); + KCal::CalFormat::setApplication(aboutData()->programName(), AlarmCalendar::icalProductId()); + KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type()); + + // Check if the system tray is supported by this window manager + mHaveSystemTray = true; // assume yes in lieu of a test which works + + if (AlarmCalendar::initialiseCalendars()) + { + connect(AlarmCalendar::expiredCalendar(), SIGNAL(purged()), SLOT(slotExpiredPurged())); + + KConfig* config = kapp->config(); + config->setGroup(QString::fromLatin1("General")); + mNoSystemTray = config->readBoolEntry(QString::fromLatin1("NoSystemTray"), false); + mSavedNoSystemTray = mNoSystemTray; + mOldRunInSystemTray = wantRunInSystemTray(); + mDisableAlarmsIfStopped = mOldRunInSystemTray && !mNoSystemTray && Preferences::disableAlarmsIfStopped(); + mStartOfDay = Preferences::startOfDay(); + if (Preferences::hasStartOfDayChanged()) + mStartOfDay.setHMS(100,0,0); // start of day time has changed: flag it as invalid + DateTime::setStartOfDay(mStartOfDay); + mPrefsExpiredColour = Preferences::expiredColour(); + mPrefsExpiredKeepDays = Preferences::expiredKeepDays(); + } + + // Check if the speech synthesis daemon is installed + mSpeechEnabled = (KTrader::self()->query("DCOP/Text-to-Speech", "Name == 'KTTSD'").count() > 0); + if (!mSpeechEnabled) + kdDebug(5950) << "KAlarmApp::KAlarmApp(): speech synthesis disabled (KTTSD not found)" << endl; + // Check if KOrganizer is installed + QString korg = QString::fromLatin1("korganizer"); + mKOrganizerEnabled = !locate("exe", korg).isNull() || !KStandardDirs::findExe(korg).isNull(); + if (!mKOrganizerEnabled) + kdDebug(5950) << "KAlarmApp::KAlarmApp(): KOrganizer options disabled (KOrganizer not found)" << endl; +} + +/****************************************************************************** +*/ +KAlarmApp::~KAlarmApp() +{ + while (!mCommandProcesses.isEmpty()) + { + ProcData* pd = mCommandProcesses.first(); + mCommandProcesses.pop_front(); + delete pd; + } + AlarmCalendar::terminateCalendars(); +} + +/****************************************************************************** +* Return the one and only KAlarmApp instance. +* If it doesn't already exist, it is created first. +*/ +KAlarmApp* KAlarmApp::getInstance() +{ + if (!theInstance) + { + theInstance = new KAlarmApp; + + if (mFatalError) + theInstance->quitFatal(); + else + { + // This is here instead of in the constructor to avoid recursion + Daemon::initialise(); // calendars must be initialised before calling this + } + } + return theInstance; +} + +/****************************************************************************** +* Restore the saved session if required. +*/ +bool KAlarmApp::restoreSession() +{ + if (!isRestored()) + return false; + if (mFatalError) + { + quitFatal(); + return false; + } + + // Process is being restored by session management. + kdDebug(5950) << "KAlarmApp::restoreSession(): Restoring\n"; + ++mActiveCount; + if (!initCheck(true)) // open the calendar file (needed for main windows) + { + --mActiveCount; + quitIf(1, true); // error opening the main calendar - quit + return true; + } + MainWindow* trayParent = 0; + for (int i = 1; KMainWindow::canBeRestored(i); ++i) + { + QString type = KMainWindow::classNameOfToplevel(i); + if (type == QString::fromLatin1("MainWindow")) + { + MainWindow* win = MainWindow::create(true); + win->restore(i, false); + if (win->isHiddenTrayParent()) + trayParent = win; + else + win->show(); + } + else if (type == QString::fromLatin1("MessageWin")) + { + MessageWin* win = new MessageWin; + win->restore(i, false); + if (win->isValid()) + win->show(); + else + delete win; + } + } + initCheck(); // register with the alarm daemon + + // Try to display the system tray icon if it is configured to be autostarted, + // or if we're in run-in-system-tray mode. + if (Preferences::autostartTrayIcon() + || MainWindow::count() && wantRunInSystemTray()) + { + displayTrayIcon(true, trayParent); + // Occasionally for no obvious reason, the main main window is + // shown when it should be hidden, so hide it just to be sure. + if (trayParent) + trayParent->hide(); + } + + --mActiveCount; + quitIf(0); // quit if no windows are open + return true; +} + +/****************************************************************************** +* Called for a KUniqueApplication when a new instance of the application is +* started. +*/ +int KAlarmApp::newInstance() +{ + kdDebug(5950)<<"KAlarmApp::newInstance()\n"; + if (mFatalError) + { + quitFatal(); + return 1; + } + ++mActiveCount; + int exitCode = 0; // default = success + static bool firstInstance = true; + bool dontRedisplay = false; + if (!firstInstance || !isRestored()) + { + QString usage; + KCmdLineArgs* args = KCmdLineArgs::parsedArgs(); + + // Use a 'do' loop which is executed only once to allow easy error exits. + // Errors use 'break' to skip to the end of the function. + + // Note that DCOP handling is only set up once the command line parameters + // have been checked, since we mustn't register with the alarm daemon only + // to quit immediately afterwards. + do + { + #define USAGE(message) { usage = message; break; } + if (args->isSet("stop")) + { + // Stop the alarm daemon + kdDebug(5950)<<"KAlarmApp::newInstance(): stop\n"; + args->clear(); // free up memory + if (!Daemon::stop()) + { + exitCode = 1; + break; + } + dontRedisplay = true; // exit program if no other instances running + } + else + if (args->isSet("reset")) + { + // Reset the alarm daemon, if it's running. + // (If it's not running, it will reset automatically when it eventually starts.) + kdDebug(5950)<<"KAlarmApp::newInstance(): reset\n"; + args->clear(); // free up memory + Daemon::reset(); + dontRedisplay = true; // exit program if no other instances running + } + else + if (args->isSet("tray")) + { + // Display only the system tray icon + kdDebug(5950)<<"KAlarmApp::newInstance(): tray\n"; + args->clear(); // free up memory + if (!mHaveSystemTray) + { + exitCode = 1; + break; + } + if (!initCheck()) // open the calendar, register with daemon + { + exitCode = 1; + break; + } + if (!displayTrayIcon(true)) + { + exitCode = 1; + break; + } + } + else + if (args->isSet("handleEvent") || args->isSet("triggerEvent") || args->isSet("cancelEvent") || args->isSet("calendarURL")) + { + // Display or delete the event with the specified event ID + kdDebug(5950)<<"KAlarmApp::newInstance(): handle event\n"; + EventFunc function = EVENT_HANDLE; + int count = 0; + const char* option = 0; + if (args->isSet("handleEvent")) { function = EVENT_HANDLE; option = "handleEvent"; ++count; } + if (args->isSet("triggerEvent")) { function = EVENT_TRIGGER; option = "triggerEvent"; ++count; } + if (args->isSet("cancelEvent")) { function = EVENT_CANCEL; option = "cancelEvent"; ++count; } + if (!count) + USAGE(i18n("%1 requires %2, %3 or %4").arg(QString::fromLatin1("--calendarURL")).arg(QString::fromLatin1("--handleEvent")).arg(QString::fromLatin1("--triggerEvent")).arg(QString::fromLatin1("--cancelEvent"))) + if (count > 1) + USAGE(i18n("%1, %2, %3 mutually exclusive").arg(QString::fromLatin1("--handleEvent")).arg(QString::fromLatin1("--triggerEvent")).arg(QString::fromLatin1("--cancelEvent"))); + if (!initCheck(true)) // open the calendar, don't register with daemon yet + { + exitCode = 1; + break; + } + if (args->isSet("calendarURL")) + { + QString calendarUrl = args->getOption("calendarURL"); + if (KURL(calendarUrl).url() != AlarmCalendar::activeCalendar()->urlString()) + USAGE(i18n("%1: wrong calendar file").arg(QString::fromLatin1("--calendarURL"))) + } + QString eventID = args->getOption(option); + args->clear(); // free up memory + if (eventID.startsWith(QString::fromLatin1("ad:"))) + { + // It's a notification from the alarm deamon + eventID = eventID.mid(3); + Daemon::queueEvent(eventID); + } + setUpDcop(); // start processing DCOP calls + if (!handleEvent(eventID, function)) + { + exitCode = 1; + break; + } + } + else + if (args->isSet("edit")) + { + QString eventID = args->getOption("edit"); + if (!initCheck()) + { + exitCode = 1; + break; + } + if (!KAlarm::edit(eventID)) + { + USAGE(i18n("%1: Event %2 not found, or not editable").arg(QString::fromLatin1("--edit")).arg(eventID)) + exitCode = 1; + break; + } + } + else + if (args->isSet("edit-new") || args->isSet("edit-new-preset")) + { + QString templ; + if (args->isSet("edit-new-preset")) + templ = args->getOption("edit-new-preset"); + if (!initCheck()) + { + exitCode = 1; + break; + } + KAlarm::editNew(templ); + } + else + if (args->isSet("file") || args->isSet("exec") || args->isSet("mail") || args->count()) + { + // Display a message or file, execute a command, or send an email + KAEvent::Action action = KAEvent::MESSAGE; + QCString alMessage; + uint alFromID = 0; + EmailAddressList alAddresses; + QStringList alAttachments; + QCString alSubject; + if (args->isSet("file")) + { + kdDebug(5950)<<"KAlarmApp::newInstance(): file\n"; + if (args->isSet("exec")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--exec")).arg(QString::fromLatin1("--file"))) + if (args->isSet("mail")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--mail")).arg(QString::fromLatin1("--file"))) + if (args->count()) + USAGE(i18n("message incompatible with %1").arg(QString::fromLatin1("--file"))) + alMessage = args->getOption("file"); + action = KAEvent::FILE; + } + else if (args->isSet("exec")) + { + kdDebug(5950)<<"KAlarmApp::newInstance(): exec\n"; + if (args->isSet("mail")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--mail")).arg(QString::fromLatin1("--exec"))) + alMessage = args->getOption("exec"); + int n = args->count(); + for (int i = 0; i < n; ++i) + { + alMessage += ' '; + alMessage += args->arg(i); + } + action = KAEvent::COMMAND; + } + else if (args->isSet("mail")) + { + kdDebug(5950)<<"KAlarmApp::newInstance(): mail\n"; + if (args->isSet("subject")) + alSubject = args->getOption("subject"); + if (args->isSet("from-id")) + alFromID = KAMail::identityUoid(args->getOption("from-id")); + QCStringList params = args->getOptionList("mail"); + for (QCStringList::Iterator i = params.begin(); i != params.end(); ++i) + { + QString addr = QString::fromLocal8Bit(*i); + if (!KAMail::checkAddress(addr)) + USAGE(i18n("%1: invalid email address").arg(QString::fromLatin1("--mail"))) + alAddresses += KCal::Person(QString::null, addr); + } + params = args->getOptionList("attach"); + for (QCStringList::Iterator i = params.begin(); i != params.end(); ++i) + alAttachments += QString::fromLocal8Bit(*i); + alMessage = args->arg(0); + action = KAEvent::EMAIL; + } + else + { + kdDebug(5950)<<"KAlarmApp::newInstance(): message\n"; + alMessage = args->arg(0); + } + + if (action != KAEvent::EMAIL) + { + if (args->isSet("subject")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--subject")).arg(QString::fromLatin1("--mail"))) + if (args->isSet("from-id")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--from-id")).arg(QString::fromLatin1("--mail"))) + if (args->isSet("attach")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--attach")).arg(QString::fromLatin1("--mail"))) + if (args->isSet("bcc")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--bcc")).arg(QString::fromLatin1("--mail"))) + } + + bool alarmNoTime = false; + QDateTime alarmTime, endTime; + QColor bgColour = Preferences::defaultBgColour(); + QColor fgColour = Preferences::defaultFgColour(); + KARecurrence recurrence; + int repeatCount = 0; + int repeatInterval = 0; + if (args->isSet("color")) + { + // Background colour is specified + QCString colourText = args->getOption("color"); + if (static_cast<const char*>(colourText)[0] == '0' + && tolower(static_cast<const char*>(colourText)[1]) == 'x') + colourText.replace(0, 2, "#"); + bgColour.setNamedColor(colourText); + if (!bgColour.isValid()) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--color"))) + } + if (args->isSet("colorfg")) + { + // Foreground colour is specified + QCString colourText = args->getOption("colorfg"); + if (static_cast<const char*>(colourText)[0] == '0' + && tolower(static_cast<const char*>(colourText)[1]) == 'x') + colourText.replace(0, 2, "#"); + fgColour.setNamedColor(colourText); + if (!fgColour.isValid()) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--colorfg"))) + } + + if (args->isSet("time")) + { + QCString dateTime = args->getOption("time"); + if (!convWakeTime(dateTime, alarmTime, alarmNoTime)) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--time"))) + } + else + alarmTime = QDateTime::currentDateTime(); + + bool haveRecurrence = args->isSet("recurrence"); + if (haveRecurrence) + { + if (args->isSet("login")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--login")).arg(QString::fromLatin1("--recurrence"))) + if (args->isSet("until")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--until")).arg(QString::fromLatin1("--recurrence"))) + QCString rule = args->getOption("recurrence"); + recurrence.set(QString::fromLocal8Bit(static_cast<const char*>(rule))); + } + if (args->isSet("interval")) + { + // Repeat count is specified + int count; + if (args->isSet("login")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--login")).arg(QString::fromLatin1("--interval"))) + bool ok; + if (args->isSet("repeat")) + { + count = args->getOption("repeat").toInt(&ok); + if (!ok || !count || count < -1 || (count < 0 && haveRecurrence)) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--repeat"))) + } + else if (haveRecurrence) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--interval")).arg(QString::fromLatin1("--repeat"))) + else if (args->isSet("until")) + { + count = 0; + QCString dateTime = args->getOption("until"); + if (!convWakeTime(dateTime, endTime, alarmNoTime)) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--until"))) + if (endTime < alarmTime) + USAGE(i18n("%1 earlier than %2").arg(QString::fromLatin1("--until")).arg(QString::fromLatin1("--time"))) + } + else + count = -1; + + // Get the recurrence interval + int interval; + KARecurrence::Type recurType; + if (!convInterval(args->getOption("interval"), recurType, interval, !haveRecurrence) + || interval < 0) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--interval"))) + if (alarmNoTime && recurType == KARecurrence::MINUTELY) + USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(QString::fromLatin1("--interval"))) + + if (haveRecurrence) + { + // There is a also a recurrence specified, so set up a sub-repetition + int longestInterval = recurrence.longestInterval(); + if (count * interval > longestInterval) + USAGE(i18n("Invalid %1 and %2 parameters: repetition is longer than %3 interval").arg(QString::fromLatin1("--interval")).arg(QString::fromLatin1("--repeat")).arg(QString::fromLatin1("--recurrence"))); + repeatCount = count; + repeatInterval = interval; + } + else + { + // There is no other recurrence specified, so convert the repetition + // parameters into a KCal::Recurrence + recurrence.set(recurType, interval, count, DateTime(alarmTime, alarmNoTime), endTime); + } + } + else + { + if (args->isSet("repeat")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--repeat")).arg(QString::fromLatin1("--interval"))) + if (args->isSet("until")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--until")).arg(QString::fromLatin1("--interval"))) + } + + QCString audioFile; + float audioVolume = -1; +#ifdef WITHOUT_ARTS + bool audioRepeat = false; +#else + bool audioRepeat = args->isSet("play-repeat"); +#endif + if (audioRepeat || args->isSet("play")) + { + // Play a sound with the alarm + if (audioRepeat && args->isSet("play")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--play")).arg(QString::fromLatin1("--play-repeat"))) + if (args->isSet("beep")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--beep")).arg(QString::fromLatin1(audioRepeat ? "--play-repeat" : "--play"))) + if (args->isSet("speak")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--speak")).arg(QString::fromLatin1(audioRepeat ? "--play-repeat" : "--play"))) + audioFile = args->getOption(audioRepeat ? "play-repeat" : "play"); +#ifndef WITHOUT_ARTS + if (args->isSet("volume")) + { + bool ok; + int volumepc = args->getOption("volume").toInt(&ok); + if (!ok || volumepc < 0 || volumepc > 100) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--volume"))) + audioVolume = static_cast<float>(volumepc) / 100; + } +#endif + } +#ifndef WITHOUT_ARTS + else if (args->isSet("volume")) + USAGE(i18n("%1 requires %2 or %3").arg(QString::fromLatin1("--volume")).arg(QString::fromLatin1("--play")).arg(QString::fromLatin1("--play-repeat"))) +#endif + if (args->isSet("speak")) + { + if (args->isSet("beep")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--beep")).arg(QString::fromLatin1("--speak"))) + if (!mSpeechEnabled) + USAGE(i18n("%1 requires speech synthesis to be configured using KTTSD").arg(QString::fromLatin1("--speak"))) + } + int reminderMinutes = 0; + bool onceOnly = args->isSet("reminder-once"); + if (args->isSet("reminder") || onceOnly) + { + // Issue a reminder alarm in advance of the main alarm + if (onceOnly && args->isSet("reminder")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--reminder")).arg(QString::fromLatin1("--reminder-once"))) + QString opt = onceOnly ? QString::fromLatin1("--reminder-once") : QString::fromLatin1("--reminder"); + if (args->isSet("exec")) + USAGE(i18n("%1 incompatible with %2").arg(opt).arg(QString::fromLatin1("--exec"))) + if (args->isSet("mail")) + USAGE(i18n("%1 incompatible with %2").arg(opt).arg(QString::fromLatin1("--mail"))) + KARecurrence::Type recurType; + QString optval = args->getOption(onceOnly ? "reminder-once" : "reminder"); + if (!convInterval(args->getOption(onceOnly ? "reminder-once" : "reminder"), recurType, reminderMinutes)) + USAGE(i18n("Invalid %1 parameter").arg(opt)) + if (recurType == KARecurrence::MINUTELY && alarmNoTime) + USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(opt)) + } + + int lateCancel = 0; + if (args->isSet("late-cancel")) + { + KARecurrence::Type recurType; + bool ok = convInterval(args->getOption("late-cancel"), recurType, lateCancel); + if (!ok || lateCancel <= 0) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("late-cancel"))) + } + else if (args->isSet("auto-close")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--auto-close")).arg(QString::fromLatin1("--late-cancel"))) + + int flags = KAEvent::DEFAULT_FONT; + if (args->isSet("ack-confirm")) + flags |= KAEvent::CONFIRM_ACK; + if (args->isSet("auto-close")) + flags |= KAEvent::AUTO_CLOSE; + if (args->isSet("beep")) + flags |= KAEvent::BEEP; + if (args->isSet("speak")) + flags |= KAEvent::SPEAK; + if (args->isSet("korganizer")) + flags |= KAEvent::COPY_KORGANIZER; + if (args->isSet("disable")) + flags |= KAEvent::DISABLED; + if (audioRepeat) + flags |= KAEvent::REPEAT_SOUND; + if (args->isSet("login")) + flags |= KAEvent::REPEAT_AT_LOGIN; + if (args->isSet("bcc")) + flags |= KAEvent::EMAIL_BCC; + if (alarmNoTime) + flags |= KAEvent::ANY_TIME; + args->clear(); // free up memory + + // Display or schedule the event + if (!initCheck()) + { + exitCode = 1; + break; + } + if (!scheduleEvent(action, alMessage, alarmTime, lateCancel, flags, bgColour, fgColour, QFont(), audioFile, + audioVolume, reminderMinutes, recurrence, repeatInterval, repeatCount, + alFromID, alAddresses, alSubject, alAttachments)) + { + exitCode = 1; + break; + } + } + else + { + // No arguments - run interactively & display the main window + kdDebug(5950)<<"KAlarmApp::newInstance(): interactive\n"; + if (args->isSet("ack-confirm")) + usage += QString::fromLatin1("--ack-confirm "); + if (args->isSet("attach")) + usage += QString::fromLatin1("--attach "); + if (args->isSet("auto-close")) + usage += QString::fromLatin1("--auto-close "); + if (args->isSet("bcc")) + usage += QString::fromLatin1("--bcc "); + if (args->isSet("beep")) + usage += QString::fromLatin1("--beep "); + if (args->isSet("color")) + usage += QString::fromLatin1("--color "); + if (args->isSet("colorfg")) + usage += QString::fromLatin1("--colorfg "); + if (args->isSet("disable")) + usage += QString::fromLatin1("--disable "); + if (args->isSet("from-id")) + usage += QString::fromLatin1("--from-id "); + if (args->isSet("korganizer")) + usage += QString::fromLatin1("--korganizer "); + if (args->isSet("late-cancel")) + usage += QString::fromLatin1("--late-cancel "); + if (args->isSet("login")) + usage += QString::fromLatin1("--login "); + if (args->isSet("play")) + usage += QString::fromLatin1("--play "); +#ifndef WITHOUT_ARTS + if (args->isSet("play-repeat")) + usage += QString::fromLatin1("--play-repeat "); +#endif + if (args->isSet("reminder")) + usage += QString::fromLatin1("--reminder "); + if (args->isSet("reminder-once")) + usage += QString::fromLatin1("--reminder-once "); + if (args->isSet("speak")) + usage += QString::fromLatin1("--speak "); + if (args->isSet("subject")) + usage += QString::fromLatin1("--subject "); + if (args->isSet("time")) + usage += QString::fromLatin1("--time "); +#ifndef WITHOUT_ARTS + if (args->isSet("volume")) + usage += QString::fromLatin1("--volume "); +#endif + if (!usage.isEmpty()) + { + usage += i18n(": option(s) only valid with a message/%1/%2").arg(QString::fromLatin1("--file")).arg(QString::fromLatin1("--exec")); + break; + } + + args->clear(); // free up memory + if (!initCheck()) + { + exitCode = 1; + break; + } + + (MainWindow::create())->show(); + } + } while (0); // only execute once + + if (!usage.isEmpty()) + { + // Note: we can't use args->usage() since that also quits any other + // running 'instances' of the program. + std::cerr << usage.local8Bit().data() + << i18n("\nUse --help to get a list of available command line options.\n").local8Bit().data(); + exitCode = 1; + } + } + if (firstInstance && !dontRedisplay && !exitCode) + redisplayAlarms(); + + --mActiveCount; + firstInstance = false; + + // Quit the application if this was the last/only running "instance" of the program. + // Executing 'return' doesn't work very well since the program continues to + // run if no windows were created. + quitIf(exitCode); + return exitCode; +} + +/****************************************************************************** +* Quit the program, optionally only if there are no more "instances" running. +*/ +void KAlarmApp::quitIf(int exitCode, bool force) +{ + if (force) + { + // Quit regardless, except for message windows + MainWindow::closeAll(); + displayTrayIcon(false); + if (MessageWin::instanceCount()) + return; + } + else + { + // Quit only if there are no more "instances" running + mPendingQuit = false; + if (mActiveCount > 0 || MessageWin::instanceCount()) + return; + int mwcount = MainWindow::count(); + MainWindow* mw = mwcount ? MainWindow::firstWindow() : 0; + if (mwcount > 1 || mwcount && (!mw->isHidden() || !mw->isTrayParent())) + return; + // There are no windows left except perhaps a main window which is a hidden tray icon parent + if (mTrayWindow) + { + // There is a system tray icon. + // Don't exit unless the system tray doesn't seem to exist. + if (checkSystemTray()) + return; + } + if (!mDcopQueue.isEmpty() || !mCommandProcesses.isEmpty()) + { + // Don't quit yet if there are outstanding actions on the DCOP queue + mPendingQuit = true; + mPendingQuitCode = exitCode; + return; + } + } + + // This was the last/only running "instance" of the program, so exit completely. + kdDebug(5950) << "KAlarmApp::quitIf(" << exitCode << "): quitting" << endl; + BirthdayDlg::close(); + exit(exitCode); +} + +/****************************************************************************** +* Called when the Quit menu item is selected. +* Closes the system tray window and all main windows, but does not exit the +* program if other windows are still open. +*/ +void KAlarmApp::doQuit(QWidget* parent) +{ + kdDebug(5950) << "KAlarmApp::doQuit()\n"; + if (mDisableAlarmsIfStopped + && MessageBox::warningContinueCancel(parent, KMessageBox::Cancel, + i18n("Quitting will disable alarms\n(once any alarm message windows are closed)."), + QString::null, KStdGuiItem::quit(), Preferences::QUIT_WARN + ) != KMessageBox::Yes) + return; + quitIf(0, true); +} + +/****************************************************************************** +* Called when the session manager is about to close down the application. +*/ +void KAlarmApp::commitData(QSessionManager& sm) +{ + mSessionClosingDown = true; + KUniqueApplication::commitData(sm); + mSessionClosingDown = false; // reset in case shutdown is cancelled +} + +/****************************************************************************** +* Display an error message for a fatal error. Prevent further actions since +* the program state is unsafe. +*/ +void KAlarmApp::displayFatalError(const QString& message) +{ + if (!mFatalError) + { + mFatalError = 1; + mFatalMessage = message; + if (theInstance) + QTimer::singleShot(0, theInstance, SLOT(quitFatal())); + } +} + +/****************************************************************************** +* Quit the program, once the fatal error message has been acknowledged. +*/ +void KAlarmApp::quitFatal() +{ + switch (mFatalError) + { + case 0: + case 2: + return; + case 1: + mFatalError = 2; + KMessageBox::error(0, mFatalMessage); + mFatalError = 3; + // fall through to '3' + case 3: + if (theInstance) + theInstance->quitIf(1, true); + break; + } + QTimer::singleShot(1000, this, SLOT(quitFatal())); +} + +/****************************************************************************** +* The main processing loop for KAlarm. +* All KAlarm operations involving opening or updating calendar files are called +* from this loop to ensure that only one operation is active at any one time. +* This precaution is necessary because KAlarm's activities are mostly +* asynchronous, being in response to DCOP calls from the alarm daemon (or other +* programs) or timer events, any of which can be received in the middle of +* performing another operation. If a calendar file is opened or updated while +* another calendar operation is in progress, the program has been observed to +* hang, or the first calendar call has failed with data loss - clearly +* unacceptable!! +*/ +void KAlarmApp::processQueue() +{ + if (mInitialised && !mProcessingQueue) + { + kdDebug(5950) << "KAlarmApp::processQueue()\n"; + mProcessingQueue = true; + + // Reset the alarm daemon if it's been queued + KAlarm::resetDaemonIfQueued(); + + // Process DCOP calls + while (!mDcopQueue.isEmpty()) + { + DcopQEntry& entry = mDcopQueue.first(); + if (entry.eventId.isEmpty()) + { + // It's a new alarm + switch (entry.function) + { + case EVENT_TRIGGER: + execAlarm(entry.event, entry.event.firstAlarm(), false); + break; + case EVENT_HANDLE: + KAlarm::addEvent(entry.event, 0); + break; + case EVENT_CANCEL: + break; + } + } + else + handleEvent(entry.eventId, entry.function); + mDcopQueue.pop_front(); + } + + // Purge the expired alarms calendar if it's time to do so + AlarmCalendar::expiredCalendar()->purgeIfQueued(); + + // Now that the queue has been processed, quit if a quit was queued + if (mPendingQuit) + quitIf(mPendingQuitCode); + + mProcessingQueue = false; + } +} + +/****************************************************************************** +* Redisplay alarms which were being shown when the program last exited. +* Normally, these alarms will have been displayed by session restoration, but +* if the program crashed or was killed, we can redisplay them here so that +* they won't be lost. +*/ +void KAlarmApp::redisplayAlarms() +{ + AlarmCalendar* cal = AlarmCalendar::displayCalendar(); + if (cal->isOpen()) + { + KCal::Event::List events = cal->events(); + for (KCal::Event::List::ConstIterator it = events.begin(); it != events.end(); ++it) + { + KCal::Event* kcalEvent = *it; + KAEvent event(*kcalEvent); + event.setUid(KAEvent::ACTIVE); + if (!MessageWin::findEvent(event.id())) + { + // This event should be displayed, but currently isn't being + kdDebug(5950) << "KAlarmApp::redisplayAlarms(): " << event.id() << endl; + KAAlarm alarm = event.convertDisplayingAlarm(); + (new MessageWin(event, alarm, false, !alarm.repeatAtLogin()))->show(); + } + } + } +} + +/****************************************************************************** +* Called when the system tray main window is closed. +*/ +void KAlarmApp::removeWindow(TrayWindow*) +{ + mTrayWindow = 0; + quitIf(); +} + +/****************************************************************************** +* Display or close the system tray icon. +*/ +bool KAlarmApp::displayTrayIcon(bool show, MainWindow* parent) +{ + static bool creating = false; + if (show) + { + if (!mTrayWindow && !creating) + { + if (!mHaveSystemTray) + return false; + if (!MainWindow::count() && wantRunInSystemTray()) + { + creating = true; // prevent main window constructor from creating an additional tray icon + parent = MainWindow::create(); + creating = false; + } + mTrayWindow = new TrayWindow(parent ? parent : MainWindow::firstWindow()); + connect(mTrayWindow, SIGNAL(deleted()), SIGNAL(trayIconToggled())); + mTrayWindow->show(); + emit trayIconToggled(); + + // Set up a timer so that we can check after all events in the window system's + // event queue have been processed, whether the system tray actually exists + mCheckingSystemTray = true; + mSavedNoSystemTray = mNoSystemTray; + mNoSystemTray = false; + QTimer::singleShot(0, this, SLOT(slotSystemTrayTimer())); + } + } + else if (mTrayWindow) + { + delete mTrayWindow; + mTrayWindow = 0; + } + return true; +} + +/****************************************************************************** +* Called by a timer to check whether the system tray icon has been housed in +* the system tray. Because there is a delay between the system tray icon show +* event and the icon being reparented by the system tray, we have to use a +* timer to check whether the system tray has actually grabbed it, or whether +* the system tray probably doesn't exist. +*/ +void KAlarmApp::slotSystemTrayTimer() +{ + mCheckingSystemTray = false; + if (!checkSystemTray()) + quitIf(0); // exit the application if there are no open windows +} + +/****************************************************************************** +* Check whether the system tray icon has been housed in the system tray. +* If the system tray doesn't seem to exist, tell the alarm daemon to notify us +* of alarms regardless of whether we're running. +*/ +bool KAlarmApp::checkSystemTray() +{ + if (mCheckingSystemTray || !mTrayWindow) + return true; + if (mTrayWindow->inSystemTray() != !mSavedNoSystemTray) + { + kdDebug(5950) << "KAlarmApp::checkSystemTray(): changed -> " << mSavedNoSystemTray << endl; + mNoSystemTray = mSavedNoSystemTray = !mSavedNoSystemTray; + + // Store the new setting in the config file, so that if KAlarm exits and is then + // next activated by the daemon to display a message, it will register with the + // daemon with the correct NOTIFY type. If that happened when there was no system + // tray and alarms are disabled when KAlarm is not running, registering with + // NO_START_NOTIFY could result in alarms never being seen. + KConfig* config = kapp->config(); + config->setGroup(QString::fromLatin1("General")); + config->writeEntry(QString::fromLatin1("NoSystemTray"), mNoSystemTray); + config->sync(); + + // Update other settings and reregister with the alarm daemon + slotPreferencesChanged(); + } + else + { + kdDebug(5950) << "KAlarmApp::checkSystemTray(): no change = " << !mSavedNoSystemTray << endl; + mNoSystemTray = mSavedNoSystemTray; + } + return !mNoSystemTray; +} + +/****************************************************************************** +* Return the main window associated with the system tray icon. +*/ +MainWindow* KAlarmApp::trayMainWindow() const +{ + return mTrayWindow ? mTrayWindow->assocMainWindow() : 0; +} + +/****************************************************************************** +* Called when KAlarm preferences have changed. +*/ +void KAlarmApp::slotPreferencesChanged() +{ + bool newRunInSysTray = wantRunInSystemTray(); + if (newRunInSysTray != mOldRunInSystemTray) + { + // The system tray run mode has changed + ++mActiveCount; // prevent the application from quitting + MainWindow* win = mTrayWindow ? mTrayWindow->assocMainWindow() : 0; + delete mTrayWindow; // remove the system tray icon if it is currently shown + mTrayWindow = 0; + mOldRunInSystemTray = newRunInSysTray; + if (!newRunInSysTray) + { + if (win && win->isHidden()) + delete win; + } + displayTrayIcon(true); + --mActiveCount; + } + + bool newDisableIfStopped = wantRunInSystemTray() && !mNoSystemTray && Preferences::disableAlarmsIfStopped(); + if (newDisableIfStopped != mDisableAlarmsIfStopped) + { + mDisableAlarmsIfStopped = newDisableIfStopped; // N.B. this setting is used by Daemon::reregister() + Preferences::setQuitWarn(true); // since mode has changed, re-allow warning messages on Quit + Daemon::reregister(); // re-register with the alarm daemon + } + + // Change alarm times for date-only alarms if the start of day time has changed + if (Preferences::startOfDay() != mStartOfDay) + changeStartOfDay(); + + // In case the date for February 29th recurrences has changed + KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type()); + + if (Preferences::expiredColour() != mPrefsExpiredColour) + { + // The expired alarms text colour has changed + mRefreshExpiredAlarms = true; + mPrefsExpiredColour = Preferences::expiredColour(); + } + + if (Preferences::expiredKeepDays() != mPrefsExpiredKeepDays) + { + // How long expired alarms are being kept has changed. + // N.B. This also adjusts for any change in start-of-day time. + mPrefsExpiredKeepDays = Preferences::expiredKeepDays(); + AlarmCalendar::expiredCalendar()->setPurgeDays(mPrefsExpiredKeepDays); + } + + if (mRefreshExpiredAlarms) + { + mRefreshExpiredAlarms = false; + MainWindow::updateExpired(); + } +} + +/****************************************************************************** +* Change alarm times for date-only alarms after the start of day time has changed. +*/ +void KAlarmApp::changeStartOfDay() +{ + Daemon::notifyTimeChanged(); // tell the alarm daemon the new time + QTime sod = Preferences::startOfDay(); + DateTime::setStartOfDay(sod); + AlarmCalendar* cal = AlarmCalendar::activeCalendar(); + if (KAEvent::adjustStartOfDay(cal->events())) + cal->save(); + Preferences::updateStartOfDayCheck(); // now that calendar is updated, set OK flag in config file + mStartOfDay = sod; +} + +/****************************************************************************** +* Called when the expired alarms calendar has been purged. +* Updates the alarm list in all main windows. +*/ +void KAlarmApp::slotExpiredPurged() +{ + mRefreshExpiredAlarms = false; + MainWindow::updateExpired(); +} + +/****************************************************************************** +* Return whether the program is configured to be running in the system tray. +*/ +bool KAlarmApp::wantRunInSystemTray() const +{ + return Preferences::runInSystemTray() && mHaveSystemTray; +} + +/****************************************************************************** +* Called to schedule a new alarm, either in response to a DCOP notification or +* to command line options. +* Reply = true unless there was a parameter error or an error opening calendar file. +*/ +bool KAlarmApp::scheduleEvent(KAEvent::Action action, const QString& text, const QDateTime& dateTime, + int lateCancel, int flags, const QColor& bg, const QColor& fg, const QFont& font, + const QString& audioFile, float audioVolume, int reminderMinutes, + const KARecurrence& recurrence, int repeatInterval, int repeatCount, + uint mailFromID, const EmailAddressList& mailAddresses, + const QString& mailSubject, const QStringList& mailAttachments) +{ + kdDebug(5950) << "KAlarmApp::scheduleEvent(): " << text << endl; + if (!dateTime.isValid()) + return false; + QDateTime now = QDateTime::currentDateTime(); + if (lateCancel && dateTime < now.addSecs(-maxLateness(lateCancel))) + return true; // alarm time was already expired too long ago + QDateTime alarmTime = dateTime; + // Round down to the nearest minute to avoid scheduling being messed up + alarmTime.setTime(QTime(alarmTime.time().hour(), alarmTime.time().minute(), 0)); + + KAEvent event(alarmTime, text, bg, fg, font, action, lateCancel, flags); + if (reminderMinutes) + { + bool onceOnly = (reminderMinutes < 0); + event.setReminder((onceOnly ? -reminderMinutes : reminderMinutes), onceOnly); + } + if (!audioFile.isEmpty()) + event.setAudioFile(audioFile, audioVolume, -1, 0); + if (!mailAddresses.isEmpty()) + event.setEmail(mailFromID, mailAddresses, mailSubject, mailAttachments); + event.setRecurrence(recurrence); + event.setFirstRecurrence(); + event.setRepetition(repeatInterval, repeatCount - 1); + if (alarmTime <= now) + { + // Alarm is due for display already. + // First execute it once without adding it to the calendar file. + if (!mInitialised) + mDcopQueue.append(DcopQEntry(event, EVENT_TRIGGER)); + else + execAlarm(event, event.firstAlarm(), false); + // If it's a recurring alarm, reschedule it for its next occurrence + if (!event.recurs() + || event.setNextOccurrence(now) == KAEvent::NO_OCCURRENCE) + return true; + // It has recurrences in the future + } + + // Queue the alarm for insertion into the calendar file + mDcopQueue.append(DcopQEntry(event)); + if (mInitialised) + QTimer::singleShot(0, this, SLOT(processQueue())); + return true; +} + +/****************************************************************************** +* Called in response to a DCOP notification by the alarm daemon that an event +* should be handled, i.e. displayed or cancelled. +* Optionally display the event. Delete the event from the calendar file and +* from every main window instance. +*/ +bool KAlarmApp::handleEvent(const QString& urlString, const QString& eventID, EventFunc function) +{ + kdDebug(5950) << "KAlarmApp::handleEvent(DCOP): " << eventID << endl; + AlarmCalendar* cal = AlarmCalendar::activeCalendar(); // this can be called before calendars have been initialised + if (cal && KURL(urlString).url() != cal->urlString()) + { + kdError(5950) << "KAlarmApp::handleEvent(DCOP): wrong calendar file " << urlString << endl; + Daemon::eventHandled(eventID, false); + return false; + } + mDcopQueue.append(DcopQEntry(function, eventID)); + if (mInitialised) + QTimer::singleShot(0, this, SLOT(processQueue())); + return true; +} + +/****************************************************************************** +* Either: +* a) Display the event and then delete it if it has no outstanding repetitions. +* b) Delete the event. +* c) Reschedule the event for its next repetition. If none remain, delete it. +* If the event is deleted, it is removed from the calendar file and from every +* main window instance. +*/ +bool KAlarmApp::handleEvent(const QString& eventID, EventFunc function) +{ + kdDebug(5950) << "KAlarmApp::handleEvent(): " << eventID << ", " << (function==EVENT_TRIGGER?"TRIGGER":function==EVENT_CANCEL?"CANCEL":function==EVENT_HANDLE?"HANDLE":"?") << endl; + KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(eventID); + if (!kcalEvent) + { + kdError(5950) << "KAlarmApp::handleEvent(): event ID not found: " << eventID << endl; + Daemon::eventHandled(eventID, false); + return false; + } + KAEvent event(*kcalEvent); + switch (function) + { + case EVENT_CANCEL: + KAlarm::deleteEvent(event, true); + break; + + case EVENT_TRIGGER: // handle it if it's due, else execute it regardless + case EVENT_HANDLE: // handle it if it's due + { + QDateTime now = QDateTime::currentDateTime(); + bool updateCalAndDisplay = false; + bool alarmToExecuteValid = false; + KAAlarm alarmToExecute; + // Check all the alarms in turn. + // Note that the main alarm is fetched before any other alarms. + for (KAAlarm alarm = event.firstAlarm(); alarm.valid(); alarm = event.nextAlarm(alarm)) + { + // Check if the alarm is due yet. + int secs = alarm.dateTime(true).dateTime().secsTo(now); + if (secs < 0) + { + // This alarm is definitely not due yet + kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": not due\n"; + continue; + } + if (alarm.repeatAtLogin()) + { + // Alarm is to be displayed at every login. + // Check if the alarm has only just been set up. + // (The alarm daemon will immediately notify that it is due + // since it is set up with a time in the past.) + kdDebug(5950) << "KAlarmApp::handleEvent(): REPEAT_AT_LOGIN\n"; + if (secs < maxLateness(1)) + continue; + + // Check if the main alarm is already being displayed. + // (We don't want to display both at the same time.) + if (alarmToExecute.valid()) + continue; + + // Set the time to display if it's a display alarm + alarm.setTime(now); + } + if (alarm.lateCancel()) + { + // Alarm is due, and it is to be cancelled if too late. + kdDebug(5950) << "KAlarmApp::handleEvent(): LATE_CANCEL\n"; + bool late = false; + bool cancel = false; + if (alarm.dateTime().isDateOnly()) + { + // The alarm has no time, so cancel it if its date is too far past + int maxlate = alarm.lateCancel() / 1440; // maximum lateness in days + QDateTime limit(alarm.date().addDays(maxlate + 1), Preferences::startOfDay()); + if (now >= limit) + { + // It's too late to display the scheduled occurrence. + // Find the last previous occurrence of the alarm. + DateTime next; + KAEvent::OccurType type = event.previousOccurrence(now, next, true); + switch (type & ~KAEvent::OCCURRENCE_REPEAT) + { + case KAEvent::FIRST_OR_ONLY_OCCURRENCE: + case KAEvent::RECURRENCE_DATE: + case KAEvent::RECURRENCE_DATE_TIME: + case KAEvent::LAST_RECURRENCE: + limit.setDate(next.date().addDays(maxlate + 1)); + limit.setTime(Preferences::startOfDay()); + if (now >= limit) + { + if (type == KAEvent::LAST_RECURRENCE + || type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs()) + cancel = true; // last occurrence (and there are no repetitions) + else + late = true; + } + break; + case KAEvent::NO_OCCURRENCE: + default: + late = true; + break; + } + } + } + else + { + // The alarm is timed. Allow it to be the permitted amount late before cancelling it. + int maxlate = maxLateness(alarm.lateCancel()); + if (secs > maxlate) + { + // It's over the maximum interval late. + // Find the most recent occurrence of the alarm. + DateTime next; + KAEvent::OccurType type = event.previousOccurrence(now, next, true); + switch (type & ~KAEvent::OCCURRENCE_REPEAT) + { + case KAEvent::FIRST_OR_ONLY_OCCURRENCE: + case KAEvent::RECURRENCE_DATE: + case KAEvent::RECURRENCE_DATE_TIME: + case KAEvent::LAST_RECURRENCE: + if (next.dateTime().secsTo(now) > maxlate) + { + if (type == KAEvent::LAST_RECURRENCE + || type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs()) + cancel = true; // last occurrence (and there are no repetitions) + else + late = true; + } + break; + case KAEvent::NO_OCCURRENCE: + default: + late = true; + break; + } + } + } + + if (cancel) + { + // All recurrences are finished, so cancel the event + event.setArchive(); + cancelAlarm(event, alarm.type(), false); + updateCalAndDisplay = true; + continue; + } + if (late) + { + // The latest repetition was too long ago, so schedule the next one + rescheduleAlarm(event, alarm, false); + updateCalAndDisplay = true; + continue; + } + } + if (!alarmToExecuteValid) + { + kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": execute\n"; + alarmToExecute = alarm; // note the alarm to be executed + alarmToExecuteValid = true; // only trigger one alarm for the event + } + else + kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": skip\n"; + } + + // If there is an alarm to execute, do this last after rescheduling/cancelling + // any others. This ensures that the updated event is only saved once to the calendar. + if (alarmToExecute.valid()) + execAlarm(event, alarmToExecute, true, !alarmToExecute.repeatAtLogin()); + else + { + if (function == EVENT_TRIGGER) + { + // The alarm is to be executed regardless of whether it's due. + // Only trigger one alarm from the event - we don't want multiple + // identical messages, for example. + KAAlarm alarm = event.firstAlarm(); + if (alarm.valid()) + execAlarm(event, alarm, false); + } + if (updateCalAndDisplay) + KAlarm::updateEvent(event, 0); // update the window lists and calendar file + else if (function != EVENT_TRIGGER) + { + kdDebug(5950) << "KAlarmApp::handleEvent(): no action\n"; + Daemon::eventHandled(eventID, false); + } + } + break; + } + } + return true; +} + +/****************************************************************************** +* Called when an alarm is currently being displayed, to store a copy of the +* alarm in the displaying calendar, and to reschedule it for its next repetition. +* If no repetitions remain, cancel it. +*/ +void KAlarmApp::alarmShowing(KAEvent& event, KAAlarm::Type alarmType, const DateTime& alarmTime) +{ + kdDebug(5950) << "KAlarmApp::alarmShowing(" << event.id() << ", " << KAAlarm::debugType(alarmType) << ")\n"; + KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(event.id()); + if (!kcalEvent) + kdError(5950) << "KAlarmApp::alarmShowing(): event ID not found: " << event.id() << endl; + else + { + KAAlarm alarm = event.alarm(alarmType); + if (!alarm.valid()) + kdError(5950) << "KAlarmApp::alarmShowing(): alarm type not found: " << event.id() << ":" << alarmType << endl; + else + { + // Copy the alarm to the displaying calendar in case of a crash, etc. + KAEvent dispEvent; + dispEvent.setDisplaying(event, alarmType, alarmTime.dateTime()); + AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen(); + if (cal) + { + cal->deleteEvent(dispEvent.id()); // in case it already exists + cal->addEvent(dispEvent); + cal->save(); + } + + rescheduleAlarm(event, alarm, true); + return; + } + } + Daemon::eventHandled(event.id(), false); +} + +/****************************************************************************** +* Called when an alarm action has completed, to perform any post-alarm actions. +*/ +void KAlarmApp::alarmCompleted(const KAEvent& event) +{ + if (!event.postAction().isEmpty() && ShellProcess::authorised()) + { + QString command = event.postAction(); + kdDebug(5950) << "KAlarmApp::alarmCompleted(" << event.id() << "): " << command << endl; + doShellCommand(command, event, 0, ProcData::POST_ACTION); + } +} + +/****************************************************************************** +* Reschedule the alarm for its next recurrence. If none remain, delete it. +* If the alarm is deleted and it is the last alarm for its event, the event is +* removed from the calendar file and from every main window instance. +*/ +void KAlarmApp::rescheduleAlarm(KAEvent& event, const KAAlarm& alarm, bool updateCalAndDisplay) +{ + kdDebug(5950) << "KAlarmApp::rescheduleAlarm()" << endl; + bool update = false; + if (alarm.reminder() || alarm.deferred()) + { + // It's an advance warning alarm or an extra deferred alarm, so delete it + event.removeExpiredAlarm(alarm.type()); + update = true; + } + else if (alarm.repeatAtLogin()) + { + // Leave an alarm which repeats at every login until its main alarm is deleted + if (updateCalAndDisplay && event.updated()) + update = true; + } + else + { + // Reschedule the alarm for its next recurrence. + KAEvent::OccurType type = event.setNextOccurrence(QDateTime::currentDateTime()); + switch (type) + { + case KAEvent::NO_OCCURRENCE: + // All repetitions are finished, so cancel the event + cancelAlarm(event, alarm.type(), updateCalAndDisplay); + break; + default: + if (!(type & KAEvent::OCCURRENCE_REPEAT)) + break; + // Next occurrence is a repeat, so fall through to recurrence handling + case KAEvent::RECURRENCE_DATE: + case KAEvent::RECURRENCE_DATE_TIME: + case KAEvent::LAST_RECURRENCE: + // The event is due by now and repetitions still remain, so rewrite the event + if (updateCalAndDisplay) + update = true; + else + { + event.cancelCancelledDeferral(); + event.setUpdated(); // note that the calendar file needs to be updated + } + break; + case KAEvent::FIRST_OR_ONLY_OCCURRENCE: + // The first occurrence is still due?!?, so don't do anything + break; + } + if (event.deferred()) + { + // Just in case there's also a deferred alarm, ensure it's removed + event.removeExpiredAlarm(KAAlarm::DEFERRED_ALARM); + update = true; + } + } + if (update) + { + event.cancelCancelledDeferral(); + KAlarm::updateEvent(event, 0); // update the window lists and calendar file + } +} + +/****************************************************************************** +* Delete the alarm. If it is the last alarm for its event, the event is removed +* from the calendar file and from every main window instance. +*/ +void KAlarmApp::cancelAlarm(KAEvent& event, KAAlarm::Type alarmType, bool updateCalAndDisplay) +{ + kdDebug(5950) << "KAlarmApp::cancelAlarm()" << endl; + event.cancelCancelledDeferral(); + if (alarmType == KAAlarm::MAIN_ALARM && !event.displaying() && event.toBeArchived()) + { + // The event is being deleted. Save it in the expired calendar file first. + QString id = event.id(); // save event ID since KAlarm::addExpiredEvent() changes it + KAlarm::addExpiredEvent(event); + event.setEventID(id); // restore event ID + } + event.removeExpiredAlarm(alarmType); + if (!event.alarmCount()) + KAlarm::deleteEvent(event, false); + else if (updateCalAndDisplay) + KAlarm::updateEvent(event, 0); // update the window lists and calendar file +} + +/****************************************************************************** +* Execute an alarm by displaying its message or file, or executing its command. +* Reply = ShellProcess instance if a command alarm +* != 0 if successful +* = 0 if the alarm is disabled, or if an error message was output. +*/ +void* KAlarmApp::execAlarm(KAEvent& event, const KAAlarm& alarm, bool reschedule, bool allowDefer, bool noPreAction) +{ + if (!event.enabled()) + { + // The event is disabled. + if (reschedule) + rescheduleAlarm(event, alarm, true); + return 0; + } + + void* result = (void*)1; + event.setArchive(); + switch (alarm.action()) + { + case KAAlarm::MESSAGE: + case KAAlarm::FILE: + { + // Display a message or file, provided that the same event isn't already being displayed + MessageWin* win = MessageWin::findEvent(event.id()); + // Find if we're changing a reminder message to the real message + bool reminder = (alarm.type() & KAAlarm::REMINDER_ALARM); + bool replaceReminder = !reminder && win && (win->alarmType() & KAAlarm::REMINDER_ALARM); + if (!reminder && !event.deferred() + && (replaceReminder || !win) && !noPreAction + && !event.preAction().isEmpty() && ShellProcess::authorised()) + { + // It's not a reminder or a deferred alarm, and there is no message window + // (other than a reminder window) currently displayed for this alarm, + // and we need to execute a command before displaying the new window. + // Check whether the command is already being executed for this alarm. + for (QValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) + { + ProcData* pd = *it; + if (pd->event->id() == event.id() && (pd->flags & ProcData::PRE_ACTION)) + { + kdDebug(5950) << "KAlarmApp::execAlarm(): already executing pre-DISPLAY command" << endl; + return pd->process; // already executing - don't duplicate the action + } + } + + QString command = event.preAction(); + kdDebug(5950) << "KAlarmApp::execAlarm(): pre-DISPLAY command: " << command << endl; + int flags = (reschedule ? ProcData::RESCHEDULE : 0) | (allowDefer ? ProcData::ALLOW_DEFER : 0); + if (doShellCommand(command, event, &alarm, (flags | ProcData::PRE_ACTION))) + return result; // display the message after the command completes + // Error executing command - display the message even though it failed + } + if (!event.enabled()) + delete win; // event is disabled - close its window + else if (!win + || !win->hasDefer() && !alarm.repeatAtLogin() + || replaceReminder) + { + // Either there isn't already a message for this event, + // or there is a repeat-at-login message with no Defer + // button, which needs to be replaced with a new message, + // or the caption needs to be changed from "Reminder" to "Message". + if (win) + win->setRecreating(); // prevent post-alarm actions + delete win; + KAlarm::cancelScreenSaver(); + (new MessageWin(event, alarm, reschedule, allowDefer))->show(); + } + else + { + // Raise the existing message window and replay any sound + KAlarm::cancelScreenSaver(); + win->repeat(alarm); // N.B. this reschedules the alarm + } + break; + } + case KAAlarm::COMMAND: + { + int flags = event.commandXterm() ? ProcData::EXEC_IN_XTERM : 0; + QString command = event.cleanText(); + if (event.commandScript()) + { + // Store the command script in a temporary file for execution + kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: (script)" << endl; + QString tmpfile = createTempScriptFile(command, false, event, alarm); + if (tmpfile.isEmpty()) + result = 0; + else + result = doShellCommand(tmpfile, event, &alarm, (flags | ProcData::TEMP_FILE)); + } + else + { + kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: " << command << endl; + result = doShellCommand(command, event, &alarm, flags); + } + if (reschedule) + rescheduleAlarm(event, alarm, true); + break; + } + case KAAlarm::EMAIL: + { + kdDebug(5950) << "KAlarmApp::execAlarm(): EMAIL to: " << event.emailAddresses(", ") << endl; + QStringList errmsgs; + if (!KAMail::send(event, errmsgs, (reschedule || allowDefer))) + result = 0; + if (!errmsgs.isEmpty()) + { + // Some error occurred, although the email may have been sent successfully + if (result) + kdDebug(5950) << "KAlarmApp::execAlarm(): copy error: " << errmsgs[1] << endl; + else + kdDebug(5950) << "KAlarmApp::execAlarm(): failed: " << errmsgs[1] << endl; + (new MessageWin(event, alarm.dateTime(), errmsgs))->show(); + } + if (reschedule) + rescheduleAlarm(event, alarm, true); + break; + } + default: + return 0; + } + return result; +} + +/****************************************************************************** +* Execute a shell command line specified by an alarm. +* If the PRE_ACTION bit of 'flags' is set, the alarm will be executed via +* execAlarm() once the command completes, the execAlarm() parameters being +* derived from the remaining bits in 'flags'. +*/ +ShellProcess* KAlarmApp::doShellCommand(const QString& command, const KAEvent& event, const KAAlarm* alarm, int flags) +{ + kdDebug(5950) << "KAlarmApp::doShellCommand(" << command << ", " << event.id() << ")" << endl; + KProcess::Communication comms = KProcess::NoCommunication; + QString cmd; + QString tmpXtermFile; + if (flags & ProcData::EXEC_IN_XTERM) + { + // Execute the command in a terminal window. + cmd = Preferences::cmdXTermCommand(); + cmd.replace("%t", aboutData()->programName()); // set the terminal window title + if (cmd.find("%C") >= 0) + { + // Execute the command from a temporary script file + if (flags & ProcData::TEMP_FILE) + cmd.replace("%C", command); // the command is already calling a temporary file + else + { + tmpXtermFile = createTempScriptFile(command, true, event, *alarm); + if (tmpXtermFile.isEmpty()) + return 0; + cmd.replace("%C", tmpXtermFile); // %C indicates where to insert the command + } + } + else if (cmd.find("%W") >= 0) + { + // Execute the command from a temporary script file, + // with a sleep after the command is executed + tmpXtermFile = createTempScriptFile(command + QString::fromLatin1("\nsleep 86400\n"), true, event, *alarm); + if (tmpXtermFile.isEmpty()) + return 0; + cmd.replace("%W", tmpXtermFile); // %w indicates where to insert the command + } + else if (cmd.find("%w") >= 0) + { + // Append a sleep to the command. + // Quote the command in case it contains characters such as [>|;]. + QString exec = KShellProcess::quote(command + QString::fromLatin1("; sleep 86400")); + cmd.replace("%w", exec); // %w indicates where to insert the command string + } + else + { + // Set the command to execute. + // Put it in quotes in case it contains characters such as [>|;]. + QString exec = KShellProcess::quote(command); + if (cmd.find("%c") >= 0) + cmd.replace("%c", exec); // %c indicates where to insert the command string + else + cmd.append(exec); // otherwise, simply append the command string + } + } + else + { + cmd = command; + comms = KProcess::AllOutput; + } + ShellProcess* proc = new ShellProcess(cmd); + connect(proc, SIGNAL(shellExited(ShellProcess*)), SLOT(slotCommandExited(ShellProcess*))); + QGuardedPtr<ShellProcess> logproc = 0; + if (comms == KProcess::AllOutput && !event.logFile().isEmpty()) + { + // Output is to be appended to a log file. + // Set up a logging process to write the command's output to. + connect(proc, SIGNAL(receivedStdout(KProcess*,char*,int)), SLOT(slotCommandOutput(KProcess*,char*,int))); + connect(proc, SIGNAL(receivedStderr(KProcess*,char*,int)), SLOT(slotCommandOutput(KProcess*,char*,int))); + logproc = new ShellProcess(QString::fromLatin1("cat >>%1").arg(event.logFile())); + connect(logproc, SIGNAL(shellExited(ShellProcess*)), SLOT(slotLogProcExited(ShellProcess*))); + logproc->start(KProcess::Stdin); + QCString heading; + if (alarm && alarm->dateTime().isValid()) + { + QString dateTime = alarm->dateTime().isDateOnly() + ? KGlobal::locale()->formatDate(alarm->dateTime().date(), true) + : KGlobal::locale()->formatDateTime(alarm->dateTime().dateTime()); + heading.sprintf("\n******* KAlarm %s *******\n", dateTime.latin1()); + } + else + heading = "\n******* KAlarm *******\n"; + logproc->writeStdin(heading, heading.length()+1); + } + ProcData* pd = new ProcData(proc, logproc, new KAEvent(event), (alarm ? new KAAlarm(*alarm) : 0), flags); + if (flags & ProcData::TEMP_FILE) + pd->tempFiles += command; + if (!tmpXtermFile.isEmpty()) + pd->tempFiles += tmpXtermFile; + mCommandProcesses.append(pd); + if (proc->start(comms)) + return proc; + + // Error executing command - report it + kdError(5950) << "KAlarmApp::doShellCommand(): command failed to start\n"; + commandErrorMsg(proc, event, alarm, flags); + mCommandProcesses.remove(pd); + delete pd; + return 0; +} + +/****************************************************************************** +* Create a temporary script file containing the specified command string. +* Reply = path of temporary file, or null string if error. +*/ +QString KAlarmApp::createTempScriptFile(const QString& command, bool insertShell, const KAEvent& event, const KAAlarm& alarm) +{ + KTempFile tmpFile(QString::null, QString::null, 0700); + tmpFile.setAutoDelete(false); // don't delete file when it is destructed + QTextStream* stream = tmpFile.textStream(); + if (!stream) + kdError(5950) << "KAlarmApp::createTempScript(): Unable to create a temporary script file" << endl; + else + { + if (insertShell) + *stream << "#!" << ShellProcess::shellPath() << "\n"; + *stream << command; + tmpFile.close(); + if (tmpFile.status()) + kdError(5950) << "KAlarmApp::createTempScript(): Error " << tmpFile.status() << " writing to temporary script file" << endl; + else + return tmpFile.name(); + } + + QStringList errmsgs(i18n("Error creating temporary script file")); + (new MessageWin(event, alarm.dateTime(), errmsgs))->show(); + return QString::null; +} + +/****************************************************************************** +* Called when an executing command alarm sends output to stdout or stderr. +*/ +void KAlarmApp::slotCommandOutput(KProcess* proc, char* buffer, int bufflen) +{ +//kdDebug(5950) << "KAlarmApp::slotCommandOutput(): '" << QCString(buffer, bufflen+1) << "'\n"; + // Find this command in the command list + for (QValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) + { + ProcData* pd = *it; + if (pd->process == proc && pd->logProcess) + { + pd->logProcess->writeStdin(buffer, bufflen); + break; + } + } +} + +/****************************************************************************** +* Called when a logging process completes. +*/ +void KAlarmApp::slotLogProcExited(ShellProcess* proc) +{ + // Because it's held as a guarded pointer in the ProcData structure, + // we don't need to set any pointers to zero. + delete proc; +} + +/****************************************************************************** +* Called when a command alarm's execution completes. +*/ +void KAlarmApp::slotCommandExited(ShellProcess* proc) +{ + kdDebug(5950) << "KAlarmApp::slotCommandExited()\n"; + // Find this command in the command list + for (QValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) + { + ProcData* pd = *it; + if (pd->process == proc) + { + // Found the command + if (pd->logProcess) + pd->logProcess->stdinExit(); // terminate the logging process + + // Check its exit status + if (!proc->normalExit()) + { + QString errmsg = proc->errorMessage(); + kdWarning(5950) << "KAlarmApp::slotCommandExited(" << pd->event->cleanText() << "): " << errmsg << endl; + if (pd->messageBoxParent) + { + // Close the existing informational KMessageBox for this process + QObjectList* dialogs = pd->messageBoxParent->queryList("KDialogBase", 0, false, true); + KDialogBase* dialog = (KDialogBase*)dialogs->getFirst(); + delete dialog; + delete dialogs; + if (!pd->tempFile()) + { + errmsg += '\n'; + errmsg += proc->command(); + } + KMessageBox::error(pd->messageBoxParent, errmsg); + } + else + commandErrorMsg(proc, *pd->event, pd->alarm, pd->flags); + } + if (pd->preAction()) + execAlarm(*pd->event, *pd->alarm, pd->reschedule(), pd->allowDefer(), true); + mCommandProcesses.remove(it); + delete pd; + break; + } + } + + // If there are now no executing shell commands, quit if a quit was queued + if (mPendingQuit && mCommandProcesses.isEmpty()) + quitIf(mPendingQuitCode); +} + +/****************************************************************************** +* Output an error message for a shell command. +*/ +void KAlarmApp::commandErrorMsg(const ShellProcess* proc, const KAEvent& event, const KAAlarm* alarm, int flags) +{ + QStringList errmsgs; + if (flags & ProcData::PRE_ACTION) + errmsgs += i18n("Pre-alarm action:"); + else if (flags & ProcData::POST_ACTION) + errmsgs += i18n("Post-alarm action:"); + errmsgs += proc->errorMessage(); + if (!(flags & ProcData::TEMP_FILE)) + errmsgs += proc->command(); + (new MessageWin(event, (alarm ? alarm->dateTime() : DateTime()), errmsgs))->show(); +} + +/****************************************************************************** +* Notes that an informational KMessageBox is displayed for this process. +*/ +void KAlarmApp::commandMessage(ShellProcess* proc, QWidget* parent) +{ + // Find this command in the command list + for (QValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) + { + ProcData* pd = *it; + if (pd->process == proc) + { + pd->messageBoxParent = parent; + break; + } + } +} + +/****************************************************************************** +* Set up remaining DCOP handlers and start processing DCOP calls. +*/ +void KAlarmApp::setUpDcop() +{ + if (!mInitialised) + { + mInitialised = true; // we're now ready to handle DCOP calls + Daemon::createDcopHandler(); + QTimer::singleShot(0, this, SLOT(processQueue())); // process anything already queued + } +} + +/****************************************************************************** +* If this is the first time through, open the calendar file, optionally start +* the alarm daemon and register with it, and set up the DCOP handler. +*/ +bool KAlarmApp::initCheck(bool calendarOnly) +{ + bool startdaemon; + AlarmCalendar* cal = AlarmCalendar::activeCalendar(); + if (!cal->isOpen()) + { + kdDebug(5950) << "KAlarmApp::initCheck(): opening active calendar\n"; + + // First time through. Open the calendar file. + if (!cal->open()) + return false; + + if (!mStartOfDay.isValid()) + changeStartOfDay(); // start of day time has changed, so adjust date-only alarms + + /* Need to open the display calendar now, since otherwise if the daemon + * immediately notifies display alarms, they will often be processed while + * redisplayAlarms() is executing open() (but before open() completes), + * which causes problems!! + */ + AlarmCalendar::displayCalendar()->open(); + + /* Need to open the expired alarm calendar now, since otherwise if the daemon + * immediately notifies multiple alarms, the second alarm is likely to be + * processed while the calendar is executing open() (but before open() completes), + * which causes a hang!! + */ + AlarmCalendar::expiredCalendar()->open(); + AlarmCalendar::expiredCalendar()->setPurgeDays(theInstance->mPrefsExpiredKeepDays); + + startdaemon = true; + } + else + startdaemon = !Daemon::isRegistered(); + + if (!calendarOnly) + { + setUpDcop(); // start processing DCOP calls + if (startdaemon) + Daemon::start(); // make sure the alarm daemon is running + } + return true; +} + +/****************************************************************************** +* Convert the --time parameter string into a date/time or date value. +* The parameter is in the form [[[yyyy-]mm-]dd-]hh:mm or yyyy-mm-dd. +* Reply = true if successful. +*/ +static bool convWakeTime(const QCString& timeParam, QDateTime& dateTime, bool& noTime) +{ + if (timeParam.length() > 19) + return false; + char timeStr[20]; + strcpy(timeStr, timeParam); + int dt[5] = { -1, -1, -1, -1, -1 }; + char* s; + char* end; + // Get the minute value + if ((s = strchr(timeStr, ':')) == 0) + noTime = true; + else + { + noTime = false; + *s++ = 0; + dt[4] = strtoul(s, &end, 10); + if (end == s || *end || dt[4] >= 60) + return false; + // Get the hour value + if ((s = strrchr(timeStr, '-')) == 0) + s = timeStr; + else + *s++ = 0; + dt[3] = strtoul(s, &end, 10); + if (end == s || *end || dt[3] >= 24) + return false; + } + bool dateSet = false; + if (s != timeStr) + { + dateSet = true; + // Get the day value + if ((s = strrchr(timeStr, '-')) == 0) + s = timeStr; + else + *s++ = 0; + dt[2] = strtoul(s, &end, 10); + if (end == s || *end || dt[2] == 0 || dt[2] > 31) + return false; + if (s != timeStr) + { + // Get the month value + if ((s = strrchr(timeStr, '-')) == 0) + s = timeStr; + else + *s++ = 0; + dt[1] = strtoul(s, &end, 10); + if (end == s || *end || dt[1] == 0 || dt[1] > 12) + return false; + if (s != timeStr) + { + // Get the year value + dt[0] = strtoul(timeStr, &end, 10); + if (end == timeStr || *end) + return false; + } + } + } + + QDate date(dt[0], dt[1], dt[2]); + QTime time(0, 0, 0); + if (noTime) + { + // No time was specified, so the full date must have been specified + if (dt[0] < 0) + return false; + } + else + { + // Compile the values into a date/time structure + QDateTime now = QDateTime::currentDateTime(); + if (dt[0] < 0) + date.setYMD(now.date().year(), + (dt[1] < 0 ? now.date().month() : dt[1]), + (dt[2] < 0 ? now.date().day() : dt[2])); + time.setHMS(dt[3], dt[4], 0); + if (!dateSet && time < now.time()) + date = date.addDays(1); + } + if (!date.isValid()) + return false; + dateTime.setDate(date); + dateTime.setTime(time); + return true; +} + +/****************************************************************************** +* Convert a time interval command line parameter. +* 'timeInterval' receives the count for the recurType. If 'allowMonthYear' is +* false, 'timeInterval' is converted to minutes. +* Reply = true if successful. +*/ +static bool convInterval(const QCString& timeParam, KARecurrence::Type& recurType, int& timeInterval, bool allowMonthYear) +{ + QCString timeString = timeParam; + // Get the recurrence interval + bool ok = true; + uint interval = 0; + bool negative = (timeString[0] == '-'); + if (negative) + timeString = timeString.right(1); + uint length = timeString.length(); + switch (timeString[length - 1]) + { + case 'Y': + if (!allowMonthYear) + ok = false; + recurType = KARecurrence::ANNUAL_DATE; + timeString = timeString.left(length - 1); + break; + case 'W': + recurType = KARecurrence::WEEKLY; + timeString = timeString.left(length - 1); + break; + case 'D': + recurType = KARecurrence::DAILY; + timeString = timeString.left(length - 1); + break; + case 'M': + { + int i = timeString.find('H'); + if (i < 0) + { + if (!allowMonthYear) + ok = false; + recurType = KARecurrence::MONTHLY_DAY; + timeString = timeString.left(length - 1); + } + else + { + recurType = KARecurrence::MINUTELY; + interval = timeString.left(i).toUInt(&ok) * 60; + timeString = timeString.mid(i + 1, length - i - 2); + } + break; + } + default: // should be a digit + recurType = KARecurrence::MINUTELY; + break; + } + if (ok) + interval += timeString.toUInt(&ok); + if (!allowMonthYear) + { + // Convert time interval to minutes + switch (recurType) + { + case KARecurrence::WEEKLY: + interval *= 7; + // fall through to DAILY + case KARecurrence::DAILY: + interval *= 24*60; + break; + default: + break; + } + } + timeInterval = static_cast<int>(interval); + if (negative) + timeInterval = -timeInterval; + return ok; +} + +KAlarmApp::ProcData::ProcData(ShellProcess* p, ShellProcess* logp, KAEvent* e, KAAlarm* a, int f) + : process(p), + logProcess(logp), + event(e), + alarm(a), + messageBoxParent(0), + flags(f) +{ } + +KAlarmApp::ProcData::~ProcData() +{ + while (!tempFiles.isEmpty()) + { + // Delete the temporary file called by the XTerm command + QFile f(tempFiles.first()); + f.remove(); + tempFiles.remove(tempFiles.begin()); + } + delete process; + delete event; + delete alarm; +} |