kalarm

kalarmapp.cpp
1/*
2 * kalarmapp.cpp - the KAlarm application object
3 * Program: kalarm
4 * Copyright © 2001-2009 by David Jarvie <djarvie@kde.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21#include "kalarm.h"
22
23#include <stdlib.h>
24#include <ctype.h>
25#include <iostream>
26
27#include <tqobjectlist.h>
28#include <tqtimer.h>
29#include <tqregexp.h>
30#include <tqfile.h>
31
32#include <tdecmdlineargs.h>
33#include <tdelocale.h>
34#include <tdestandarddirs.h>
35#include <tdeconfig.h>
36#include <tdeaboutdata.h>
37#include <dcopclient.h>
38#include <tdeprocess.h>
39#include <tdetempfile.h>
40#include <tdefileitem.h>
41#include <kstdguiitem.h>
42#include <ktrader.h>
43#include <kstaticdeleter.h>
44#include <kdebug.h>
45
46#include <libkcal/calformat.h>
47
48#include <kalarmd/clientinfo.h>
49
50#include "alarmcalendar.h"
51#include "alarmlistview.h"
52#include "birthdaydlg.h"
53#include "editdlg.h"
54#include "daemon.h"
55#include "dcophandler.h"
56#include "functions.h"
57#include "kamail.h"
58#include "karecurrence.h"
59#include "mainwindow.h"
60#include "messagebox.h"
61#include "messagewin.h"
62#include "preferences.h"
63#include "prefdlg.h"
64#include "shellprocess.h"
65#include "traywindow.h"
66#include "kalarmapp.moc"
67
68#include <netwm.h>
69
70
71static bool convWakeTime(const TQCString& timeParam, TQDateTime&, bool& noTime);
72static bool convInterval(const TQCString& timeParam, KARecurrence::Type&, int& timeInterval, bool allowMonthYear = false);
73
74/******************************************************************************
75* Find the maximum number of seconds late which a late-cancel alarm is allowed
76* to be. This is calculated as the alarm daemon's check interval, plus a few
77* seconds leeway to cater for any timing irregularities.
78*/
79static inline int maxLateness(int lateCancel)
80{
81 static const int LATENESS_LEEWAY = 5;
82 int lc = (lateCancel >= 1) ? (lateCancel - 1)*60 : 0;
83 return Daemon::maxTimeSinceCheck() + LATENESS_LEEWAY + lc;
84}
85
86
87KAlarmApp* KAlarmApp::theInstance = 0;
88int KAlarmApp::mActiveCount = 0;
89int KAlarmApp::mFatalError = 0;
90TQString KAlarmApp::mFatalMessage;
91
92
93/******************************************************************************
94* Construct the application.
95*/
96KAlarmApp::KAlarmApp()
97 : TDEUniqueApplication(),
98 mInitialised(false),
99 mDcopHandler(new DcopHandler()),
100#ifdef OLD_DCOP
101 mDcopHandlerOld(new DcopHandlerOld()),
102#endif
103 mTrayWindow(0),
104 mPendingQuit(false),
105 mProcessingQueue(false),
106 mCheckingSystemTray(false),
107 mSessionClosingDown(false),
108 mRefreshExpiredAlarms(false),
109 mSpeechEnabled(false)
110{
111 Preferences::initialise();
112 Preferences::connect(TQ_SIGNAL(preferencesChanged()), this, TQ_SLOT(slotPreferencesChanged()));
113 KCal::CalFormat::setApplication(aboutData()->programName(), AlarmCalendar::icalProductId());
114 KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type());
115
116 // Check if the system tray is supported by this window manager
117 mHaveSystemTray = true; // assume yes in lieu of a test which works
118
119 if (AlarmCalendar::initialiseCalendars())
120 {
121 connect(AlarmCalendar::expiredCalendar(), TQ_SIGNAL(purged()), TQ_SLOT(slotExpiredPurged()));
122
123 TDEConfig* config = tdeApp->config();
124 config->setGroup(TQString::fromLatin1("General"));
125 mNoSystemTray = config->readBoolEntry(TQString::fromLatin1("NoSystemTray"), false);
126 mSavedNoSystemTray = mNoSystemTray;
127 mOldRunInSystemTray = wantRunInSystemTray();
128 mDisableAlarmsIfStopped = mOldRunInSystemTray && !mNoSystemTray && Preferences::disableAlarmsIfStopped();
129 mStartOfDay = Preferences::startOfDay();
130 if (Preferences::hasStartOfDayChanged())
131 mStartOfDay.setHMS(100,0,0); // start of day time has changed: flag it as invalid
132 DateTime::setStartOfDay(mStartOfDay);
133 mPrefsExpiredColour = Preferences::expiredColour();
134 mPrefsExpiredKeepDays = Preferences::expiredKeepDays();
135 }
136
137 // Check if the speech synthesis daemon is installed
138 mSpeechEnabled = (TDETrader::self()->query("DCOP/Text-to-Speech", "Name == 'KTTSD'").count() > 0);
139 if (!mSpeechEnabled)
140 kdDebug(5950) << "KAlarmApp::KAlarmApp(): speech synthesis disabled (KTTSD not found)" << endl;
141 // Check if KOrganizer is installed
142 TQString korg = TQString::fromLatin1("korganizer");
143 mKOrganizerEnabled = !locate("exe", korg).isNull() || !TDEStandardDirs::findExe(korg).isNull();
144 if (!mKOrganizerEnabled)
145 kdDebug(5950) << "KAlarmApp::KAlarmApp(): KOrganizer options disabled (KOrganizer not found)" << endl;
146}
147
148/******************************************************************************
149*/
150KAlarmApp::~KAlarmApp()
151{
152 while (!mCommandProcesses.isEmpty())
153 {
154 ProcData* pd = mCommandProcesses.first();
155 mCommandProcesses.pop_front();
156 delete pd;
157 }
158 AlarmCalendar::terminateCalendars();
159}
160
161/******************************************************************************
162* Return the one and only KAlarmApp instance.
163* If it doesn't already exist, it is created first.
164*/
165KAlarmApp* KAlarmApp::getInstance()
166{
167 if (!theInstance)
168 {
169 theInstance = new KAlarmApp;
170
171 if (mFatalError)
172 theInstance->quitFatal();
173 else
174 {
175 // This is here instead of in the constructor to avoid recursion
176 Daemon::initialise(); // calendars must be initialised before calling this
177 }
178 }
179 return theInstance;
180}
181
182/******************************************************************************
183* Restore the saved session if required.
184*/
185bool KAlarmApp::restoreSession()
186{
187 if (!isRestored())
188 return false;
189 if (mFatalError)
190 {
191 quitFatal();
192 return false;
193 }
194
195 // Process is being restored by session management.
196 kdDebug(5950) << "KAlarmApp::restoreSession(): Restoring\n";
197 ++mActiveCount;
198 if (!initCheck(true)) // open the calendar file (needed for main windows)
199 {
200 --mActiveCount;
201 quitIf(1, true); // error opening the main calendar - quit
202 return true;
203 }
204 MainWindow* trayParent = 0;
205 for (int i = 1; TDEMainWindow::canBeRestored(i); ++i)
206 {
207 TQString type = TDEMainWindow::classNameOfToplevel(i);
208 if (type == TQString::fromLatin1("MainWindow"))
209 {
210 MainWindow* win = MainWindow::create(true);
211 win->restore(i, false);
212 if (win->isHiddenTrayParent())
213 trayParent = win;
214 else
215 win->show();
216 }
217 else if (type == TQString::fromLatin1("MessageWin"))
218 {
219 MessageWin* win = new MessageWin;
220 win->restore(i, false);
221 if (win->isValid())
222 win->show();
223 else
224 delete win;
225 }
226 }
227 initCheck(); // register with the alarm daemon
228
229 // Try to display the system tray icon if it is configured to be autostarted,
230 // or if we're in run-in-system-tray mode.
231 if (Preferences::autostartTrayIcon()
232 || (MainWindow::count() && wantRunInSystemTray()))
233 {
234 displayTrayIcon(true, trayParent);
235 // Occasionally for no obvious reason, the main main window is
236 // shown when it should be hidden, so hide it just to be sure.
237 if (trayParent)
238 trayParent->hide();
239 }
240
241 --mActiveCount;
242 quitIf(0); // quit if no windows are open
243 return true;
244}
245
246/******************************************************************************
247* Called for a TDEUniqueApplication when a new instance of the application is
248* started.
249*/
250int KAlarmApp::newInstance()
251{
252 kdDebug(5950)<<"KAlarmApp::newInstance()\n";
253 if (mFatalError)
254 {
255 quitFatal();
256 return 1;
257 }
258 ++mActiveCount;
259 int exitCode = 0; // default = success
260 static bool firstInstance = true;
261 bool dontRedisplay = false;
262 if (!firstInstance || !isRestored())
263 {
264 TQString usage;
265 TDECmdLineArgs* args = TDECmdLineArgs::parsedArgs();
266
267 // Use a 'do' loop which is executed only once to allow easy error exits.
268 // Errors use 'break' to skip to the end of the function.
269
270 // Note that DCOP handling is only set up once the command line parameters
271 // have been checked, since we mustn't register with the alarm daemon only
272 // to quit immediately afterwards.
273 do
274 {
275 #define USAGE(message) { usage = message; break; }
276 if (args->isSet("stop"))
277 {
278 // Stop the alarm daemon
279 kdDebug(5950)<<"KAlarmApp::newInstance(): stop\n";
280 args->clear(); // free up memory
281 if (!Daemon::stop())
282 {
283 exitCode = 1;
284 break;
285 }
286 dontRedisplay = true; // exit program if no other instances running
287 }
288 else
289 if (args->isSet("reset"))
290 {
291 // Reset the alarm daemon, if it's running.
292 // (If it's not running, it will reset automatically when it eventually starts.)
293 kdDebug(5950)<<"KAlarmApp::newInstance(): reset\n";
294 args->clear(); // free up memory
295 Daemon::reset();
296 dontRedisplay = true; // exit program if no other instances running
297 }
298 else
299 if (args->isSet("tray"))
300 {
301 // Display only the system tray icon
302 kdDebug(5950)<<"KAlarmApp::newInstance(): tray\n";
303 args->clear(); // free up memory
304 if (!mHaveSystemTray)
305 {
306 exitCode = 1;
307 break;
308 }
309 if (!initCheck()) // open the calendar, register with daemon
310 {
311 exitCode = 1;
312 break;
313 }
314 if (!displayTrayIcon(true))
315 {
316 exitCode = 1;
317 break;
318 }
319 }
320 else
321 if (args->isSet("handleEvent") || args->isSet("triggerEvent") || args->isSet("cancelEvent") || args->isSet("calendarURL"))
322 {
323 // Display or delete the event with the specified event ID
324 kdDebug(5950)<<"KAlarmApp::newInstance(): handle event\n";
325 EventFunc function = EVENT_HANDLE;
326 int count = 0;
327 const char* option = 0;
328 if (args->isSet("handleEvent")) { function = EVENT_HANDLE; option = "handleEvent"; ++count; }
329 if (args->isSet("triggerEvent")) { function = EVENT_TRIGGER; option = "triggerEvent"; ++count; }
330 if (args->isSet("cancelEvent")) { function = EVENT_CANCEL; option = "cancelEvent"; ++count; }
331 if (!count)
332 USAGE(i18n("%1 requires %2, %3 or %4").arg(TQString::fromLatin1("--calendarURL")).arg(TQString::fromLatin1("--handleEvent")).arg(TQString::fromLatin1("--triggerEvent")).arg(TQString::fromLatin1("--cancelEvent")))
333 if (count > 1)
334 USAGE(i18n("%1, %2, %3 mutually exclusive").arg(TQString::fromLatin1("--handleEvent")).arg(TQString::fromLatin1("--triggerEvent")).arg(TQString::fromLatin1("--cancelEvent")));
335 if (!initCheck(true)) // open the calendar, don't register with daemon yet
336 {
337 exitCode = 1;
338 break;
339 }
340 if (args->isSet("calendarURL"))
341 {
342 TQString calendarUrl = args->getOption("calendarURL");
343 if (KURL(calendarUrl).url() != AlarmCalendar::activeCalendar()->urlString())
344 USAGE(i18n("%1: wrong calendar file").arg(TQString::fromLatin1("--calendarURL")))
345 }
346 TQString eventID = args->getOption(option);
347 args->clear(); // free up memory
348 if (eventID.startsWith(TQString::fromLatin1("ad:")))
349 {
350 // It's a notification from the alarm deamon
351 eventID = eventID.mid(3);
352 Daemon::queueEvent(eventID);
353 }
354 setUpDcop(); // start processing DCOP calls
355 if (!handleEvent(eventID, function))
356 {
357 exitCode = 1;
358 break;
359 }
360 }
361 else
362 if (args->isSet("edit"))
363 {
364 TQString eventID = args->getOption("edit");
365 if (!initCheck())
366 {
367 exitCode = 1;
368 break;
369 }
370 if (!KAlarm::edit(eventID))
371 {
372 USAGE(i18n("%1: Event %2 not found, or not editable").arg(TQString::fromLatin1("--edit")).arg(eventID))
373 exitCode = 1;
374 break;
375 }
376 }
377 else
378 if (args->isSet("edit-new") || args->isSet("edit-new-preset"))
379 {
380 TQString templ;
381 if (args->isSet("edit-new-preset"))
382 templ = args->getOption("edit-new-preset");
383 if (!initCheck())
384 {
385 exitCode = 1;
386 break;
387 }
388 KAlarm::editNew(templ);
389 }
390 else
391 if (args->isSet("file") || args->isSet("exec") || args->isSet("mail") || args->count())
392 {
393 // Display a message or file, execute a command, or send an email
394 KAEvent::Action action = KAEvent::MESSAGE;
395 TQCString alMessage;
396 uint alFromID = 0;
397 EmailAddressList alAddresses;
398 TQStringList alAttachments;
399 TQCString alSubject;
400 if (args->isSet("file"))
401 {
402 kdDebug(5950)<<"KAlarmApp::newInstance(): file\n";
403 if (args->isSet("exec"))
404 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--exec")).arg(TQString::fromLatin1("--file")))
405 if (args->isSet("mail"))
406 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--mail")).arg(TQString::fromLatin1("--file")))
407 if (args->count())
408 USAGE(i18n("message incompatible with %1").arg(TQString::fromLatin1("--file")))
409 alMessage = args->getOption("file");
410 action = KAEvent::FILE;
411 }
412 else if (args->isSet("exec"))
413 {
414 kdDebug(5950)<<"KAlarmApp::newInstance(): exec\n";
415 if (args->isSet("mail"))
416 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--mail")).arg(TQString::fromLatin1("--exec")))
417 alMessage = args->getOption("exec");
418 int n = args->count();
419 for (int i = 0; i < n; ++i)
420 {
421 alMessage += ' ';
422 alMessage += args->arg(i);
423 }
424 action = KAEvent::COMMAND;
425 }
426 else if (args->isSet("mail"))
427 {
428 kdDebug(5950)<<"KAlarmApp::newInstance(): mail\n";
429 if (args->isSet("subject"))
430 alSubject = args->getOption("subject");
431 if (args->isSet("from-id"))
432 alFromID = KAMail::identityUoid(args->getOption("from-id"));
433 QCStringList params = args->getOptionList("mail");
434 for (QCStringList::Iterator i = params.begin(); i != params.end(); ++i)
435 {
436 TQString addr = TQString::fromLocal8Bit(*i);
437 if (!KAMail::checkAddress(addr))
438 USAGE(i18n("%1: invalid email address").arg(TQString::fromLatin1("--mail")))
439 alAddresses += KCal::Person(TQString(), addr);
440 }
441 params = args->getOptionList("attach");
442 for (QCStringList::Iterator i = params.begin(); i != params.end(); ++i)
443 alAttachments += TQString::fromLocal8Bit(*i);
444 alMessage = args->arg(0);
445 action = KAEvent::EMAIL;
446 }
447 else
448 {
449 kdDebug(5950)<<"KAlarmApp::newInstance(): message\n";
450 alMessage = args->arg(0);
451 }
452
453 if (action != KAEvent::EMAIL)
454 {
455 if (args->isSet("subject"))
456 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--subject")).arg(TQString::fromLatin1("--mail")))
457 if (args->isSet("from-id"))
458 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--from-id")).arg(TQString::fromLatin1("--mail")))
459 if (args->isSet("attach"))
460 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--attach")).arg(TQString::fromLatin1("--mail")))
461 if (args->isSet("bcc"))
462 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--bcc")).arg(TQString::fromLatin1("--mail")))
463 }
464
465 bool alarmNoTime = false;
466 TQDateTime alarmTime, endTime;
467 TQColor bgColour = Preferences::defaultBgColour();
468 TQColor fgColour = Preferences::defaultFgColour();
469 KARecurrence recurrence;
470 int repeatCount = 0;
471 int repeatInterval = 0;
472 if (args->isSet("color"))
473 {
474 // Background colour is specified
475 TQCString colourText = args->getOption("color");
476 if (static_cast<const char*>(colourText)[0] == '0'
477 && tolower(static_cast<const char*>(colourText)[1]) == 'x')
478 colourText.replace(0, 2, "#");
479 bgColour.setNamedColor(colourText);
480 if (!bgColour.isValid())
481 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--color")))
482 }
483 if (args->isSet("colorfg"))
484 {
485 // Foreground colour is specified
486 TQCString colourText = args->getOption("colorfg");
487 if (static_cast<const char*>(colourText)[0] == '0'
488 && tolower(static_cast<const char*>(colourText)[1]) == 'x')
489 colourText.replace(0, 2, "#");
490 fgColour.setNamedColor(colourText);
491 if (!fgColour.isValid())
492 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--colorfg")))
493 }
494
495 if (args->isSet("time"))
496 {
497 TQCString dateTime = args->getOption("time");
498 if (!convWakeTime(dateTime, alarmTime, alarmNoTime))
499 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--time")))
500 }
501 else
502 alarmTime = TQDateTime::currentDateTime();
503
504 bool haveRecurrence = args->isSet("recurrence");
505 if (haveRecurrence)
506 {
507 if (args->isSet("login"))
508 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--login")).arg(TQString::fromLatin1("--recurrence")))
509 if (args->isSet("until"))
510 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--recurrence")))
511 TQCString rule = args->getOption("recurrence");
512 recurrence.set(TQString::fromLocal8Bit(static_cast<const char*>(rule)));
513 }
514 if (args->isSet("interval"))
515 {
516 // Repeat count is specified
517 int count;
518 if (args->isSet("login"))
519 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--login")).arg(TQString::fromLatin1("--interval")))
520 bool ok;
521 if (args->isSet("repeat"))
522 {
523 count = args->getOption("repeat").toInt(&ok);
524 if (!ok || !count || count < -1 || (count < 0 && haveRecurrence))
525 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--repeat")))
526 }
527 else if (haveRecurrence)
528 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--interval")).arg(TQString::fromLatin1("--repeat")))
529 else if (args->isSet("until"))
530 {
531 count = 0;
532 TQCString dateTime = args->getOption("until");
533 if (!convWakeTime(dateTime, endTime, alarmNoTime))
534 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--until")))
535 if (endTime < alarmTime)
536 USAGE(i18n("%1 earlier than %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--time")))
537 }
538 else
539 count = -1;
540
541 // Get the recurrence interval
542 int interval;
543 KARecurrence::Type recurType;
544 if (!convInterval(args->getOption("interval"), recurType, interval, !haveRecurrence)
545 || interval < 0)
546 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--interval")))
547 if (alarmNoTime && recurType == KARecurrence::MINUTELY)
548 USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(TQString::fromLatin1("--interval")))
549
550 if (haveRecurrence)
551 {
552 // There is a also a recurrence specified, so set up a sub-repetition
553 int longestInterval = recurrence.longestInterval();
554 if (count * interval > longestInterval)
555 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")));
556 repeatCount = count;
557 repeatInterval = interval;
558 }
559 else
560 {
561 // There is no other recurrence specified, so convert the repetition
562 // parameters into a KCal::Recurrence
563 recurrence.set(recurType, interval, count, DateTime(alarmTime, alarmNoTime), endTime);
564 }
565 }
566 else
567 {
568 if (args->isSet("repeat"))
569 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--repeat")).arg(TQString::fromLatin1("--interval")))
570 if (args->isSet("until"))
571 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--interval")))
572 }
573
574 TQCString audioFile;
575 float audioVolume = -1;
576#ifdef WITHOUT_ARTS
577 bool audioRepeat = false;
578#else
579 bool audioRepeat = args->isSet("play-repeat");
580#endif
581 if (audioRepeat || args->isSet("play"))
582 {
583 // Play a sound with the alarm
584 if (audioRepeat && args->isSet("play"))
585 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--play")).arg(TQString::fromLatin1("--play-repeat")))
586 if (args->isSet("beep"))
587 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--beep")).arg(TQString::fromLatin1(audioRepeat ? "--play-repeat" : "--play")))
588 if (args->isSet("speak"))
589 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--speak")).arg(TQString::fromLatin1(audioRepeat ? "--play-repeat" : "--play")))
590 audioFile = args->getOption(audioRepeat ? "play-repeat" : "play");
591#ifndef WITHOUT_ARTS
592 if (args->isSet("volume"))
593 {
594 bool ok;
595 int volumepc = args->getOption("volume").toInt(&ok);
596 if (!ok || volumepc < 0 || volumepc > 100)
597 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--volume")))
598 audioVolume = static_cast<float>(volumepc) / 100;
599 }
600#endif
601 }
602#ifndef WITHOUT_ARTS
603 else if (args->isSet("volume"))
604 USAGE(i18n("%1 requires %2 or %3").arg(TQString::fromLatin1("--volume")).arg(TQString::fromLatin1("--play")).arg(TQString::fromLatin1("--play-repeat")))
605#endif
606 if (args->isSet("speak"))
607 {
608 if (args->isSet("beep"))
609 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--beep")).arg(TQString::fromLatin1("--speak")))
610 if (!mSpeechEnabled)
611 USAGE(i18n("%1 requires speech synthesis to be configured using KTTSD").arg(TQString::fromLatin1("--speak")))
612 }
613 int reminderMinutes = 0;
614 bool onceOnly = args->isSet("reminder-once");
615 if (args->isSet("reminder") || onceOnly)
616 {
617 // Issue a reminder alarm in advance of the main alarm
618 if (onceOnly && args->isSet("reminder"))
619 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--reminder")).arg(TQString::fromLatin1("--reminder-once")))
620 TQString opt = onceOnly ? TQString::fromLatin1("--reminder-once") : TQString::fromLatin1("--reminder");
621 if (args->isSet("exec"))
622 USAGE(i18n("%1 incompatible with %2").arg(opt).arg(TQString::fromLatin1("--exec")))
623 if (args->isSet("mail"))
624 USAGE(i18n("%1 incompatible with %2").arg(opt).arg(TQString::fromLatin1("--mail")))
625 KARecurrence::Type recurType;
626 TQString optval = args->getOption(onceOnly ? "reminder-once" : "reminder");
627 if (!convInterval(args->getOption(onceOnly ? "reminder-once" : "reminder"), recurType, reminderMinutes))
628 USAGE(i18n("Invalid %1 parameter").arg(opt))
629 if (recurType == KARecurrence::MINUTELY && alarmNoTime)
630 USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(opt))
631 }
632
633 int lateCancel = 0;
634 if (args->isSet("late-cancel"))
635 {
636 KARecurrence::Type recurType;
637 bool ok = convInterval(args->getOption("late-cancel"), recurType, lateCancel);
638 if (!ok || lateCancel <= 0)
639 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("late-cancel")))
640 }
641 else if (args->isSet("auto-close"))
642 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--auto-close")).arg(TQString::fromLatin1("--late-cancel")))
643
644 int flags = KAEvent::DEFAULT_FONT;
645 if (args->isSet("ack-confirm"))
646 flags |= KAEvent::CONFIRM_ACK;
647 if (args->isSet("auto-close"))
648 flags |= KAEvent::AUTO_CLOSE;
649 if (args->isSet("beep"))
650 flags |= KAEvent::BEEP;
651 if (args->isSet("speak"))
652 flags |= KAEvent::SPEAK;
653 if (args->isSet("korganizer"))
654 flags |= KAEvent::COPY_KORGANIZER;
655 if (args->isSet("disable"))
656 flags |= KAEvent::DISABLED;
657 if (audioRepeat)
658 flags |= KAEvent::REPEAT_SOUND;
659 if (args->isSet("login"))
660 flags |= KAEvent::REPEAT_AT_LOGIN;
661 if (args->isSet("bcc"))
662 flags |= KAEvent::EMAIL_BCC;
663 if (alarmNoTime)
664 flags |= KAEvent::ANY_TIME;
665 args->clear(); // free up memory
666
667 // Display or schedule the event
668 if (!initCheck())
669 {
670 exitCode = 1;
671 break;
672 }
673 if (!scheduleEvent(action, alMessage, alarmTime, lateCancel, flags, bgColour, fgColour, TQFont(), audioFile,
674 audioVolume, reminderMinutes, recurrence, repeatInterval, repeatCount,
675 alFromID, alAddresses, alSubject, alAttachments))
676 {
677 exitCode = 1;
678 break;
679 }
680 }
681 else
682 {
683 // No arguments - run interactively & display the main window
684 kdDebug(5950)<<"KAlarmApp::newInstance(): interactive\n";
685 if (args->isSet("ack-confirm"))
686 usage += TQString::fromLatin1("--ack-confirm ");
687 if (args->isSet("attach"))
688 usage += TQString::fromLatin1("--attach ");
689 if (args->isSet("auto-close"))
690 usage += TQString::fromLatin1("--auto-close ");
691 if (args->isSet("bcc"))
692 usage += TQString::fromLatin1("--bcc ");
693 if (args->isSet("beep"))
694 usage += TQString::fromLatin1("--beep ");
695 if (args->isSet("color"))
696 usage += TQString::fromLatin1("--color ");
697 if (args->isSet("colorfg"))
698 usage += TQString::fromLatin1("--colorfg ");
699 if (args->isSet("disable"))
700 usage += TQString::fromLatin1("--disable ");
701 if (args->isSet("from-id"))
702 usage += TQString::fromLatin1("--from-id ");
703 if (args->isSet("korganizer"))
704 usage += TQString::fromLatin1("--korganizer ");
705 if (args->isSet("late-cancel"))
706 usage += TQString::fromLatin1("--late-cancel ");
707 if (args->isSet("login"))
708 usage += TQString::fromLatin1("--login ");
709 if (args->isSet("play"))
710 usage += TQString::fromLatin1("--play ");
711#ifndef WITHOUT_ARTS
712 if (args->isSet("play-repeat"))
713 usage += TQString::fromLatin1("--play-repeat ");
714#endif
715 if (args->isSet("reminder"))
716 usage += TQString::fromLatin1("--reminder ");
717 if (args->isSet("reminder-once"))
718 usage += TQString::fromLatin1("--reminder-once ");
719 if (args->isSet("speak"))
720 usage += TQString::fromLatin1("--speak ");
721 if (args->isSet("subject"))
722 usage += TQString::fromLatin1("--subject ");
723 if (args->isSet("time"))
724 usage += TQString::fromLatin1("--time ");
725#ifndef WITHOUT_ARTS
726 if (args->isSet("volume"))
727 usage += TQString::fromLatin1("--volume ");
728#endif
729 if (!usage.isEmpty())
730 {
731 usage += i18n(": option(s) only valid with a message/%1/%2").arg(TQString::fromLatin1("--file")).arg(TQString::fromLatin1("--exec"));
732 break;
733 }
734
735 args->clear(); // free up memory
736 if (!initCheck())
737 {
738 exitCode = 1;
739 break;
740 }
741
742 (MainWindow::create())->show();
743 }
744 } while (0); // only execute once
745
746 if (!usage.isEmpty())
747 {
748 // Note: we can't use args->usage() since that also quits any other
749 // running 'instances' of the program.
750 std::cerr << usage.local8Bit().data()
751 << i18n("\nUse --help to get a list of available command line options.\n").local8Bit().data();
752 exitCode = 1;
753 }
754 }
755 if (firstInstance && !dontRedisplay && !exitCode)
756 redisplayAlarms();
757
758 --mActiveCount;
759 firstInstance = false;
760
761 // Quit the application if this was the last/only running "instance" of the program.
762 // Executing 'return' doesn't work very well since the program continues to
763 // run if no windows were created.
764 quitIf(exitCode);
765 return exitCode;
766}
767
768/******************************************************************************
769* Quit the program, optionally only if there are no more "instances" running.
770*/
771void KAlarmApp::quitIf(int exitCode, bool force)
772{
773 if (force)
774 {
775 // Quit regardless, except for message windows
776 MainWindow::closeAll();
777 displayTrayIcon(false);
778 if (MessageWin::instanceCount())
779 return;
780 }
781 else
782 {
783 // Quit only if there are no more "instances" running
784 mPendingQuit = false;
785 if (mActiveCount > 0 || MessageWin::instanceCount())
786 return;
787 int mwcount = MainWindow::count();
788 MainWindow* mw = mwcount ? MainWindow::firstWindow() : 0;
789 if (mwcount > 1 || (mwcount && (!mw->isHidden() || !mw->isTrayParent())))
790 return;
791 // There are no windows left except perhaps a main window which is a hidden tray icon parent
792 if (mTrayWindow)
793 {
794 // There is a system tray icon.
795 // Don't exit unless the system tray doesn't seem to exist.
796 if (checkSystemTray())
797 return;
798 }
799 if (!mDcopQueue.isEmpty() || !mCommandProcesses.isEmpty())
800 {
801 // Don't quit yet if there are outstanding actions on the DCOP queue
802 mPendingQuit = true;
803 mPendingQuitCode = exitCode;
804 return;
805 }
806 }
807
808 // This was the last/only running "instance" of the program, so exit completely.
809 kdDebug(5950) << "KAlarmApp::quitIf(" << exitCode << "): quitting" << endl;
810 BirthdayDlg::close();
811 exit(exitCode);
812}
813
814/******************************************************************************
815* Called when the Quit menu item is selected.
816* Closes the system tray window and all main windows, but does not exit the
817* program if other windows are still open.
818*/
819void KAlarmApp::doQuit(TQWidget* parent)
820{
821 kdDebug(5950) << "KAlarmApp::doQuit()\n";
822 if (mDisableAlarmsIfStopped
823 && MessageBox::warningContinueCancel(parent, KMessageBox::Cancel,
824 i18n("Quitting will disable alarms\n(once any alarm message windows are closed)."),
825 TQString(), KStdGuiItem::quit(), Preferences::QUIT_WARN
826 ) != KMessageBox::Yes)
827 return;
828 quitIf(0, true);
829}
830
831/******************************************************************************
832* Called when the session manager is about to close down the application.
833*/
834void KAlarmApp::commitData(TQSessionManager& sm)
835{
836 mSessionClosingDown = true;
837 TDEUniqueApplication::commitData(sm);
838 mSessionClosingDown = false; // reset in case shutdown is cancelled
839}
840
841/******************************************************************************
842* Display an error message for a fatal error. Prevent further actions since
843* the program state is unsafe.
844*/
845void KAlarmApp::displayFatalError(const TQString& message)
846{
847 if (!mFatalError)
848 {
849 mFatalError = 1;
850 mFatalMessage = message;
851 if (theInstance)
852 TQTimer::singleShot(0, theInstance, TQ_SLOT(quitFatal()));
853 }
854}
855
856/******************************************************************************
857* Quit the program, once the fatal error message has been acknowledged.
858*/
859void KAlarmApp::quitFatal()
860{
861 switch (mFatalError)
862 {
863 case 0:
864 case 2:
865 return;
866 case 1:
867 mFatalError = 2;
868 KMessageBox::error(0, mFatalMessage);
869 mFatalError = 3;
870 // fall through to '3'
871 case 3:
872 if (theInstance)
873 theInstance->quitIf(1, true);
874 break;
875 }
876 TQTimer::singleShot(1000, this, TQ_SLOT(quitFatal()));
877}
878
879/******************************************************************************
880* The main processing loop for KAlarm.
881* All KAlarm operations involving opening or updating calendar files are called
882* from this loop to ensure that only one operation is active at any one time.
883* This precaution is necessary because KAlarm's activities are mostly
884* asynchronous, being in response to DCOP calls from the alarm daemon (or other
885* programs) or timer events, any of which can be received in the middle of
886* performing another operation. If a calendar file is opened or updated while
887* another calendar operation is in progress, the program has been observed to
888* hang, or the first calendar call has failed with data loss - clearly
889* unacceptable!!
890*/
891void KAlarmApp::processQueue()
892{
893 if (mInitialised && !mProcessingQueue)
894 {
895 kdDebug(5950) << "KAlarmApp::processQueue()\n";
896 mProcessingQueue = true;
897
898 // Reset the alarm daemon if it's been queued
899 KAlarm::resetDaemonIfQueued();
900
901 // Process DCOP calls
902 while (!mDcopQueue.isEmpty())
903 {
904 DcopTQEntry& entry = mDcopQueue.first();
905 if (entry.eventId.isEmpty())
906 {
907 // It's a new alarm
908 switch (entry.function)
909 {
910 case EVENT_TRIGGER:
911 execAlarm(entry.event, entry.event.firstAlarm(), false);
912 break;
913 case EVENT_HANDLE:
914 KAlarm::addEvent(entry.event, 0);
915 break;
916 case EVENT_CANCEL:
917 break;
918 }
919 }
920 else
921 handleEvent(entry.eventId, entry.function);
922 mDcopQueue.pop_front();
923 }
924
925 // Purge the expired alarms calendar if it's time to do so
926 AlarmCalendar::expiredCalendar()->purgeIfQueued();
927
928 // Now that the queue has been processed, quit if a quit was queued
929 if (mPendingQuit)
930 quitIf(mPendingQuitCode);
931
932 mProcessingQueue = false;
933 }
934}
935
936/******************************************************************************
937* Redisplay alarms which were being shown when the program last exited.
938* Normally, these alarms will have been displayed by session restoration, but
939* if the program crashed or was killed, we can redisplay them here so that
940* they won't be lost.
941*/
942void KAlarmApp::redisplayAlarms()
943{
944 AlarmCalendar* cal = AlarmCalendar::displayCalendar();
945 if (cal->isOpen())
946 {
947 KCal::Event::List events = cal->events();
948 for (KCal::Event::List::ConstIterator it = events.begin(); it != events.end(); ++it)
949 {
950 KCal::Event* kcalEvent = *it;
951 KAEvent event(*kcalEvent);
952 event.setUid(KAEvent::ACTIVE);
953 if (!MessageWin::findEvent(event.id()))
954 {
955 // This event should be displayed, but currently isn't being
956 kdDebug(5950) << "KAlarmApp::redisplayAlarms(): " << event.id() << endl;
957 KAAlarm alarm = event.convertDisplayingAlarm();
958 (new MessageWin(event, alarm, false, !alarm.repeatAtLogin()))->show();
959 }
960 }
961 }
962}
963
964/******************************************************************************
965* Called when the system tray main window is closed.
966*/
967void KAlarmApp::removeWindow(TrayWindow*)
968{
969 mTrayWindow = 0;
970 quitIf();
971}
972
973/******************************************************************************
974* Display or close the system tray icon.
975*/
976bool KAlarmApp::displayTrayIcon(bool show, MainWindow* parent)
977{
978 static bool creating = false;
979 if (show)
980 {
981 if (!mTrayWindow && !creating)
982 {
983 if (!mHaveSystemTray)
984 return false;
985 if (!MainWindow::count() && wantRunInSystemTray())
986 {
987 creating = true; // prevent main window constructor from creating an additional tray icon
988 parent = MainWindow::create();
989 creating = false;
990 }
991 mTrayWindow = new TrayWindow(parent ? parent : MainWindow::firstWindow());
992 connect(mTrayWindow, TQ_SIGNAL(deleted()), TQ_SIGNAL(trayIconToggled()));
993 mTrayWindow->show();
994 emit trayIconToggled();
995
996 // Set up a timer so that we can check after all events in the window system's
997 // event queue have been processed, whether the system tray actually exists
998 mCheckingSystemTray = true;
999 mSavedNoSystemTray = mNoSystemTray;
1000 mNoSystemTray = false;
1001 TQTimer::singleShot(0, this, TQ_SLOT(slotSystemTrayTimer()));
1002 }
1003 }
1004 else if (mTrayWindow)
1005 {
1006 delete mTrayWindow;
1007 mTrayWindow = 0;
1008 }
1009 return true;
1010}
1011
1012/******************************************************************************
1013* Called by a timer to check whether the system tray icon has been housed in
1014* the system tray. Because there is a delay between the system tray icon show
1015* event and the icon being reparented by the system tray, we have to use a
1016* timer to check whether the system tray has actually grabbed it, or whether
1017* the system tray probably doesn't exist.
1018*/
1019void KAlarmApp::slotSystemTrayTimer()
1020{
1021 mCheckingSystemTray = false;
1022 if (!checkSystemTray())
1023 quitIf(0); // exit the application if there are no open windows
1024}
1025
1026/******************************************************************************
1027* Check whether the system tray icon has been housed in the system tray.
1028* If the system tray doesn't seem to exist, tell the alarm daemon to notify us
1029* of alarms regardless of whether we're running.
1030*/
1031bool KAlarmApp::checkSystemTray()
1032{
1033 if (mCheckingSystemTray || !mTrayWindow)
1034 return true;
1035 if (mTrayWindow->inSystemTray() != !mSavedNoSystemTray)
1036 {
1037 kdDebug(5950) << "KAlarmApp::checkSystemTray(): changed -> " << mSavedNoSystemTray << endl;
1038 mNoSystemTray = mSavedNoSystemTray = !mSavedNoSystemTray;
1039
1040 // Store the new setting in the config file, so that if KAlarm exits and is then
1041 // next activated by the daemon to display a message, it will register with the
1042 // daemon with the correct NOTIFY type. If that happened when there was no system
1043 // tray and alarms are disabled when KAlarm is not running, registering with
1044 // NO_START_NOTIFY could result in alarms never being seen.
1045 TDEConfig* config = tdeApp->config();
1046 config->setGroup(TQString::fromLatin1("General"));
1047 config->writeEntry(TQString::fromLatin1("NoSystemTray"), mNoSystemTray);
1048 config->sync();
1049
1050 // Update other settings and reregister with the alarm daemon
1051 slotPreferencesChanged();
1052 }
1053 else
1054 {
1055 kdDebug(5950) << "KAlarmApp::checkSystemTray(): no change = " << !mSavedNoSystemTray << endl;
1056 mNoSystemTray = mSavedNoSystemTray;
1057 }
1058 return !mNoSystemTray;
1059}
1060
1061/******************************************************************************
1062* Return the main window associated with the system tray icon.
1063*/
1064MainWindow* KAlarmApp::trayMainWindow() const
1065{
1066 return mTrayWindow ? mTrayWindow->assocMainWindow() : 0;
1067}
1068
1069/******************************************************************************
1070* Called when KAlarm preferences have changed.
1071*/
1072void KAlarmApp::slotPreferencesChanged()
1073{
1074 bool newRunInSysTray = wantRunInSystemTray();
1075 if (newRunInSysTray != mOldRunInSystemTray)
1076 {
1077 // The system tray run mode has changed
1078 ++mActiveCount; // prevent the application from quitting
1079 MainWindow* win = mTrayWindow ? mTrayWindow->assocMainWindow() : 0;
1080 delete mTrayWindow; // remove the system tray icon if it is currently shown
1081 mTrayWindow = 0;
1082 mOldRunInSystemTray = newRunInSysTray;
1083 if (!newRunInSysTray)
1084 {
1085 if (win && win->isHidden())
1086 delete win;
1087 }
1088 displayTrayIcon(true);
1089 --mActiveCount;
1090 }
1091
1092 bool newDisableIfStopped = wantRunInSystemTray() && !mNoSystemTray && Preferences::disableAlarmsIfStopped();
1093 if (newDisableIfStopped != mDisableAlarmsIfStopped)
1094 {
1095 mDisableAlarmsIfStopped = newDisableIfStopped; // N.B. this setting is used by Daemon::reregister()
1096 Preferences::setQuitWarn(true); // since mode has changed, re-allow warning messages on Quit
1097 Daemon::reregister(); // re-register with the alarm daemon
1098 }
1099
1100 // Change alarm times for date-only alarms if the start of day time has changed
1101 if (Preferences::startOfDay() != mStartOfDay)
1102 changeStartOfDay();
1103
1104 // In case the date for February 29th recurrences has changed
1105 KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type());
1106
1107 if (Preferences::expiredColour() != mPrefsExpiredColour)
1108 {
1109 // The expired alarms text colour has changed
1110 mRefreshExpiredAlarms = true;
1111 mPrefsExpiredColour = Preferences::expiredColour();
1112 }
1113
1114 if (Preferences::expiredKeepDays() != mPrefsExpiredKeepDays)
1115 {
1116 // How long expired alarms are being kept has changed.
1117 // N.B. This also adjusts for any change in start-of-day time.
1118 mPrefsExpiredKeepDays = Preferences::expiredKeepDays();
1119 AlarmCalendar::expiredCalendar()->setPurgeDays(mPrefsExpiredKeepDays);
1120 }
1121
1122 if (mRefreshExpiredAlarms)
1123 {
1124 mRefreshExpiredAlarms = false;
1125 MainWindow::updateExpired();
1126 }
1127}
1128
1129/******************************************************************************
1130* Change alarm times for date-only alarms after the start of day time has changed.
1131*/
1132void KAlarmApp::changeStartOfDay()
1133{
1134 Daemon::notifyTimeChanged(); // tell the alarm daemon the new time
1135 TQTime sod = Preferences::startOfDay();
1136 DateTime::setStartOfDay(sod);
1137 AlarmCalendar* cal = AlarmCalendar::activeCalendar();
1138 if (KAEvent::adjustStartOfDay(cal->events()))
1139 cal->save();
1140 Preferences::updateStartOfDayCheck(); // now that calendar is updated, set OK flag in config file
1141 mStartOfDay = sod;
1142}
1143
1144/******************************************************************************
1145* Called when the expired alarms calendar has been purged.
1146* Updates the alarm list in all main windows.
1147*/
1148void KAlarmApp::slotExpiredPurged()
1149{
1150 mRefreshExpiredAlarms = false;
1151 MainWindow::updateExpired();
1152}
1153
1154/******************************************************************************
1155* Return whether the program is configured to be running in the system tray.
1156*/
1157bool KAlarmApp::wantRunInSystemTray() const
1158{
1159 return Preferences::runInSystemTray() && mHaveSystemTray;
1160}
1161
1162/******************************************************************************
1163* Called to schedule a new alarm, either in response to a DCOP notification or
1164* to command line options.
1165* Reply = true unless there was a parameter error or an error opening calendar file.
1166*/
1167bool KAlarmApp::scheduleEvent(KAEvent::Action action, const TQString& text, const TQDateTime& dateTime,
1168 int lateCancel, int flags, const TQColor& bg, const TQColor& fg, const TQFont& font,
1169 const TQString& audioFile, float audioVolume, int reminderMinutes,
1170 const KARecurrence& recurrence, int repeatInterval, int repeatCount,
1171 uint mailFromID, const EmailAddressList& mailAddresses,
1172 const TQString& mailSubject, const TQStringList& mailAttachments)
1173{
1174 kdDebug(5950) << "KAlarmApp::scheduleEvent(): " << text << endl;
1175 if (!dateTime.isValid())
1176 return false;
1177 TQDateTime now = TQDateTime::currentDateTime();
1178 if (lateCancel && dateTime < now.addSecs(-maxLateness(lateCancel)))
1179 return true; // alarm time was already expired too long ago
1180 TQDateTime alarmTime = dateTime;
1181 // Round down to the nearest minute to avoid scheduling being messed up
1182 alarmTime.setTime(TQTime(alarmTime.time().hour(), alarmTime.time().minute(), 0));
1183
1184 KAEvent event(alarmTime, text, bg, fg, font, action, lateCancel, flags);
1185 if (reminderMinutes)
1186 {
1187 bool onceOnly = (reminderMinutes < 0);
1188 event.setReminder((onceOnly ? -reminderMinutes : reminderMinutes), onceOnly);
1189 }
1190 if (!audioFile.isEmpty())
1191 event.setAudioFile(audioFile, audioVolume, -1, 0);
1192 if (!mailAddresses.isEmpty())
1193 event.setEmail(mailFromID, mailAddresses, mailSubject, mailAttachments);
1194 event.setRecurrence(recurrence);
1195 event.setFirstRecurrence();
1196 event.setRepetition(repeatInterval, repeatCount - 1);
1197 if (alarmTime <= now)
1198 {
1199 // Alarm is due for display already.
1200 // First execute it once without adding it to the calendar file.
1201 if (!mInitialised)
1202 mDcopQueue.append(DcopTQEntry(event, EVENT_TRIGGER));
1203 else
1204 execAlarm(event, event.firstAlarm(), false);
1205 // If it's a recurring alarm, reschedule it for its next occurrence
1206 if (!event.recurs()
1207 || event.setNextOccurrence(now) == KAEvent::NO_OCCURRENCE)
1208 return true;
1209 // It has recurrences in the future
1210 }
1211
1212 // Queue the alarm for insertion into the calendar file
1213 mDcopQueue.append(DcopTQEntry(event));
1214 if (mInitialised)
1215 TQTimer::singleShot(0, this, TQ_SLOT(processQueue()));
1216 return true;
1217}
1218
1219/******************************************************************************
1220* Called in response to a DCOP notification by the alarm daemon that an event
1221* should be handled, i.e. displayed or cancelled.
1222* Optionally display the event. Delete the event from the calendar file and
1223* from every main window instance.
1224*/
1225bool KAlarmApp::handleEvent(const TQString& urlString, const TQString& eventID, EventFunc function)
1226{
1227 kdDebug(5950) << "KAlarmApp::handleEvent(DCOP): " << eventID << endl;
1228 AlarmCalendar* cal = AlarmCalendar::activeCalendar(); // this can be called before calendars have been initialised
1229 if (cal && KURL(urlString).url() != cal->urlString())
1230 {
1231 kdError(5950) << "KAlarmApp::handleEvent(DCOP): wrong calendar file " << urlString << endl;
1232 Daemon::eventHandled(eventID, false);
1233 return false;
1234 }
1235 mDcopQueue.append(DcopTQEntry(function, eventID));
1236 if (mInitialised)
1237 TQTimer::singleShot(0, this, TQ_SLOT(processQueue()));
1238 return true;
1239}
1240
1241/******************************************************************************
1242* Either:
1243* a) Display the event and then delete it if it has no outstanding repetitions.
1244* b) Delete the event.
1245* c) Reschedule the event for its next repetition. If none remain, delete it.
1246* If the event is deleted, it is removed from the calendar file and from every
1247* main window instance.
1248*/
1249bool KAlarmApp::handleEvent(const TQString& eventID, EventFunc function)
1250{
1251 kdDebug(5950) << "KAlarmApp::handleEvent(): " << eventID << ", " << (function==EVENT_TRIGGER?"TRIGGER":function==EVENT_CANCEL?"CANCEL":function==EVENT_HANDLE?"HANDLE":"?") << endl;
1252 KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(eventID);
1253 if (!kcalEvent)
1254 {
1255 kdError(5950) << "KAlarmApp::handleEvent(): event ID not found: " << eventID << endl;
1256 Daemon::eventHandled(eventID, false);
1257 return false;
1258 }
1259 KAEvent event(*kcalEvent);
1260 switch (function)
1261 {
1262 case EVENT_CANCEL:
1263 KAlarm::deleteEvent(event, true);
1264 break;
1265
1266 case EVENT_TRIGGER: // handle it if it's due, else execute it regardless
1267 case EVENT_HANDLE: // handle it if it's due
1268 {
1269 TQDateTime now = TQDateTime::currentDateTime();
1270 bool updateCalAndDisplay = false;
1271 bool alarmToExecuteValid = false;
1272 KAAlarm alarmToExecute;
1273 // Check all the alarms in turn.
1274 // Note that the main alarm is fetched before any other alarms.
1275 for (KAAlarm alarm = event.firstAlarm(); alarm.valid(); alarm = event.nextAlarm(alarm))
1276 {
1277 // Check if the alarm is due yet.
1278 int secs = alarm.dateTime(true).dateTime().secsTo(now);
1279 if (secs < 0)
1280 {
1281 // This alarm is definitely not due yet
1282 kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": not due\n";
1283 continue;
1284 }
1285 if (alarm.repeatAtLogin())
1286 {
1287 // Alarm is to be displayed at every login.
1288 // Check if the alarm has only just been set up.
1289 // (The alarm daemon will immediately notify that it is due
1290 // since it is set up with a time in the past.)
1291 kdDebug(5950) << "KAlarmApp::handleEvent(): REPEAT_AT_LOGIN\n";
1292 if (secs < maxLateness(1))
1293 continue;
1294
1295 // Check if the main alarm is already being displayed.
1296 // (We don't want to display both at the same time.)
1297 if (alarmToExecute.valid())
1298 continue;
1299
1300 // Set the time to display if it's a display alarm
1301 alarm.setTime(now);
1302 }
1303 if (alarm.lateCancel())
1304 {
1305 // Alarm is due, and it is to be cancelled if too late.
1306 kdDebug(5950) << "KAlarmApp::handleEvent(): LATE_CANCEL\n";
1307 bool late = false;
1308 bool cancel = false;
1309 if (alarm.dateTime().isDateOnly())
1310 {
1311 // The alarm has no time, so cancel it if its date is too far past
1312 int maxlate = alarm.lateCancel() / 1440; // maximum lateness in days
1313 TQDateTime limit(alarm.date().addDays(maxlate + 1), Preferences::startOfDay());
1314 if (now >= limit)
1315 {
1316 // It's too late to display the scheduled occurrence.
1317 // Find the last previous occurrence of the alarm.
1318 DateTime next;
1319 KAEvent::OccurType type = event.previousOccurrence(now, next, true);
1320 switch (type & ~KAEvent::OCCURRENCE_REPEAT)
1321 {
1322 case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
1323 case KAEvent::RECURRENCE_DATE:
1324 case KAEvent::RECURRENCE_DATE_TIME:
1325 case KAEvent::LAST_RECURRENCE:
1326 limit.setDate(next.date().addDays(maxlate + 1));
1327 limit.setTime(Preferences::startOfDay());
1328 if (now >= limit)
1329 {
1330 if (type == KAEvent::LAST_RECURRENCE
1331 || (type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs()))
1332 cancel = true; // last occurrence (and there are no repetitions)
1333 else
1334 late = true;
1335 }
1336 break;
1337 case KAEvent::NO_OCCURRENCE:
1338 default:
1339 late = true;
1340 break;
1341 }
1342 }
1343 }
1344 else
1345 {
1346 // The alarm is timed. Allow it to be the permitted amount late before cancelling it.
1347 int maxlate = maxLateness(alarm.lateCancel());
1348 if (secs > maxlate)
1349 {
1350 // It's over the maximum interval late.
1351 // Find the most recent occurrence of the alarm.
1352 DateTime next;
1353 KAEvent::OccurType type = event.previousOccurrence(now, next, true);
1354 switch (type & ~KAEvent::OCCURRENCE_REPEAT)
1355 {
1356 case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
1357 case KAEvent::RECURRENCE_DATE:
1358 case KAEvent::RECURRENCE_DATE_TIME:
1359 case KAEvent::LAST_RECURRENCE:
1360 if (next.dateTime().secsTo(now) > maxlate)
1361 {
1362 if (type == KAEvent::LAST_RECURRENCE
1363 || (type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs()))
1364 cancel = true; // last occurrence (and there are no repetitions)
1365 else
1366 late = true;
1367 }
1368 break;
1369 case KAEvent::NO_OCCURRENCE:
1370 default:
1371 late = true;
1372 break;
1373 }
1374 }
1375 }
1376
1377 if (cancel)
1378 {
1379 // All recurrences are finished, so cancel the event
1380 event.setArchive();
1381 cancelAlarm(event, alarm.type(), false);
1382 updateCalAndDisplay = true;
1383 continue;
1384 }
1385 if (late)
1386 {
1387 // The latest repetition was too long ago, so schedule the next one
1388 rescheduleAlarm(event, alarm, false);
1389 updateCalAndDisplay = true;
1390 continue;
1391 }
1392 }
1393 if (!alarmToExecuteValid)
1394 {
1395 kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": execute\n";
1396 alarmToExecute = alarm; // note the alarm to be executed
1397 alarmToExecuteValid = true; // only trigger one alarm for the event
1398 }
1399 else
1400 kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": skip\n";
1401 }
1402
1403 // If there is an alarm to execute, do this last after rescheduling/cancelling
1404 // any others. This ensures that the updated event is only saved once to the calendar.
1405 if (alarmToExecute.valid())
1406 execAlarm(event, alarmToExecute, true, !alarmToExecute.repeatAtLogin());
1407 else
1408 {
1409 if (function == EVENT_TRIGGER)
1410 {
1411 // The alarm is to be executed regardless of whether it's due.
1412 // Only trigger one alarm from the event - we don't want multiple
1413 // identical messages, for example.
1414 KAAlarm alarm = event.firstAlarm();
1415 if (alarm.valid())
1416 execAlarm(event, alarm, false);
1417 }
1418 if (updateCalAndDisplay)
1419 KAlarm::updateEvent(event, 0); // update the window lists and calendar file
1420 else if (function != EVENT_TRIGGER)
1421 {
1422 kdDebug(5950) << "KAlarmApp::handleEvent(): no action\n";
1423 Daemon::eventHandled(eventID, false);
1424 }
1425 }
1426 break;
1427 }
1428 }
1429 return true;
1430}
1431
1432/******************************************************************************
1433* Called when an alarm is currently being displayed, to store a copy of the
1434* alarm in the displaying calendar, and to reschedule it for its next repetition.
1435* If no repetitions remain, cancel it.
1436*/
1437void KAlarmApp::alarmShowing(KAEvent& event, KAAlarm::Type alarmType, const DateTime& alarmTime)
1438{
1439 kdDebug(5950) << "KAlarmApp::alarmShowing(" << event.id() << ", " << KAAlarm::debugType(alarmType) << ")\n";
1440 KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(event.id());
1441 if (!kcalEvent)
1442 kdError(5950) << "KAlarmApp::alarmShowing(): event ID not found: " << event.id() << endl;
1443 else
1444 {
1445 KAAlarm alarm = event.alarm(alarmType);
1446 if (!alarm.valid())
1447 kdError(5950) << "KAlarmApp::alarmShowing(): alarm type not found: " << event.id() << ":" << alarmType << endl;
1448 else
1449 {
1450 // Copy the alarm to the displaying calendar in case of a crash, etc.
1451 KAEvent dispEvent;
1452 dispEvent.setDisplaying(event, alarmType, alarmTime.dateTime());
1453 AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen();
1454 if (cal)
1455 {
1456 cal->deleteEvent(dispEvent.id()); // in case it already exists
1457 cal->addEvent(dispEvent);
1458 cal->save();
1459 }
1460
1461 rescheduleAlarm(event, alarm, true);
1462 return;
1463 }
1464 }
1465 Daemon::eventHandled(event.id(), false);
1466}
1467
1468/******************************************************************************
1469* Called when an alarm action has completed, to perform any post-alarm actions.
1470*/
1471void KAlarmApp::alarmCompleted(const KAEvent& event)
1472{
1473 if (!event.postAction().isEmpty() && ShellProcess::authorised())
1474 {
1475 TQString command = event.postAction();
1476 kdDebug(5950) << "KAlarmApp::alarmCompleted(" << event.id() << "): " << command << endl;
1477 doShellCommand(command, event, 0, ProcData::POST_ACTION);
1478 }
1479}
1480
1481/******************************************************************************
1482* Reschedule the alarm for its next recurrence. If none remain, delete it.
1483* If the alarm is deleted and it is the last alarm for its event, the event is
1484* removed from the calendar file and from every main window instance.
1485*/
1486void KAlarmApp::rescheduleAlarm(KAEvent& event, const KAAlarm& alarm, bool updateCalAndDisplay)
1487{
1488 kdDebug(5950) << "KAlarmApp::rescheduleAlarm()" << endl;
1489 bool update = false;
1490 if (alarm.reminder() || alarm.deferred())
1491 {
1492 // It's an advance warning alarm or an extra deferred alarm, so delete it
1493 event.removeExpiredAlarm(alarm.type());
1494 update = true;
1495 }
1496 else if (alarm.repeatAtLogin())
1497 {
1498 // Leave an alarm which repeats at every login until its main alarm is deleted
1499 if (updateCalAndDisplay && event.updated())
1500 update = true;
1501 }
1502 else
1503 {
1504 // Reschedule the alarm for its next recurrence.
1505 KAEvent::OccurType type = event.setNextOccurrence(TQDateTime::currentDateTime());
1506 switch (type)
1507 {
1508 case KAEvent::NO_OCCURRENCE:
1509 // All repetitions are finished, so cancel the event
1510 cancelAlarm(event, alarm.type(), updateCalAndDisplay);
1511 break;
1512 default:
1513 if (!(type & KAEvent::OCCURRENCE_REPEAT))
1514 break;
1515 // Next occurrence is a repeat, so fall through to recurrence handling
1516 case KAEvent::RECURRENCE_DATE:
1517 case KAEvent::RECURRENCE_DATE_TIME:
1518 case KAEvent::LAST_RECURRENCE:
1519 // The event is due by now and repetitions still remain, so rewrite the event
1520 if (updateCalAndDisplay)
1521 update = true;
1522 else
1523 {
1524 event.cancelCancelledDeferral();
1525 event.setUpdated(); // note that the calendar file needs to be updated
1526 }
1527 break;
1528 case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
1529 // The first occurrence is still due?!?, so don't do anything
1530 break;
1531 }
1532 if (event.deferred())
1533 {
1534 // Just in case there's also a deferred alarm, ensure it's removed
1535 event.removeExpiredAlarm(KAAlarm::DEFERRED_ALARM);
1536 update = true;
1537 }
1538 }
1539 if (update)
1540 {
1541 event.cancelCancelledDeferral();
1542 KAlarm::updateEvent(event, 0); // update the window lists and calendar file
1543 }
1544}
1545
1546/******************************************************************************
1547* Delete the alarm. If it is the last alarm for its event, the event is removed
1548* from the calendar file and from every main window instance.
1549*/
1550void KAlarmApp::cancelAlarm(KAEvent& event, KAAlarm::Type alarmType, bool updateCalAndDisplay)
1551{
1552 kdDebug(5950) << "KAlarmApp::cancelAlarm()" << endl;
1553 event.cancelCancelledDeferral();
1554 if (alarmType == KAAlarm::MAIN_ALARM && !event.displaying() && event.toBeArchived())
1555 {
1556 // The event is being deleted. Save it in the expired calendar file first.
1557 TQString id = event.id(); // save event ID since KAlarm::addExpiredEvent() changes it
1558 KAlarm::addExpiredEvent(event);
1559 event.setEventID(id); // restore event ID
1560 }
1561 event.removeExpiredAlarm(alarmType);
1562 if (!event.alarmCount())
1563 KAlarm::deleteEvent(event, false);
1564 else if (updateCalAndDisplay)
1565 KAlarm::updateEvent(event, 0); // update the window lists and calendar file
1566}
1567
1568/******************************************************************************
1569* Execute an alarm by displaying its message or file, or executing its command.
1570* Reply = ShellProcess instance if a command alarm
1571* != 0 if successful
1572* = 0 if the alarm is disabled, or if an error message was output.
1573*/
1574void* KAlarmApp::execAlarm(KAEvent& event, const KAAlarm& alarm, bool reschedule, bool allowDefer, bool noPreAction)
1575{
1576 if (!event.enabled())
1577 {
1578 // The event is disabled.
1579 if (reschedule)
1580 rescheduleAlarm(event, alarm, true);
1581 return 0;
1582 }
1583
1584 void* result = (void*)1;
1585 event.setArchive();
1586 switch (alarm.action())
1587 {
1588 case KAAlarm::MESSAGE:
1589 case KAAlarm::FILE:
1590 {
1591 // Display a message or file, provided that the same event isn't already being displayed
1592 MessageWin* win = MessageWin::findEvent(event.id());
1593 // Find if we're changing a reminder message to the real message
1594 bool reminder = (alarm.type() & KAAlarm::REMINDER_ALARM);
1595 bool replaceReminder = !reminder && win && (win->alarmType() & KAAlarm::REMINDER_ALARM);
1596 if (!reminder && !event.deferred()
1597 && (replaceReminder || !win) && !noPreAction
1598 && !event.preAction().isEmpty() && ShellProcess::authorised())
1599 {
1600 // It's not a reminder or a deferred alarm, and there is no message window
1601 // (other than a reminder window) currently displayed for this alarm,
1602 // and we need to execute a command before displaying the new window.
1603 // Check whether the command is already being executed for this alarm.
1604 for (TQValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it)
1605 {
1606 ProcData* pd = *it;
1607 if (pd->event->id() == event.id() && (pd->flags & ProcData::PRE_ACTION))
1608 {
1609 kdDebug(5950) << "KAlarmApp::execAlarm(): already executing pre-DISPLAY command" << endl;
1610 return pd->process; // already executing - don't duplicate the action
1611 }
1612 }
1613
1614 TQString command = event.preAction();
1615 kdDebug(5950) << "KAlarmApp::execAlarm(): pre-DISPLAY command: " << command << endl;
1616 int flags = (reschedule ? ProcData::RESCHEDULE : 0) | (allowDefer ? ProcData::ALLOW_DEFER : 0);
1617 if (doShellCommand(command, event, &alarm, (flags | ProcData::PRE_ACTION)))
1618 return result; // display the message after the command completes
1619 // Error executing command - display the message even though it failed
1620 }
1621 if (!event.enabled())
1622 delete win; // event is disabled - close its window
1623 else if (!win
1624 || (!win->hasDefer() && !alarm.repeatAtLogin())
1625 || replaceReminder)
1626 {
1627 // Either there isn't already a message for this event,
1628 // or there is a repeat-at-login message with no Defer
1629 // button, which needs to be replaced with a new message,
1630 // or the caption needs to be changed from "Reminder" to "Message".
1631 if (win)
1632 win->setRecreating(); // prevent post-alarm actions
1633 delete win;
1634 (new MessageWin(event, alarm, reschedule, allowDefer))->show();
1635 }
1636 else
1637 {
1638 // Raise the existing message window and replay any sound
1639 win->repeat(alarm); // N.B. this reschedules the alarm
1640 }
1641 break;
1642 }
1643 case KAAlarm::COMMAND:
1644 {
1645 int flags = event.commandXterm() ? ProcData::EXEC_IN_XTERM : 0;
1646 TQString command = event.cleanText();
1647 if (event.commandScript())
1648 {
1649 // Store the command script in a temporary file for execution
1650 kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: (script)" << endl;
1651 TQString tmpfile = createTempScriptFile(command, false, event, alarm);
1652 if (tmpfile.isEmpty())
1653 result = 0;
1654 else
1655 result = doShellCommand(tmpfile, event, &alarm, (flags | ProcData::TEMP_FILE));
1656 }
1657 else
1658 {
1659 kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: " << command << endl;
1660 result = doShellCommand(command, event, &alarm, flags);
1661 }
1662 if (reschedule)
1663 rescheduleAlarm(event, alarm, true);
1664 break;
1665 }
1666 case KAAlarm::EMAIL:
1667 {
1668 kdDebug(5950) << "KAlarmApp::execAlarm(): EMAIL to: " << event.emailAddresses(", ") << endl;
1669 TQStringList errmsgs;
1670 if (!KAMail::send(event, errmsgs, (reschedule || allowDefer)))
1671 result = 0;
1672 if (!errmsgs.isEmpty())
1673 {
1674 // Some error occurred, although the email may have been sent successfully
1675 if (result)
1676 kdDebug(5950) << "KAlarmApp::execAlarm(): copy error: " << errmsgs[1] << endl;
1677 else
1678 kdDebug(5950) << "KAlarmApp::execAlarm(): failed: " << errmsgs[1] << endl;
1679 (new MessageWin(event, alarm.dateTime(), errmsgs))->show();
1680 }
1681 if (reschedule)
1682 rescheduleAlarm(event, alarm, true);
1683 break;
1684 }
1685 default:
1686 return 0;
1687 }
1688 return result;
1689}
1690
1691/******************************************************************************
1692* Execute a shell command line specified by an alarm.
1693* If the PRE_ACTION bit of 'flags' is set, the alarm will be executed via
1694* execAlarm() once the command completes, the execAlarm() parameters being
1695* derived from the remaining bits in 'flags'.
1696*/
1697ShellProcess* KAlarmApp::doShellCommand(const TQString& command, const KAEvent& event, const KAAlarm* alarm, int flags)
1698{
1699 kdDebug(5950) << "KAlarmApp::doShellCommand(" << command << ", " << event.id() << ")" << endl;
1700 TDEProcess::Communication comms = TDEProcess::NoCommunication;
1701 TQString cmd;
1702 TQString tmpXtermFile;
1703 if (flags & ProcData::EXEC_IN_XTERM)
1704 {
1705 // Execute the command in a terminal window.
1706 cmd = Preferences::cmdXTermCommand();
1707 cmd.replace("%t", aboutData()->programName()); // set the terminal window title
1708 if (cmd.find("%C") >= 0)
1709 {
1710 // Execute the command from a temporary script file
1711 if (flags & ProcData::TEMP_FILE)
1712 cmd.replace("%C", command); // the command is already calling a temporary file
1713 else
1714 {
1715 tmpXtermFile = createTempScriptFile(command, true, event, *alarm);
1716 if (tmpXtermFile.isEmpty())
1717 return 0;
1718 cmd.replace("%C", tmpXtermFile); // %C indicates where to insert the command
1719 }
1720 }
1721 else if (cmd.find("%W") >= 0)
1722 {
1723 // Execute the command from a temporary script file,
1724 // with a sleep after the command is executed
1725 tmpXtermFile = createTempScriptFile(command + TQString::fromLatin1("\nsleep 86400\n"), true, event, *alarm);
1726 if (tmpXtermFile.isEmpty())
1727 return 0;
1728 cmd.replace("%W", tmpXtermFile); // %w indicates where to insert the command
1729 }
1730 else if (cmd.find("%w") >= 0)
1731 {
1732 // Append a sleep to the command.
1733 // Quote the command in case it contains characters such as [>|;].
1734 TQString exec = KShellProcess::quote(command + TQString::fromLatin1("; sleep 86400"));
1735 cmd.replace("%w", exec); // %w indicates where to insert the command string
1736 }
1737 else
1738 {
1739 // Set the command to execute.
1740 // Put it in quotes in case it contains characters such as [>|;].
1741 TQString exec = KShellProcess::quote(command);
1742 if (cmd.find("%c") >= 0)
1743 cmd.replace("%c", exec); // %c indicates where to insert the command string
1744 else
1745 cmd.append(exec); // otherwise, simply append the command string
1746 }
1747 }
1748 else
1749 {
1750 cmd = command;
1751 comms = TDEProcess::AllOutput;
1752 }
1753 ShellProcess* proc = new ShellProcess(cmd);
1754 connect(proc, TQ_SIGNAL(shellExited(ShellProcess*)), TQ_SLOT(slotCommandExited(ShellProcess*)));
1755 TQGuardedPtr<ShellProcess> logproc = 0;
1756 if (comms == TDEProcess::AllOutput && !event.logFile().isEmpty())
1757 {
1758 // Output is to be appended to a log file.
1759 // Set up a logging process to write the command's output to.
1760 connect(proc, TQ_SIGNAL(receivedStdout(TDEProcess*,char*,int)), TQ_SLOT(slotCommandOutput(TDEProcess*,char*,int)));
1761 connect(proc, TQ_SIGNAL(receivedStderr(TDEProcess*,char*,int)), TQ_SLOT(slotCommandOutput(TDEProcess*,char*,int)));
1762 logproc = new ShellProcess(TQString::fromLatin1("cat >>%1").arg(event.logFile()));
1763 connect(logproc, TQ_SIGNAL(shellExited(ShellProcess*)), TQ_SLOT(slotLogProcExited(ShellProcess*)));
1764 logproc->start(TDEProcess::Stdin);
1765 TQCString heading;
1766 if (alarm && alarm->dateTime().isValid())
1767 {
1768 TQString dateTime = alarm->dateTime().isDateOnly()
1769 ? TDEGlobal::locale()->formatDate(alarm->dateTime().date(), true)
1770 : TDEGlobal::locale()->formatDateTime(alarm->dateTime().dateTime());
1771 heading.sprintf("\n******* KAlarm %s *******\n", dateTime.latin1());
1772 }
1773 else
1774 heading = "\n******* KAlarm *******\n";
1775 logproc->writeStdin(heading, heading.length()+1);
1776 }
1777 ProcData* pd = new ProcData(proc, logproc, new KAEvent(event), (alarm ? new KAAlarm(*alarm) : 0), flags);
1778 if (flags & ProcData::TEMP_FILE)
1779 pd->tempFiles += command;
1780 if (!tmpXtermFile.isEmpty())
1781 pd->tempFiles += tmpXtermFile;
1782 mCommandProcesses.append(pd);
1783 if (proc->start(comms))
1784 return proc;
1785
1786 // Error executing command - report it
1787 kdError(5950) << "KAlarmApp::doShellCommand(): command failed to start\n";
1788 commandErrorMsg(proc, event, alarm, flags);
1789 mCommandProcesses.remove(pd);
1790 delete pd;
1791 return 0;
1792}
1793
1794/******************************************************************************
1795* Create a temporary script file containing the specified command string.
1796* Reply = path of temporary file, or null string if error.
1797*/
1798TQString KAlarmApp::createTempScriptFile(const TQString& command, bool insertShell, const KAEvent& event, const KAAlarm& alarm)
1799{
1800 KTempFile tmpFile(TQString(), TQString(), 0700);
1801 tmpFile.setAutoDelete(false); // don't delete file when it is destructed
1802 TQTextStream* stream = tmpFile.textStream();
1803 if (!stream)
1804 kdError(5950) << "KAlarmApp::createTempScript(): Unable to create a temporary script file" << endl;
1805 else
1806 {
1807 if (insertShell)
1808 *stream << "#!" << ShellProcess::shellPath() << "\n";
1809 *stream << command;
1810 tmpFile.close();
1811 if (tmpFile.status())
1812 kdError(5950) << "KAlarmApp::createTempScript(): Error " << tmpFile.status() << " writing to temporary script file" << endl;
1813 else
1814 return tmpFile.name();
1815 }
1816
1817 TQStringList errmsgs(i18n("Error creating temporary script file"));
1818 (new MessageWin(event, alarm.dateTime(), errmsgs))->show();
1819 return TQString();
1820}
1821
1822/******************************************************************************
1823* Called when an executing command alarm sends output to stdout or stderr.
1824*/
1825void KAlarmApp::slotCommandOutput(TDEProcess* proc, char* buffer, int bufflen)
1826{
1827//kdDebug(5950) << "KAlarmApp::slotCommandOutput(): '" << TQCString(buffer, bufflen+1) << "'\n";
1828 // Find this command in the command list
1829 for (TQValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it)
1830 {
1831 ProcData* pd = *it;
1832 if (pd->process == proc && pd->logProcess)
1833 {
1834 pd->logProcess->writeStdin(buffer, bufflen);
1835 break;
1836 }
1837 }
1838}
1839
1840/******************************************************************************
1841* Called when a logging process completes.
1842*/
1843void KAlarmApp::slotLogProcExited(ShellProcess* proc)
1844{
1845 // Because it's held as a guarded pointer in the ProcData structure,
1846 // we don't need to set any pointers to zero.
1847 delete proc;
1848}
1849
1850/******************************************************************************
1851* Called when a command alarm's execution completes.
1852*/
1853void KAlarmApp::slotCommandExited(ShellProcess* proc)
1854{
1855 kdDebug(5950) << "KAlarmApp::slotCommandExited()\n";
1856 // Find this command in the command list
1857 for (TQValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it)
1858 {
1859 ProcData* pd = *it;
1860 if (pd->process == proc)
1861 {
1862 // Found the command
1863 if (pd->logProcess)
1864 pd->logProcess->stdinExit(); // terminate the logging process
1865
1866 // Check its exit status
1867 if (!proc->normalExit())
1868 {
1869 TQString errmsg = proc->errorMessage();
1870 kdWarning(5950) << "KAlarmApp::slotCommandExited(" << pd->event->cleanText() << "): " << errmsg << endl;
1871 if (pd->messageBoxParent)
1872 {
1873 // Close the existing informational KMessageBox for this process
1874 TQObjectList* dialogs = pd->messageBoxParent->queryList("KDialogBase", 0, false, true);
1875 KDialogBase* dialog = (KDialogBase*)dialogs->getFirst();
1876 delete dialog;
1877 delete dialogs;
1878 if (!pd->tempFile())
1879 {
1880 errmsg += '\n';
1881 errmsg += proc->command();
1882 }
1883 KMessageBox::error(pd->messageBoxParent, errmsg);
1884 }
1885 else
1886 commandErrorMsg(proc, *pd->event, pd->alarm, pd->flags);
1887 }
1888 if (pd->preAction())
1889 execAlarm(*pd->event, *pd->alarm, pd->reschedule(), pd->allowDefer(), true);
1890 mCommandProcesses.remove(it);
1891 delete pd;
1892 break;
1893 }
1894 }
1895
1896 // If there are now no executing shell commands, quit if a quit was queued
1897 if (mPendingQuit && mCommandProcesses.isEmpty())
1898 quitIf(mPendingQuitCode);
1899}
1900
1901/******************************************************************************
1902* Output an error message for a shell command.
1903*/
1904void KAlarmApp::commandErrorMsg(const ShellProcess* proc, const KAEvent& event, const KAAlarm* alarm, int flags)
1905{
1906 TQStringList errmsgs;
1907 if (flags & ProcData::PRE_ACTION)
1908 errmsgs += i18n("Pre-alarm action:");
1909 else if (flags & ProcData::POST_ACTION)
1910 errmsgs += i18n("Post-alarm action:");
1911 errmsgs += proc->errorMessage();
1912 if (!(flags & ProcData::TEMP_FILE))
1913 errmsgs += proc->command();
1914 (new MessageWin(event, (alarm ? alarm->dateTime() : DateTime()), errmsgs))->show();
1915}
1916
1917/******************************************************************************
1918* Notes that an informational KMessageBox is displayed for this process.
1919*/
1920void KAlarmApp::commandMessage(ShellProcess* proc, TQWidget* parent)
1921{
1922 // Find this command in the command list
1923 for (TQValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it)
1924 {
1925 ProcData* pd = *it;
1926 if (pd->process == proc)
1927 {
1928 pd->messageBoxParent = parent;
1929 break;
1930 }
1931 }
1932}
1933
1934/******************************************************************************
1935* Set up remaining DCOP handlers and start processing DCOP calls.
1936*/
1937void KAlarmApp::setUpDcop()
1938{
1939 if (!mInitialised)
1940 {
1941 mInitialised = true; // we're now ready to handle DCOP calls
1942 Daemon::createDcopHandler();
1943 TQTimer::singleShot(0, this, TQ_SLOT(processQueue())); // process anything already queued
1944 }
1945}
1946
1947/******************************************************************************
1948* If this is the first time through, open the calendar file, optionally start
1949* the alarm daemon and register with it, and set up the DCOP handler.
1950*/
1951bool KAlarmApp::initCheck(bool calendarOnly)
1952{
1953 bool startdaemon;
1954 AlarmCalendar* cal = AlarmCalendar::activeCalendar();
1955 if (!cal->isOpen())
1956 {
1957 kdDebug(5950) << "KAlarmApp::initCheck(): opening active calendar\n";
1958
1959 // First time through. Open the calendar file.
1960 if (!cal->open())
1961 return false;
1962
1963 if (!mStartOfDay.isValid())
1964 changeStartOfDay(); // start of day time has changed, so adjust date-only alarms
1965
1966 /* Need to open the display calendar now, since otherwise if the daemon
1967 * immediately notifies display alarms, they will often be processed while
1968 * redisplayAlarms() is executing open() (but before open() completes),
1969 * which causes problems!!
1970 */
1971 AlarmCalendar::displayCalendar()->open();
1972
1973 /* Need to open the expired alarm calendar now, since otherwise if the daemon
1974 * immediately notifies multiple alarms, the second alarm is likely to be
1975 * processed while the calendar is executing open() (but before open() completes),
1976 * which causes a hang!!
1977 */
1978 AlarmCalendar::expiredCalendar()->open();
1979 AlarmCalendar::expiredCalendar()->setPurgeDays(theInstance->mPrefsExpiredKeepDays);
1980
1981 startdaemon = true;
1982 }
1983 else
1984 startdaemon = !Daemon::isRegistered();
1985
1986 if (!calendarOnly)
1987 {
1988 setUpDcop(); // start processing DCOP calls
1989 if (startdaemon)
1990 Daemon::start(); // make sure the alarm daemon is running
1991 }
1992 return true;
1993}
1994
1995/******************************************************************************
1996* Convert the --time parameter string into a date/time or date value.
1997* The parameter is in the form [[[yyyy-]mm-]dd-]hh:mm or yyyy-mm-dd.
1998* Reply = true if successful.
1999*/
2000static bool convWakeTime(const TQCString& timeParam, TQDateTime& dateTime, bool& noTime)
2001{
2002 if (timeParam.length() > 19)
2003 return false;
2004 char timeStr[20];
2005 strcpy(timeStr, timeParam);
2006 int dt[5] = { -1, -1, -1, -1, -1 };
2007 char* s;
2008 char* end;
2009 // Get the minute value
2010 if ((s = strchr(timeStr, ':')) == 0)
2011 noTime = true;
2012 else
2013 {
2014 noTime = false;
2015 *s++ = 0;
2016 dt[4] = strtoul(s, &end, 10);
2017 if (end == s || *end || dt[4] >= 60)
2018 return false;
2019 // Get the hour value
2020 if ((s = strrchr(timeStr, '-')) == 0)
2021 s = timeStr;
2022 else
2023 *s++ = 0;
2024 dt[3] = strtoul(s, &end, 10);
2025 if (end == s || *end || dt[3] >= 24)
2026 return false;
2027 }
2028 bool dateSet = false;
2029 if (s != timeStr)
2030 {
2031 dateSet = true;
2032 // Get the day value
2033 if ((s = strrchr(timeStr, '-')) == 0)
2034 s = timeStr;
2035 else
2036 *s++ = 0;
2037 dt[2] = strtoul(s, &end, 10);
2038 if (end == s || *end || dt[2] == 0 || dt[2] > 31)
2039 return false;
2040 if (s != timeStr)
2041 {
2042 // Get the month value
2043 if ((s = strrchr(timeStr, '-')) == 0)
2044 s = timeStr;
2045 else
2046 *s++ = 0;
2047 dt[1] = strtoul(s, &end, 10);
2048 if (end == s || *end || dt[1] == 0 || dt[1] > 12)
2049 return false;
2050 if (s != timeStr)
2051 {
2052 // Get the year value
2053 dt[0] = strtoul(timeStr, &end, 10);
2054 if (end == timeStr || *end)
2055 return false;
2056 }
2057 }
2058 }
2059
2060 TQDate date(dt[0], dt[1], dt[2]);
2061 TQTime time(0, 0, 0);
2062 if (noTime)
2063 {
2064 // No time was specified, so the full date must have been specified
2065 if (dt[0] < 0)
2066 return false;
2067 }
2068 else
2069 {
2070 // Compile the values into a date/time structure
2071 TQDateTime now = TQDateTime::currentDateTime();
2072 if (dt[0] < 0)
2073 date.setYMD(now.date().year(),
2074 (dt[1] < 0 ? now.date().month() : dt[1]),
2075 (dt[2] < 0 ? now.date().day() : dt[2]));
2076 time.setHMS(dt[3], dt[4], 0);
2077 if (!dateSet && time < now.time())
2078 date = date.addDays(1);
2079 }
2080 if (!date.isValid())
2081 return false;
2082 dateTime.setDate(date);
2083 dateTime.setTime(time);
2084 return true;
2085}
2086
2087/******************************************************************************
2088* Convert a time interval command line parameter.
2089* 'timeInterval' receives the count for the recurType. If 'allowMonthYear' is
2090* false, 'timeInterval' is converted to minutes.
2091* Reply = true if successful.
2092*/
2093static bool convInterval(const TQCString& timeParam, KARecurrence::Type& recurType, int& timeInterval, bool allowMonthYear)
2094{
2095 TQCString timeString = timeParam;
2096 // Get the recurrence interval
2097 bool ok = true;
2098 uint interval = 0;
2099 bool negative = (timeString[0] == '-');
2100 if (negative)
2101 timeString = timeString.right(1);
2102 uint length = timeString.length();
2103 switch (timeString[length - 1])
2104 {
2105 case 'Y':
2106 if (!allowMonthYear)
2107 ok = false;
2108 recurType = KARecurrence::ANNUAL_DATE;
2109 timeString = timeString.left(length - 1);
2110 break;
2111 case 'W':
2112 recurType = KARecurrence::WEEKLY;
2113 timeString = timeString.left(length - 1);
2114 break;
2115 case 'D':
2116 recurType = KARecurrence::DAILY;
2117 timeString = timeString.left(length - 1);
2118 break;
2119 case 'M':
2120 {
2121 int i = timeString.find('H');
2122 if (i < 0)
2123 {
2124 if (!allowMonthYear)
2125 ok = false;
2126 recurType = KARecurrence::MONTHLY_DAY;
2127 timeString = timeString.left(length - 1);
2128 }
2129 else
2130 {
2131 recurType = KARecurrence::MINUTELY;
2132 interval = timeString.left(i).toUInt(&ok) * 60;
2133 timeString = timeString.mid(i + 1, length - i - 2);
2134 }
2135 break;
2136 }
2137 default: // should be a digit
2138 recurType = KARecurrence::MINUTELY;
2139 break;
2140 }
2141 if (ok)
2142 interval += timeString.toUInt(&ok);
2143 if (!allowMonthYear)
2144 {
2145 // Convert time interval to minutes
2146 switch (recurType)
2147 {
2148 case KARecurrence::WEEKLY:
2149 interval *= 7;
2150 // fall through to DAILY
2151 case KARecurrence::DAILY:
2152 interval *= 24*60;
2153 break;
2154 default:
2155 break;
2156 }
2157 }
2158 timeInterval = static_cast<int>(interval);
2159 if (negative)
2160 timeInterval = -timeInterval;
2161 return ok;
2162}
2163
2164KAlarmApp::ProcData::ProcData(ShellProcess* p, ShellProcess* logp, KAEvent* e, KAAlarm* a, int f)
2165 : process(p),
2166 logProcess(logp),
2167 event(e),
2168 alarm(a),
2169 messageBoxParent(0),
2170 flags(f)
2171{ }
2172
2173KAlarmApp::ProcData::~ProcData()
2174{
2175 while (!tempFiles.isEmpty())
2176 {
2177 // Delete the temporary file called by the XTerm command
2178 TQFile f(tempFiles.first());
2179 f.remove();
2180 tempFiles.remove(tempFiles.begin());
2181 }
2182 delete process;
2183 delete event;
2184 delete alarm;
2185}
Provides read and write access to calendar files.
Definition: alarmcalendar.h:37
KAEvent corresponds to a KCal::Event instance.
Definition: alarmevent.h:232
static void setApplication(const TQString &app, const TQString &productID)
MessageWin: A window to display an alarm message.
Definition: messagewin.h:45
miscellaneous functions
main application window
displays an alarm message