You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2186 lines
74 KiB
2186 lines
74 KiB
/*
|
|
* 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()
|
|
: TDEUniqueApplication(),
|
|
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(TQ_SIGNAL(preferencesChanged()), this, TQ_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(), TQ_SIGNAL(purged()), TQ_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 TDEUniqueApplication 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;
|
|
TDEUniqueApplication::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, TQ_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, TQ_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, TQ_SIGNAL(deleted()), TQ_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, TQ_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, TQ_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, TQ_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, TQ_SIGNAL(shellExited(ShellProcess*)), TQ_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, TQ_SIGNAL(receivedStdout(TDEProcess*,char*,int)), TQ_SLOT(slotCommandOutput(TDEProcess*,char*,int)));
|
|
connect(proc, TQ_SIGNAL(receivedStderr(TDEProcess*,char*,int)), TQ_SLOT(slotCommandOutput(TDEProcess*,char*,int)));
|
|
logproc = new ShellProcess(TQString::fromLatin1("cat >>%1").arg(event.logFile()));
|
|
connect(logproc, TQ_SIGNAL(shellExited(ShellProcess*)), TQ_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, TQ_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;
|
|
}
|