/*
 *  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 <tqobjectlist.h>
#include <tqtimer.h>
#include <tqregexp.h>
#include <tqfile.h>

#include <tdecmdlineargs.h>
#include <tdelocale.h>
#include <kstandarddirs.h>
#include <tdeconfig.h>
#include <tdeaboutdata.h>
#include <dcopclient.h>
#include <kprocess.h>
#include <tdetempfile.h>
#include <tdefileitem.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 TQCString& timeParam, TQDateTime&, bool& noTime);
static bool convInterval(const TQCString& 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;
TQString     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(TQT_SIGNAL(preferencesChanged()), TQT_TQOBJECT(this), TQT_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(), TQT_SIGNAL(purged()), TQT_SLOT(slotExpiredPurged()));

		TDEConfig* config = kapp->config();
		config->setGroup(TQString::fromLatin1("General"));
		mNoSystemTray           = config->readBoolEntry(TQString::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 = (TDETrader::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
	TQString korg = TQString::fromLatin1("korganizer");
	mKOrganizerEnabled = !locate("exe", korg).isNull()  ||  !TDEStandardDirs::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;  TDEMainWindow::canBeRestored(i);  ++i)
	{
		TQString type = TDEMainWindow::classNameOfToplevel(i);
		if (type == TQString::fromLatin1("MainWindow"))
		{
			MainWindow* win = MainWindow::create(true);
			win->restore(i, false);
			if (win->isHiddenTrayParent())
				trayParent = win;
			else
				win->show();
		}
		else if (type == TQString::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())
	{
		TQString usage;
		TDECmdLineArgs* args = TDECmdLineArgs::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(TQString::fromLatin1("--calendarURL")).arg(TQString::fromLatin1("--handleEvent")).arg(TQString::fromLatin1("--triggerEvent")).arg(TQString::fromLatin1("--cancelEvent")))
				if (count > 1)
					USAGE(i18n("%1, %2, %3 mutually exclusive").arg(TQString::fromLatin1("--handleEvent")).arg(TQString::fromLatin1("--triggerEvent")).arg(TQString::fromLatin1("--cancelEvent")));
				if (!initCheck(true))   // open the calendar, don't register with daemon yet
				{
					exitCode = 1;
					break;
				}
				if (args->isSet("calendarURL"))
				{
					TQString calendarUrl = args->getOption("calendarURL");
					if (KURL(calendarUrl).url() != AlarmCalendar::activeCalendar()->urlString())
						USAGE(i18n("%1: wrong calendar file").arg(TQString::fromLatin1("--calendarURL")))
				}
				TQString eventID = args->getOption(option);
				args->clear();      // free up memory
				if (eventID.startsWith(TQString::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"))
			{
				TQString eventID = args->getOption("edit");
				if (!initCheck())
				{
					exitCode = 1;
					break;
				}
				if (!KAlarm::edit(eventID))
				{
					USAGE(i18n("%1: Event %2 not found, or not editable").arg(TQString::fromLatin1("--edit")).arg(eventID))
					exitCode = 1;
					break;
				}
			}
			else
			if (args->isSet("edit-new")  ||  args->isSet("edit-new-preset"))
			{
				TQString 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;
				TQCString         alMessage;
				uint             alFromID = 0;
				EmailAddressList alAddresses;
				TQStringList      alAttachments;
				TQCString         alSubject;
				if (args->isSet("file"))
				{
					kdDebug(5950)<<"KAlarmApp::newInstance(): file\n";
					if (args->isSet("exec"))
						USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--exec")).arg(TQString::fromLatin1("--file")))
					if (args->isSet("mail"))
						USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--mail")).arg(TQString::fromLatin1("--file")))
					if (args->count())
						USAGE(i18n("message incompatible with %1").arg(TQString::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(TQString::fromLatin1("--mail")).arg(TQString::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)
					{
						TQString addr = TQString::fromLocal8Bit(*i);
						if (!KAMail::checkAddress(addr))
							USAGE(i18n("%1: invalid email address").arg(TQString::fromLatin1("--mail")))
						alAddresses += KCal::Person(TQString(), addr);
					}
					params = args->getOptionList("attach");
					for (QCStringList::Iterator i = params.begin();  i != params.end();  ++i)
						alAttachments += TQString::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(TQString::fromLatin1("--subject")).arg(TQString::fromLatin1("--mail")))
					if (args->isSet("from-id"))
						USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--from-id")).arg(TQString::fromLatin1("--mail")))
					if (args->isSet("attach"))
						USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--attach")).arg(TQString::fromLatin1("--mail")))
					if (args->isSet("bcc"))
						USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--bcc")).arg(TQString::fromLatin1("--mail")))
				}

				bool      alarmNoTime = false;
				TQDateTime alarmTime, endTime;
				TQColor    bgColour = Preferences::defaultBgColour();
				TQColor    fgColour = Preferences::defaultFgColour();
				KARecurrence recurrence;
				int       repeatCount    = 0;
				int       repeatInterval = 0;
				if (args->isSet("color"))
				{
					// Background colour is specified
					TQCString 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(TQString::fromLatin1("--color")))
				}
				if (args->isSet("colorfg"))
				{
					// Foreground colour is specified
					TQCString 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(TQString::fromLatin1("--colorfg")))
				}

				if (args->isSet("time"))
				{
					TQCString dateTime = args->getOption("time");
					if (!convWakeTime(dateTime, alarmTime, alarmNoTime))
						USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--time")))
				}
				else
					alarmTime = TQDateTime::currentDateTime();

				bool haveRecurrence = args->isSet("recurrence");
				if (haveRecurrence)
				{
					if (args->isSet("login"))
						USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--login")).arg(TQString::fromLatin1("--recurrence")))
					if (args->isSet("until"))
						USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--recurrence")))
					TQCString rule = args->getOption("recurrence");
					recurrence.set(TQString::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(TQString::fromLatin1("--login")).arg(TQString::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(TQString::fromLatin1("--repeat")))
					}
					else if (haveRecurrence)
						USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--interval")).arg(TQString::fromLatin1("--repeat")))
					else if (args->isSet("until"))
					{
						count = 0;
						TQCString dateTime = args->getOption("until");
						if (!convWakeTime(dateTime, endTime, alarmNoTime))
							USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--until")))
						if (endTime < alarmTime)
							USAGE(i18n("%1 earlier than %2").arg(TQString::fromLatin1("--until")).arg(TQString::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(TQString::fromLatin1("--interval")))
					if (alarmNoTime  &&  recurType == KARecurrence::MINUTELY)
						USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(TQString::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(TQString::fromLatin1("--interval")).arg(TQString::fromLatin1("--repeat")).arg(TQString::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(TQString::fromLatin1("--repeat")).arg(TQString::fromLatin1("--interval")))
					if (args->isSet("until"))
						USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--interval")))
				}

				TQCString 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(TQString::fromLatin1("--play")).arg(TQString::fromLatin1("--play-repeat")))
					if (args->isSet("beep"))
						USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--beep")).arg(TQString::fromLatin1(audioRepeat ? "--play-repeat" : "--play")))
					if (args->isSet("speak"))
						USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--speak")).arg(TQString::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(TQString::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(TQString::fromLatin1("--volume")).arg(TQString::fromLatin1("--play")).arg(TQString::fromLatin1("--play-repeat")))
#endif
				if (args->isSet("speak"))
				{
					if (args->isSet("beep"))
						USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--beep")).arg(TQString::fromLatin1("--speak")))
					if (!mSpeechEnabled)
						USAGE(i18n("%1 requires speech synthesis to be configured using KTTSD").arg(TQString::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(TQString::fromLatin1("--reminder")).arg(TQString::fromLatin1("--reminder-once")))
					TQString opt = onceOnly ? TQString::fromLatin1("--reminder-once") : TQString::fromLatin1("--reminder");
					if (args->isSet("exec"))
						USAGE(i18n("%1 incompatible with %2").arg(opt).arg(TQString::fromLatin1("--exec")))
					if (args->isSet("mail"))
						USAGE(i18n("%1 incompatible with %2").arg(opt).arg(TQString::fromLatin1("--mail")))
					KARecurrence::Type recurType;
					TQString 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(TQString::fromLatin1("late-cancel")))
				}
				else if (args->isSet("auto-close"))
					USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--auto-close")).arg(TQString::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, TQFont(), 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 += TQString::fromLatin1("--ack-confirm ");
				if (args->isSet("attach"))
					usage += TQString::fromLatin1("--attach ");
				if (args->isSet("auto-close"))
					usage += TQString::fromLatin1("--auto-close ");
				if (args->isSet("bcc"))
					usage += TQString::fromLatin1("--bcc ");
				if (args->isSet("beep"))
					usage += TQString::fromLatin1("--beep ");
				if (args->isSet("color"))
					usage += TQString::fromLatin1("--color ");
				if (args->isSet("colorfg"))
					usage += TQString::fromLatin1("--colorfg ");
				if (args->isSet("disable"))
					usage += TQString::fromLatin1("--disable ");
				if (args->isSet("from-id"))
					usage += TQString::fromLatin1("--from-id ");
				if (args->isSet("korganizer"))
					usage += TQString::fromLatin1("--korganizer ");
				if (args->isSet("late-cancel"))
					usage += TQString::fromLatin1("--late-cancel ");
				if (args->isSet("login"))
					usage += TQString::fromLatin1("--login ");
				if (args->isSet("play"))
					usage += TQString::fromLatin1("--play ");
#ifndef WITHOUT_ARTS
				if (args->isSet("play-repeat"))
					usage += TQString::fromLatin1("--play-repeat ");
#endif
				if (args->isSet("reminder"))
					usage += TQString::fromLatin1("--reminder ");
				if (args->isSet("reminder-once"))
					usage += TQString::fromLatin1("--reminder-once ");
				if (args->isSet("speak"))
					usage += TQString::fromLatin1("--speak ");
				if (args->isSet("subject"))
					usage += TQString::fromLatin1("--subject ");
				if (args->isSet("time"))
					usage += TQString::fromLatin1("--time ");
#ifndef WITHOUT_ARTS
				if (args->isSet("volume"))
					usage += TQString::fromLatin1("--volume ");
#endif
				if (!usage.isEmpty())
				{
					usage += i18n(": option(s) only valid with a message/%1/%2").arg(TQString::fromLatin1("--file")).arg(TQString::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(TQWidget* 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)."),
	                                      TQString(), 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(TQSessionManager& 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 TQString& message)
{
	if (!mFatalError)
	{
		mFatalError = 1;
		mFatalMessage = message;
		if (theInstance)
			TQTimer::singleShot(0, theInstance, TQT_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;
	}
	TQTimer::singleShot(1000, this, TQT_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())
		{
			DcopTQEntry& 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, TQT_SIGNAL(deleted()), TQT_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;
			TQTimer::singleShot(0, this, TQT_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.
		TDEConfig* config = kapp->config();
		config->setGroup(TQString::fromLatin1("General"));
		config->writeEntry(TQString::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
	TQTime 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 TQString& text, const TQDateTime& dateTime,
                              int lateCancel, int flags, const TQColor& bg, const TQColor& fg, const TQFont& font,
                              const TQString& audioFile, float audioVolume, int reminderMinutes,
                              const KARecurrence& recurrence, int repeatInterval, int repeatCount,
                              uint mailFromID, const EmailAddressList& mailAddresses,
                              const TQString& mailSubject, const TQStringList& mailAttachments)
{
	kdDebug(5950) << "KAlarmApp::scheduleEvent(): " << text << endl;
	if (!dateTime.isValid())
		return false;
	TQDateTime now = TQDateTime::currentDateTime();
	if (lateCancel  &&  dateTime < now.addSecs(-maxLateness(lateCancel)))
		return true;               // alarm time was already expired too long ago
	TQDateTime alarmTime = dateTime;
	// Round down to the nearest minute to avoid scheduling being messed up
	alarmTime.setTime(TQTime(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(DcopTQEntry(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(DcopTQEntry(event));
	if (mInitialised)
		TQTimer::singleShot(0, this, TQT_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 TQString& urlString, const TQString& 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(DcopTQEntry(function, eventID));
	if (mInitialised)
		TQTimer::singleShot(0, this, TQT_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 TQString& 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
		{
			TQDateTime now = TQDateTime::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
						TQDateTime 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())
	{
		TQString 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(TQDateTime::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.
		TQString 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 (TQValueList<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
					}
				}

				TQString 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;
				(new MessageWin(event, alarm, reschedule, allowDefer))->show();
			}
			else
			{
				// Raise the existing message window and replay any sound
				win->repeat(alarm);    // N.B. this reschedules the alarm
			}
			break;
		}
		case KAAlarm::COMMAND:
		{
			int flags = event.commandXterm() ? ProcData::EXEC_IN_XTERM : 0;
			TQString command = event.cleanText();
			if (event.commandScript())
			{
				// Store the command script in a temporary file for execution
				kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: (script)" << endl;
				TQString 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;
			TQStringList 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 TQString& command, const KAEvent& event, const KAAlarm* alarm, int flags)
{
	kdDebug(5950) << "KAlarmApp::doShellCommand(" << command << ", " << event.id() << ")" << endl;
	TDEProcess::Communication comms = TDEProcess::NoCommunication;
	TQString cmd;
	TQString 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 + TQString::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 [>|;].
			TQString exec = KShellProcess::quote(command + TQString::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 [>|;].
			TQString 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 = TDEProcess::AllOutput;
	}
	ShellProcess* proc = new ShellProcess(cmd);
	connect(proc, TQT_SIGNAL(shellExited(ShellProcess*)), TQT_SLOT(slotCommandExited(ShellProcess*)));
	TQGuardedPtr<ShellProcess> logproc = 0;
	if (comms == TDEProcess::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, TQT_SIGNAL(receivedStdout(TDEProcess*,char*,int)), TQT_SLOT(slotCommandOutput(TDEProcess*,char*,int)));
		connect(proc, TQT_SIGNAL(receivedStderr(TDEProcess*,char*,int)), TQT_SLOT(slotCommandOutput(TDEProcess*,char*,int)));
		logproc = new ShellProcess(TQString::fromLatin1("cat >>%1").arg(event.logFile()));
		connect(logproc, TQT_SIGNAL(shellExited(ShellProcess*)), TQT_SLOT(slotLogProcExited(ShellProcess*)));
		logproc->start(TDEProcess::Stdin);
		TQCString heading;
		if (alarm  &&  alarm->dateTime().isValid())
		{
			TQString dateTime = alarm->dateTime().isDateOnly()
			                 ? TDEGlobal::locale()->formatDate(alarm->dateTime().date(), true)
			                 : TDEGlobal::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.
*/
TQString KAlarmApp::createTempScriptFile(const TQString& command, bool insertShell, const KAEvent& event, const KAAlarm& alarm)
{
	KTempFile tmpFile(TQString(), TQString(), 0700);
	tmpFile.setAutoDelete(false);     // don't delete file when it is destructed
	TQTextStream* 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();
	}

	TQStringList errmsgs(i18n("Error creating temporary script file"));
	(new MessageWin(event, alarm.dateTime(), errmsgs))->show();
	return TQString();
}

/******************************************************************************
* Called when an executing command alarm sends output to stdout or stderr.
*/
void KAlarmApp::slotCommandOutput(TDEProcess* proc, char* buffer, int bufflen)
{
//kdDebug(5950) << "KAlarmApp::slotCommandOutput(): '" << TQCString(buffer, bufflen+1) << "'\n";
	// Find this command in the command list
	for (TQValueList<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 (TQValueList<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())
			{
				TQString errmsg = proc->errorMessage();
				kdWarning(5950) << "KAlarmApp::slotCommandExited(" << pd->event->cleanText() << "): " << errmsg << endl;
				if (pd->messageBoxParent)
				{
					// Close the existing informational KMessageBox for this process
					TQObjectList* 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)
{
	TQStringList 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, TQWidget* parent)
{
	// Find this command in the command list
	for (TQValueList<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();
		TQTimer::singleShot(0, this, TQT_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 TQCString& timeParam, TQDateTime& 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;
			}
		}
	}

	TQDate date(dt[0], dt[1], dt[2]);
	TQTime 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
		TQDateTime now = TQDateTime::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 TQCString& timeParam, KARecurrence::Type& recurType, int& timeInterval, bool allowMonthYear)
{
	TQCString 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
		TQFile f(tempFiles.first());
		f.remove();
		tempFiles.remove(tempFiles.begin());
	}
	delete process;
	delete event;
	delete alarm;
}
