The concept of local folders doesn't apply any more.
[kdepim.git] / kalarm / kalarmapp.cpp
blob2db4c4afbf9d01482915afce072cbe8e554139f6
1 /*
2 * kalarmapp.cpp - the KAlarm application object
3 * Program: kalarm
4 * Copyright © 2001-2012 by David Jarvie <djarvie@kde.org>
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.
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.
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.
21 #include "kalarm.h"
22 #include "kalarmapp.moc"
24 #include "alarmcalendar.h"
25 #include "alarmlistview.h"
26 #include "autoqpointer.h"
27 #include "commandoptions.h"
28 #include "dbushandler.h"
29 #include "editdlgtypes.h"
30 #ifdef USE_AKONADI
31 #include "collectionmodel.h"
32 #else
33 #include "eventlistmodel.h"
34 #endif
35 #include "functions.h"
36 #include "kamail.h"
37 #include "mainwindow.h"
38 #include "messagebox.h"
39 #include "messagewin.h"
40 #include "preferences.h"
41 #include "prefdlg.h"
42 #include "shellprocess.h"
43 #include "startdaytimer.h"
44 #include "traywindow.h"
46 #include "kspeechinterface.h"
48 #include <kalarmcal/datetime.h>
49 #include <kalarmcal/karecurrence.h>
51 #include <klocale.h>
52 #include <kstandarddirs.h>
53 #include <kconfig.h>
54 #include <kaboutdata.h>
55 #include <ktemporaryfile.h>
56 #include <kfileitem.h>
57 #include <kglobal.h>
58 #include <kstandardguiitem.h>
59 #include <kservicetypetrader.h>
60 #include <kspeech.h>
61 #include <ktoolinvocation.h>
62 #include <netwm.h>
63 #include <kdebug.h>
64 #include <kshell.h>
65 #include <ksystemtrayicon.h>
66 #include <ksystemtimezone.h>
68 #include <QObject>
69 #include <QTimer>
70 #include <QRegExp>
71 #include <QFile>
72 #include <QByteArray>
73 #include <QTextStream>
74 #include <QtDBus/QtDBus>
76 #include <stdlib.h>
77 #include <ctype.h>
78 #include <iostream>
79 #include <climits>
81 static const char* KTTSD_DBUS_SERVICE = "org.kde.kttsd";
82 static const char* KTTDS_DBUS_PATH = "/KSpeech";
84 static void setEventCommandError(const KAEvent&, KAEvent::CmdErrType);
85 static void clearEventCommandError(const KAEvent&, KAEvent::CmdErrType);
87 /******************************************************************************
88 * Find the maximum number of seconds late which a late-cancel alarm is allowed
89 * to be. This is calculated as the late cancel interval, plus a few seconds
90 * leeway to cater for any timing irregularities.
92 static inline int maxLateness(int lateCancel)
94 static const int LATENESS_LEEWAY = 5;
95 int lc = (lateCancel >= 1) ? (lateCancel - 1)*60 : 0;
96 return LATENESS_LEEWAY + lc;
100 KAlarmApp* KAlarmApp::theInstance = 0;
101 int KAlarmApp::mActiveCount = 0;
102 int KAlarmApp::mFatalError = 0;
103 QString KAlarmApp::mFatalMessage;
106 /******************************************************************************
107 * Construct the application.
109 KAlarmApp::KAlarmApp()
110 : KUniqueApplication(),
111 mInitialised(false),
112 mQuitting(false),
113 mLoginAlarmsDone(false),
114 mDBusHandler(new DBusHandler()),
115 mTrayWindow(0),
116 mAlarmTimer(new QTimer(this)),
117 mArchivedPurgeDays(-1), // default to not purging
118 mPurgeDaysQueued(-1),
119 mKSpeech(0),
120 mPendingQuit(false),
121 mCancelRtcWake(false),
122 mProcessingQueue(false),
123 mSessionClosingDown(false),
124 mAlarmsEnabled(true),
125 mSpeechEnabled(false)
127 KGlobal::locale()->insertCatalog("libkdepim");
128 kDebug();
129 #ifndef NDEBUG
130 KAlarm::setTestModeConditions();
131 #endif
132 mAlarmTimer->setSingleShot(true);
133 connect(mAlarmTimer, SIGNAL(timeout()), SLOT(checkNextDueAlarm()));
135 setQuitOnLastWindowClosed(false);
136 Preferences::self()->readConfig();
137 if (!Preferences::noAutoStart())
139 Preferences::setAutoStart(true);
140 Preferences::self()->writeConfig();
142 Preferences::connect(SIGNAL(startOfDayChanged(QTime)), this, SLOT(changeStartOfDay()));
143 Preferences::connect(SIGNAL(workTimeChanged(QTime,QTime,QBitArray)), this, SLOT(slotWorkTimeChanged(QTime,QTime,QBitArray)));
144 Preferences::connect(SIGNAL(holidaysChanged(KHolidays::HolidayRegion)), this, SLOT(slotHolidaysChanged(KHolidays::HolidayRegion)));
145 Preferences::connect(SIGNAL(feb29TypeChanged(Feb29Type)), this, SLOT(slotFeb29TypeChanged(Feb29Type)));
146 Preferences::connect(SIGNAL(showInSystemTrayChanged(bool)), this, SLOT(slotShowInSystemTrayChanged()));
147 Preferences::connect(SIGNAL(archivedKeepDaysChanged(int)), this, SLOT(setArchivePurgeDays()));
148 Preferences::connect(SIGNAL(messageFontChanged(QFont)), this, SLOT(slotMessageFontChanged(QFont)));
149 slotFeb29TypeChanged(Preferences::defaultFeb29Type());
151 connect(QDBusConnection::sessionBus().interface(), SIGNAL(serviceUnregistered(QString)),
152 SLOT(slotDBusServiceUnregistered(QString)));
153 KAEvent::setStartOfDay(Preferences::startOfDay());
154 KAEvent::setWorkTime(Preferences::workDays(), Preferences::workDayStart(), Preferences::workDayEnd());
155 KAEvent::setHolidays(Preferences::holidays());
156 KAEvent::setDefaultFont(Preferences::messageFont());
157 if (AlarmCalendar::initialiseCalendars())
159 connect(AlarmCalendar::resources(), SIGNAL(earliestAlarmChanged()), SLOT(checkNextDueAlarm()));
160 #ifdef USE_AKONADI
161 connect(AlarmCalendar::resources(), SIGNAL(atLoginEventAdded(KAEvent)), SLOT(atLoginEventAdded(KAEvent)));
162 connect(AkonadiModel::instance(), SIGNAL(collectionAdded(Akonadi::Collection)),
163 SLOT(purgeNewArchivedDefault(Akonadi::Collection)));
164 #endif
166 KConfigGroup config(KGlobal::config(), "General");
167 mNoSystemTray = config.readEntry("NoSystemTray", false);
168 mOldShowInSystemTray = wantShowInSystemTray();
169 DateTime::setStartOfDay(Preferences::startOfDay());
170 mPrefsArchivedColour = Preferences::archivedColour();
173 // Check if the speech synthesis daemon is installed
174 mSpeechEnabled = (KServiceTypeTrader::self()->query("DBUS/Text-to-Speech", "Name == 'KTTSD'").count() > 0);
175 if (!mSpeechEnabled) { kDebug() << "Speech synthesis disabled (KTTSD not found)"; }
176 // Check if KOrganizer is installed
177 QString korg = QLatin1String("korganizer");
178 mKOrganizerEnabled = !KStandardDirs::locate("exe", korg).isNull() || !KStandardDirs::findExe(korg).isNull();
179 if (!mKOrganizerEnabled) { kDebug() << "KOrganizer options disabled (KOrganizer not found)"; }
182 /******************************************************************************
184 KAlarmApp::~KAlarmApp()
186 while (!mCommandProcesses.isEmpty())
188 ProcData* pd = mCommandProcesses[0];
189 mCommandProcesses.pop_front();
190 delete pd;
192 AlarmCalendar::terminateCalendars();
195 /******************************************************************************
196 * Return the one and only KAlarmApp instance.
197 * If it doesn't already exist, it is created first.
199 KAlarmApp* KAlarmApp::getInstance()
201 if (!theInstance)
203 theInstance = new KAlarmApp;
205 if (mFatalError)
206 theInstance->quitFatal();
208 return theInstance;
211 /******************************************************************************
212 * Restore the saved session if required.
214 bool KAlarmApp::restoreSession()
216 if (!isSessionRestored())
217 return false;
218 if (mFatalError)
220 quitFatal();
221 return false;
224 // Process is being restored by session management.
225 kDebug() << "Restoring";
226 ++mActiveCount;
227 // Create the session config object now.
228 // This is necessary since if initCheck() below causes calendars to be updated,
229 // the session config created after that points to an invalid file, resulting
230 // in no windows being restored followed by a later crash.
231 kapp->sessionConfig();
233 // When KAlarm is session restored, automatically set start-at-login to true.
234 Preferences::self()->readConfig();
235 Preferences::setAutoStart(true);
236 Preferences::setNoAutoStart(false);
237 Preferences::setAskAutoStart(true); // cancel any start-at-login prompt suppression
238 Preferences::self()->writeConfig();
240 if (!initCheck(true)) // open the calendar file (needed for main windows), don't process queue yet
242 --mActiveCount;
243 quitIf(1, true); // error opening the main calendar - quit
244 return false;
246 MainWindow* trayParent = 0;
247 for (int i = 1; KMainWindow::canBeRestored(i); ++i)
249 QString type = KMainWindow::classNameOfToplevel(i);
250 if (type == QLatin1String("MainWindow"))
252 MainWindow* win = MainWindow::create(true);
253 win->restore(i, false);
254 if (win->isHiddenTrayParent())
255 trayParent = win;
256 else
257 win->show();
259 else if (type == QLatin1String("MessageWin"))
261 MessageWin* win = new MessageWin;
262 win->restore(i, false);
263 if (win->isValid())
264 win->show();
265 else
266 delete win;
270 // Try to display the system tray icon if it is configured to be shown
271 if (trayParent || wantShowInSystemTray())
273 if (!MainWindow::count())
274 kWarning() << "no main window to be restored!?";
275 else
277 displayTrayIcon(true, trayParent);
278 // Occasionally for no obvious reason, the main main window is
279 // shown when it should be hidden, so hide it just to be sure.
280 if (trayParent)
281 trayParent->hide();
285 --mActiveCount;
286 if (quitIf(0)) // quit if no windows are open
287 return false; // quitIf() can sometimes return, despite calling exit()
289 // Check whether the KDE time zone daemon is running (but don't hold up initialisation)
290 QTimer::singleShot(0, this, SLOT(checkKtimezoned()));
292 startProcessQueue(); // start processing the execution queue
293 return true;
296 /******************************************************************************
297 * Called for a KUniqueApplication when a new instance of the application is
298 * started.
300 int KAlarmApp::newInstance()
302 kDebug();
303 if (mFatalError)
305 quitFatal();
306 return 1;
308 ++mActiveCount;
309 int exitCode = 0; // default = success
310 static bool firstInstance = true;
311 bool dontRedisplay = false;
312 if (!firstInstance || !isSessionRestored())
314 CommandOptions options; // fetch and parse command line options
315 #ifndef NDEBUG
316 if (options.simulationTime().isValid())
317 KAlarm::setSimulatedSystemTime(options.simulationTime());
318 #endif
319 CommandOptions::Command command = options.command();
320 if (options.disableAll())
321 setAlarmsEnabled(false); // disable alarm monitoring
322 switch (command)
324 case CommandOptions::TRIGGER_EVENT:
325 case CommandOptions::CANCEL_EVENT:
327 // Display or delete the event with the specified event ID
328 EventFunc function = (command == CommandOptions::TRIGGER_EVENT) ? EVENT_TRIGGER : EVENT_CANCEL;
329 if (!initCheck(true)) // open the calendar, don't start processing execution queue yet
330 exitCode = 1;
331 else
333 startProcessQueue(); // start processing the execution queue
334 if (!handleEvent(options.eventId(), function))
335 exitCode = 1;
337 break;
339 case CommandOptions::EDIT:
340 // Edit a specified existing alarm
341 if (!initCheck())
342 exitCode = 1;
343 else if (!KAlarm::editAlarmById(options.eventId()))
345 CommandOptions::printError(i18nc("@info:shell", "<icode>%1</icode>: Event <resource>%2</resource> not found, or not editable", QString::fromLatin1("--edit"), options.eventId()));
346 exitCode = 1;
348 break;
350 case CommandOptions::EDIT_NEW:
352 // Edit a new alarm, and optionally preset selected values
353 if (!initCheck())
354 exitCode = 1;
355 else
357 // Use AutoQPointer to guard against crash on application exit while
358 // the dialogue is still open. It prevents double deletion (both on
359 // deletion of parent, and on return from this function).
360 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(false, options.editType());
361 if (options.alarmTime().isValid())
362 editDlg->setTime(options.alarmTime());
363 if (options.recurrence())
364 editDlg->setRecurrence(*options.recurrence(), options.subRepeatInterval(), options.subRepeatCount());
365 else if (options.flags() & KAEvent::REPEAT_AT_LOGIN)
366 editDlg->setRepeatAtLogin();
367 editDlg->setAction(options.editAction(), AlarmText(options.text()));
368 if (options.lateCancel())
369 editDlg->setLateCancel(options.lateCancel());
370 if (options.flags() & KAEvent::COPY_KORGANIZER)
371 editDlg->setShowInKOrganizer(true);
372 switch (options.editType())
374 case EditAlarmDlg::DISPLAY:
376 // EditAlarmDlg::create() always returns EditDisplayAlarmDlg for type = DISPLAY
377 EditDisplayAlarmDlg* dlg = qobject_cast<EditDisplayAlarmDlg*>(editDlg);
378 if (options.fgColour().isValid())
379 dlg->setFgColour(options.fgColour());
380 if (options.bgColour().isValid())
381 dlg->setBgColour(options.bgColour());
382 if (!options.audioFile().isEmpty()
383 || options.flags() & (KAEvent::BEEP | KAEvent::SPEAK))
385 KAEvent::Flags flags = options.flags();
386 Preferences::SoundType type = (flags & KAEvent::BEEP) ? Preferences::Sound_Beep
387 : (flags & KAEvent::SPEAK) ? Preferences::Sound_Speak
388 : Preferences::Sound_File;
389 dlg->setAudio(type, options.audioFile(), options.audioVolume(), (flags & KAEvent::REPEAT_SOUND ? 0 : -1));
391 if (options.reminderMinutes())
392 dlg->setReminder(options.reminderMinutes(), (options.flags() & KAEvent::REMINDER_ONCE));
393 if (options.flags() & KAEvent::CONFIRM_ACK)
394 dlg->setConfirmAck(true);
395 if (options.flags() & KAEvent::AUTO_CLOSE)
396 dlg->setAutoClose(true);
397 break;
399 case EditAlarmDlg::COMMAND:
400 break;
401 case EditAlarmDlg::EMAIL:
403 // EditAlarmDlg::create() always returns EditEmailAlarmDlg for type = EMAIL
404 EditEmailAlarmDlg* dlg = qobject_cast<EditEmailAlarmDlg*>(editDlg);
405 if (options.fromID()
406 || !options.addressees().isEmpty()
407 || !options.subject().isEmpty()
408 || !options.attachments().isEmpty())
409 dlg->setEmailFields(options.fromID(), options.addressees(), options.subject(), options.attachments());
410 if (options.flags() & KAEvent::EMAIL_BCC)
411 dlg->setBcc(true);
412 break;
414 case EditAlarmDlg::AUDIO:
416 // EditAlarmDlg::create() always returns EditAudioAlarmDlg for type = AUDIO
417 EditAudioAlarmDlg* dlg = qobject_cast<EditAudioAlarmDlg*>(editDlg);
418 if (!options.audioFile().isEmpty() || options.audioVolume() >= 0)
419 dlg->setAudio(options.audioFile(), options.audioVolume());
420 break;
422 case EditAlarmDlg::NO_TYPE:
423 break;
425 KAlarm::execNewAlarmDlg(editDlg);
427 break;
429 case CommandOptions::EDIT_NEW_PRESET:
430 // Edit a new alarm, preset with a template
431 if (!initCheck())
432 exitCode = 1;
433 else
434 KAlarm::editNewAlarm(options.templateName());
435 break;
437 case CommandOptions::NEW:
438 // Display a message or file, execute a command, or send an email
439 if (!initCheck()
440 || !scheduleEvent(options.editAction(), options.text(), options.alarmTime(),
441 options.lateCancel(), options.flags(), options.bgColour(),
442 options.fgColour(), QFont(), options.audioFile(), options.audioVolume(),
443 options.reminderMinutes(), (options.recurrence() ? *options.recurrence() : KARecurrence()),
444 options.subRepeatInterval(), options.subRepeatCount(),
445 options.fromID(), options.addressees(),
446 options.subject(), options.attachments()))
447 exitCode = 1;
448 break;
450 case CommandOptions::TRAY:
451 // Display only the system tray icon
452 if (Preferences::showInSystemTray() && KSystemTrayIcon::isSystemTrayAvailable())
454 if (!initCheck() // open the calendar, start processing execution queue
455 || !displayTrayIcon(true))
456 exitCode = 1;
457 break;
459 // fall through to NONE
460 case CommandOptions::NONE:
461 // No arguments - run interactively & display the main window
462 #ifndef NDEBUG
463 if (options.simulationTime().isValid() && !firstInstance)
464 break; // simulating time: don't open main window if already running
465 #endif
466 if (!initCheck())
467 exitCode = 1;
468 else
470 MainWindow* win = MainWindow::create();
471 if (command == CommandOptions::TRAY)
472 win->setWindowState(win->windowState() | Qt::WindowMinimized);
473 win->show();
475 break;
477 case CommandOptions::CMD_ERROR:
478 exitCode = 1;
479 break;
483 // If this is the first time through, redisplay any alarm message windows
484 // from last time.
485 if (firstInstance && !dontRedisplay && !exitCode)
487 /* First time through, so redisplay alarm message windows from last time.
488 * But it is possible for session restoration in some circumstances to
489 * not create any windows, in which case the alarm calendars will have
490 * been deleted - if so, don't try to do anything. (This has been known
491 * to happen under the Xfce desktop.)
493 if (AlarmCalendar::resources())
494 MessageWin::redisplayAlarms();
497 --mActiveCount;
498 firstInstance = false;
500 // Quit the application if this was the last/only running "instance" of the program.
501 // Executing 'return' doesn't work very well since the program continues to
502 // run if no windows were created.
503 quitIf(exitCode);
505 // Check whether the KDE time zone daemon is running (but don't hold up initialisation)
506 QTimer::singleShot(0, this, SLOT(checkKtimezoned()));
508 return exitCode;
511 void KAlarmApp::checkKtimezoned()
513 // Check that the KDE time zone daemon is running
514 static bool done = false;
515 if (done)
516 return;
517 done = true;
518 #if KDE_IS_VERSION(4,5,70)
519 if (!KSystemTimeZones::isTimeZoneDaemonAvailable())
521 kDebug() << "ktimezoned not running: using UTC only";
522 KAMessageBox::information(MainWindow::mainMainWindow(),
523 i18nc("@info", "Time zones are not accessible:<nl/>KAlarm will use the UTC time zone.<nl/><nl/>(The KDE time zone service is not available:<nl/>check that <application>ktimezoned</application> is installed.)"),
524 QString(), QLatin1String("tzunavailable"));
526 #endif
529 /******************************************************************************
530 * Quit the program, optionally only if there are no more "instances" running.
531 * Reply = true if program exited.
533 bool KAlarmApp::quitIf(int exitCode, bool force)
535 if (force)
537 // Quit regardless, except for message windows
538 mQuitting = true;
539 MainWindow::closeAll();
540 mQuitting = false;
541 displayTrayIcon(false);
542 if (MessageWin::instanceCount(true)) // ignore always-hidden windows (e.g. audio alarms)
543 return false;
545 else if (mQuitting)
546 return false; // MainWindow::closeAll() causes quitIf() to be called again
547 else
549 // Quit only if there are no more "instances" running
550 mPendingQuit = false;
551 if (mActiveCount > 0 || MessageWin::instanceCount(true)) // ignore always-hidden windows (e.g. audio alarms)
552 return false;
553 int mwcount = MainWindow::count();
554 MainWindow* mw = mwcount ? MainWindow::firstWindow() : 0;
555 if (mwcount > 1 || (mwcount && (!mw->isHidden() || !mw->isTrayParent())))
556 return false;
557 // There are no windows left except perhaps a main window which is a hidden
558 // tray icon parent, or an always-hidden message window.
559 if (mTrayWindow)
561 // There is a system tray icon.
562 // Don't exit unless the system tray doesn't seem to exist.
563 if (checkSystemTray())
564 return false;
566 if (!mDcopQueue.isEmpty() || !mCommandProcesses.isEmpty())
568 // Don't quit yet if there are outstanding actions on the execution queue
569 mPendingQuit = true;
570 mPendingQuitCode = exitCode;
571 return false;
575 // This was the last/only running "instance" of the program, so exit completely.
576 kDebug() << exitCode << ": quitting";
577 MessageWin::stopAudio(true);
578 if (mCancelRtcWake)
580 KAlarm::setRtcWakeTime(0, 0);
581 KAlarm::deleteRtcWakeConfig();
583 delete mAlarmTimer; // prevent checking for alarms after deleting calendars
584 mAlarmTimer = 0;
585 mInitialised = false; // prevent processQueue() from running
586 AlarmCalendar::terminateCalendars();
587 exit(exitCode);
588 return true; // sometimes we actually get to here, despite calling exit()
591 /******************************************************************************
592 * Called when the Quit menu item is selected.
593 * Closes the system tray window and all main windows, but does not exit the
594 * program if other windows are still open.
596 void KAlarmApp::doQuit(QWidget* parent)
598 kDebug();
599 if (KAMessageBox::warningContinueCancel(parent, KMessageBox::Cancel,
600 i18nc("@info", "Quitting will disable alarms (once any alarm message windows are closed)."),
601 QString(), KStandardGuiItem::quit(), Preferences::QUIT_WARN
602 ) != KMessageBox::Yes)
603 return;
604 if (!KAlarm::checkRtcWakeConfig(true).isEmpty())
606 // A wake-on-suspend alarm is set
607 if (KAMessageBox::warningContinueCancel(parent, KMessageBox::Cancel,
608 i18nc("@info", "Quitting will cancel the scheduled Wake from Suspend."),
609 QString(), KStandardGuiItem::quit()
610 ) != KMessageBox::Yes)
611 return;
612 mCancelRtcWake = true;
614 if (!Preferences::autoStart())
616 int option = KMessageBox::No;
617 if (!Preferences::autoStartChangedByUser())
619 option = KAMessageBox::questionYesNoCancel(parent,
620 i18nc("@info", "Do you want to start KAlarm at login?<nl/>"
621 "(Note that alarms will be disabled if KAlarm is not started.)"),
622 QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(),
623 KStandardGuiItem::cancel(), Preferences::ASK_AUTO_START);
625 switch (option)
627 case KMessageBox::Yes:
628 Preferences::setAutoStart(true);
629 Preferences::setNoAutoStart(false);
630 break;
631 case KMessageBox::No:
632 Preferences::setNoAutoStart(true);
633 break;
634 case KMessageBox::Cancel:
635 default:
636 return;
638 Preferences::self()->writeConfig();
640 quitIf(0, true);
643 /******************************************************************************
644 * Called when the session manager is about to close down the application.
646 void KAlarmApp::commitData(QSessionManager& sm)
648 mSessionClosingDown = true;
649 KUniqueApplication::commitData(sm);
650 mSessionClosingDown = false; // reset in case shutdown is cancelled
653 /******************************************************************************
654 * Display an error message for a fatal error. Prevent further actions since
655 * the program state is unsafe.
657 void KAlarmApp::displayFatalError(const QString& message)
659 if (!mFatalError)
661 mFatalError = 1;
662 mFatalMessage = message;
663 if (theInstance)
664 QTimer::singleShot(0, theInstance, SLOT(quitFatal()));
668 /******************************************************************************
669 * Quit the program, once the fatal error message has been acknowledged.
671 void KAlarmApp::quitFatal()
673 switch (mFatalError)
675 case 0:
676 case 2:
677 return;
678 case 1:
679 mFatalError = 2;
680 KMessageBox::error(0, mFatalMessage); // this is an application modal window
681 mFatalError = 3;
682 // fall through to '3'
683 case 3:
684 if (theInstance)
685 theInstance->quitIf(1, true);
686 break;
688 QTimer::singleShot(1000, this, SLOT(quitFatal()));
691 /******************************************************************************
692 * Called by the alarm timer when the next alarm is due.
693 * Also called when the execution queue has finished processing to check for the
694 * next alarm.
696 void KAlarmApp::checkNextDueAlarm()
698 if (!mAlarmsEnabled)
699 return;
700 // Find the first alarm due
701 KAEvent* nextEvent = AlarmCalendar::resources()->earliestAlarm();
702 if (!nextEvent)
703 return; // there are no alarms pending
704 KDateTime nextDt = nextEvent->nextTrigger(KAEvent::ALL_TRIGGER).effectiveKDateTime();
705 KDateTime now = KDateTime::currentDateTime(Preferences::timeZone());
706 qint64 interval = now.secsTo_long(nextDt);
707 kDebug() << "now:" << qPrintable(now.toString("%Y-%m-%d %H:%M %:Z")) << ", next:" << qPrintable(nextDt.toString("%Y-%m-%d %H:%M %:Z")) << ", due:" << interval;
708 if (interval <= 0)
710 // Queue the alarm
711 queueAlarmId(nextEvent->id());
712 kDebug() << nextEvent->id() << ": due now";
713 QTimer::singleShot(0, this, SLOT(processQueue()));
715 else
717 // No alarm is due yet, so set timer to wake us when it's due.
718 // Check for integer overflow before setting timer.
719 #ifndef HIBERNATION_SIGNAL
720 /* TODO: REPLACE THIS CODE WHEN A SYSTEM NOTIFICATION SIGNAL BECOMES
721 * AVAILABLE FOR WAKEUP FROM HIBERNATION.
722 * Re-evaluate the next alarm time every minute, in case the
723 * system clock jumps. The most common case when the clock jumps
724 * is when a laptop wakes from hibernation. If timers were left to
725 * run, they would trigger late by the length of time the system
726 * was asleep.
728 if (interval > 60) // 1 minute
729 interval = 60;
730 #endif
731 interval *= 1000;
732 if (interval > INT_MAX)
733 interval = INT_MAX;
734 kDebug() << nextEvent->id() << "wait" << interval/1000 << "seconds";
735 mAlarmTimer->start(static_cast<int>(interval));
739 /******************************************************************************
740 * Called by the alarm timer when the next alarm is due.
741 * Also called when the execution queue has finished processing to check for the
742 * next alarm.
744 void KAlarmApp::queueAlarmId(const QString& id)
746 for (int i = 0, end = mDcopQueue.count(); i < end; ++i)
748 if (mDcopQueue[i].function == EVENT_HANDLE && mDcopQueue[i].eventId == id)
749 return; // the alarm is already queued
751 mDcopQueue.enqueue(DcopQEntry(EVENT_HANDLE, id));
754 /******************************************************************************
755 * Start processing the execution queue.
757 void KAlarmApp::startProcessQueue()
759 if (!mInitialised)
761 kDebug();
762 mInitialised = true;
763 QTimer::singleShot(0, this, SLOT(processQueue())); // process anything already queued
767 /******************************************************************************
768 * The main processing loop for KAlarm.
769 * All KAlarm operations involving opening or updating calendar files are called
770 * from this loop to ensure that only one operation is active at any one time.
771 * This precaution is necessary because KAlarm's activities are mostly
772 * asynchronous, being in response to D-Bus calls from other programs or timer
773 * events, any of which can be received in the middle of performing another
774 * operation. If a calendar file is opened or updated while another calendar
775 * operation is in progress, the program has been observed to hang, or the first
776 * calendar call has failed with data loss - clearly unacceptable!!
778 void KAlarmApp::processQueue()
780 if (mInitialised && !mProcessingQueue)
782 kDebug();
783 mProcessingQueue = true;
785 // Refresh alarms if that's been queued
786 KAlarm::refreshAlarmsIfQueued();
788 if (!mLoginAlarmsDone)
790 // Queue all at-login alarms once only, at program start-up.
791 // First, cancel any scheduled reminders or deferrals for them,
792 // since these will be superseded by the new at-login trigger.
793 KAEvent::List events = AlarmCalendar::resources()->atLoginAlarms();
794 for (int i = 0, end = events.count(); i < end; ++i)
796 KAEvent event = *events[i];
797 if (!cancelReminderAndDeferral(event))
799 if (mAlarmsEnabled)
800 queueAlarmId(event.id());
803 mLoginAlarmsDone = true;
806 // Process queued events
807 while (!mDcopQueue.isEmpty())
809 DcopQEntry& entry = mDcopQueue.head();
810 if (entry.eventId.isEmpty())
812 // It's a new alarm
813 switch (entry.function)
815 case EVENT_TRIGGER:
816 execAlarm(entry.event, entry.event.firstAlarm(), false);
817 break;
818 case EVENT_HANDLE:
819 KAlarm::addEvent(entry.event, 0, 0, KAlarm::ALLOW_KORG_UPDATE | KAlarm::NO_RESOURCE_PROMPT);
820 break;
821 case EVENT_CANCEL:
822 break;
825 else
826 handleEvent(entry.eventId, entry.function);
827 mDcopQueue.dequeue();
830 // Purge the default archived alarms resource if it's time to do so
831 if (mPurgeDaysQueued >= 0)
833 KAlarm::purgeArchive(mPurgeDaysQueued);
834 mPurgeDaysQueued = -1;
837 // Now that the queue has been processed, quit if a quit was queued
838 if (mPendingQuit)
840 if (quitIf(mPendingQuitCode))
841 return; // quitIf() can sometimes return, despite calling exit()
844 mProcessingQueue = false;
846 // Schedule the application to be woken when the next alarm is due
847 checkNextDueAlarm();
851 #ifdef USE_AKONADI
852 /******************************************************************************
853 * Called when a repeat-at-login alarm has been added externally.
854 * Queues the alarm for triggering.
855 * First, cancel any scheduled reminder or deferral for it, since these will be
856 * superseded by the new at-login trigger.
858 void KAlarmApp::atLoginEventAdded(const KAEvent& event)
860 KAEvent ev = event;
861 if (!cancelReminderAndDeferral(ev))
863 if (mAlarmsEnabled)
865 mDcopQueue.enqueue(DcopQEntry(EVENT_HANDLE, ev.id()));
866 if (mInitialised)
867 QTimer::singleShot(0, this, SLOT(processQueue()));
871 #endif
873 /******************************************************************************
874 * Called when the system tray main window is closed.
876 void KAlarmApp::removeWindow(TrayWindow*)
878 mTrayWindow = 0;
881 /******************************************************************************
882 * Display or close the system tray icon.
884 bool KAlarmApp::displayTrayIcon(bool show, MainWindow* parent)
886 kDebug();
887 static bool creating = false;
888 if (show)
890 if (!mTrayWindow && !creating)
892 if (!KSystemTrayIcon::isSystemTrayAvailable())
893 return false;
894 if (!MainWindow::count())
896 // We have to have at least one main window to act
897 // as parent to the system tray icon (even if the
898 // window is hidden).
899 creating = true; // prevent main window constructor from creating an additional tray icon
900 parent = MainWindow::create();
901 creating = false;
903 mTrayWindow = new TrayWindow(parent ? parent : MainWindow::firstWindow());
904 connect(mTrayWindow, SIGNAL(deleted()), SIGNAL(trayIconToggled()));
905 emit trayIconToggled();
907 if (!checkSystemTray())
908 quitIf(0); // exit the application if there are no open windows
911 else
913 delete mTrayWindow;
914 mTrayWindow = 0;
916 return true;
919 /******************************************************************************
920 * Check whether the system tray icon has been housed in the system tray.
922 bool KAlarmApp::checkSystemTray()
924 if (!mTrayWindow)
925 return true;
926 if (KSystemTrayIcon::isSystemTrayAvailable() == mNoSystemTray)
928 kDebug() << "changed ->" << mNoSystemTray;
929 mNoSystemTray = !mNoSystemTray;
931 // Store the new setting in the config file, so that if KAlarm exits it will
932 // restart with the correct default.
933 KConfigGroup config(KGlobal::config(), "General");
934 config.writeEntry("NoSystemTray", mNoSystemTray);
935 config.sync();
937 // Update other settings
938 slotShowInSystemTrayChanged();
940 return !mNoSystemTray;
943 /******************************************************************************
944 * Return the main window associated with the system tray icon.
946 MainWindow* KAlarmApp::trayMainWindow() const
948 return mTrayWindow ? mTrayWindow->assocMainWindow() : 0;
951 /******************************************************************************
952 * Called when the show-in-system-tray preference setting has changed, to show
953 * or hide the system tray icon.
955 void KAlarmApp::slotShowInSystemTrayChanged()
957 bool newShowInSysTray = wantShowInSystemTray();
958 if (newShowInSysTray != mOldShowInSystemTray)
960 // The system tray run mode has changed
961 ++mActiveCount; // prevent the application from quitting
962 MainWindow* win = mTrayWindow ? mTrayWindow->assocMainWindow() : 0;
963 delete mTrayWindow; // remove the system tray icon if it is currently shown
964 mTrayWindow = 0;
965 mOldShowInSystemTray = newShowInSysTray;
966 if (newShowInSysTray)
968 // Show the system tray icon
969 displayTrayIcon(true);
971 else
973 // Stop showing the system tray icon
974 if (win && win->isHidden())
976 if (MainWindow::count() > 1)
977 delete win;
978 else
980 win->setWindowState(win->windowState() | Qt::WindowMinimized);
981 win->show();
985 --mActiveCount;
989 /******************************************************************************
990 * Called when the start-of-day time preference setting has changed.
991 * Change alarm times for date-only alarms.
993 void KAlarmApp::changeStartOfDay()
995 DateTime::setStartOfDay(Preferences::startOfDay());
996 KAEvent::setStartOfDay(Preferences::startOfDay());
997 AlarmCalendar::resources()->adjustStartOfDay();
1000 /******************************************************************************
1001 * Called when the default alarm message font preference setting has changed.
1002 * Notify KAEvent.
1004 void KAlarmApp::slotMessageFontChanged(const QFont& font)
1006 KAEvent::setDefaultFont(font);
1009 /******************************************************************************
1010 * Called when the working time preference settings have changed.
1011 * Notify KAEvent.
1013 void KAlarmApp::slotWorkTimeChanged(const QTime& start, const QTime& end, const QBitArray& days)
1015 KAEvent::setWorkTime(days, start, end);
1018 /******************************************************************************
1019 * Called when the holiday region preference setting has changed.
1020 * Notify KAEvent.
1022 void KAlarmApp::slotHolidaysChanged(const KHolidays::HolidayRegion& holidays)
1024 KAEvent::setHolidays(holidays);
1027 /******************************************************************************
1028 * Called when the date for February 29th recurrences has changed in the
1029 * preferences settings.
1031 void KAlarmApp::slotFeb29TypeChanged(Preferences::Feb29Type type)
1033 KARecurrence::Feb29Type rtype;
1034 switch (type)
1036 default:
1037 case Preferences::Feb29_None: rtype = KARecurrence::Feb29_None; break;
1038 case Preferences::Feb29_Feb28: rtype = KARecurrence::Feb29_Feb28; break;
1039 case Preferences::Feb29_Mar1: rtype = KARecurrence::Feb29_Mar1; break;
1041 KARecurrence::setDefaultFeb29Type(rtype);
1044 /******************************************************************************
1045 * Return whether the program is configured to be running in the system tray.
1047 bool KAlarmApp::wantShowInSystemTray() const
1049 return Preferences::showInSystemTray() && KSystemTrayIcon::isSystemTrayAvailable();
1052 #ifdef USE_AKONADI
1053 /******************************************************************************
1054 * Called when a new collection has been added, or when a collection has been
1055 * set as the standard collection for its type.
1056 * If it is the default archived calendar, purge its old alarms if necessary.
1058 void KAlarmApp::purgeNewArchivedDefault(const Akonadi::Collection& collection)
1060 Akonadi::Collection col(collection);
1061 if (CollectionControlModel::isStandard(col, CalEvent::ARCHIVED))
1063 // Allow time (1 minute) for AkonadiModel to be populated with the
1064 // collection's events before purging it.
1065 kDebug() << collection.id() << ": standard archived...";
1066 QTimer::singleShot(60000, this, SLOT(purgeAfterDelay()));
1070 /******************************************************************************
1071 * Called after a delay, after the default archived calendar has been added to
1072 * AkonadiModel.
1073 * Purge old alarms from it if necessary.
1075 void KAlarmApp::purgeAfterDelay()
1077 if (mArchivedPurgeDays >= 0)
1078 purge(mArchivedPurgeDays);
1079 else
1080 setArchivePurgeDays();
1082 #endif
1084 /******************************************************************************
1085 * Called when the length of time to keep archived alarms changes in KAlarm's
1086 * preferences.
1087 * Set the number of days to keep archived alarms.
1088 * Alarms which are older are purged immediately, and at the start of each day.
1090 void KAlarmApp::setArchivePurgeDays()
1092 int newDays = Preferences::archivedKeepDays();
1093 if (newDays != mArchivedPurgeDays)
1095 int oldDays = mArchivedPurgeDays;
1096 mArchivedPurgeDays = newDays;
1097 if (mArchivedPurgeDays <= 0)
1098 StartOfDayTimer::disconnect(this);
1099 if (mArchivedPurgeDays < 0)
1100 return; // keep indefinitely, so don't purge
1101 if (oldDays < 0 || mArchivedPurgeDays < oldDays)
1103 // Alarms are now being kept for less long, so purge them
1104 purge(mArchivedPurgeDays);
1105 if (!mArchivedPurgeDays)
1106 return; // don't archive any alarms
1108 // Start the purge timer to expire at the start of the next day
1109 // (using the user-defined start-of-day time).
1110 StartOfDayTimer::connect(this, SLOT(slotPurge()));
1114 /******************************************************************************
1115 * Purge all archived events from the calendar whose end time is longer ago than
1116 * 'daysToKeep'. All events are deleted if 'daysToKeep' is zero.
1118 void KAlarmApp::purge(int daysToKeep)
1120 if (mPurgeDaysQueued < 0 || daysToKeep < mPurgeDaysQueued)
1121 mPurgeDaysQueued = daysToKeep;
1123 // Do the purge once any other current operations are completed
1124 processQueue();
1128 /******************************************************************************
1129 * Enable or disable alarm monitoring.
1131 void KAlarmApp::setAlarmsEnabled(bool enabled)
1133 if (enabled != mAlarmsEnabled)
1135 mAlarmsEnabled = enabled;
1136 emit alarmEnabledToggled(enabled);
1137 if (!enabled)
1138 KAlarm::cancelRtcWake(0);
1139 else if (!mProcessingQueue)
1140 checkNextDueAlarm();
1144 /******************************************************************************
1145 * Spread or collect alarm message and error message windows.
1147 void KAlarmApp::spreadWindows(bool spread)
1149 spread = MessageWin::spread(spread);
1150 emit spreadWindowsToggled(spread);
1153 /******************************************************************************
1154 * Called when the spread status of message windows changes.
1155 * Set the 'spread windows' action state.
1157 void KAlarmApp::setSpreadWindowsState(bool spread)
1159 emit spreadWindowsToggled(spread);
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.
1167 bool KAlarmApp::scheduleEvent(KAEvent::SubAction action, const QString& text, const KDateTime& dateTime,
1168 int lateCancel, KAEvent::Flags flags, const QColor& bg, const QColor& fg,
1169 const QFont& font, const QString& audioFile, float audioVolume, int reminderMinutes,
1170 const KARecurrence& recurrence, int repeatInterval, int repeatCount,
1171 #ifdef USE_AKONADI
1172 uint mailFromID, const KCalCore::Person::List& mailAddresses,
1173 #else
1174 uint mailFromID, const QList<KCal::Person>& mailAddresses,
1175 #endif
1176 const QString& mailSubject, const QStringList& mailAttachments)
1178 kDebug() << text;
1179 if (!dateTime.isValid())
1180 return false;
1181 KDateTime now = KDateTime::currentUtcDateTime();
1182 if (lateCancel && dateTime < now.addSecs(-maxLateness(lateCancel)))
1183 return true; // alarm time was already archived too long ago
1184 KDateTime alarmTime = dateTime;
1185 // Round down to the nearest minute to avoid scheduling being messed up
1186 if (!dateTime.isDateOnly())
1187 alarmTime.setTime(QTime(alarmTime.time().hour(), alarmTime.time().minute(), 0));
1189 KAEvent event(alarmTime, text, bg, fg, font, action, lateCancel, flags, true);
1190 if (reminderMinutes)
1192 bool onceOnly = flags & KAEvent::REMINDER_ONCE;
1193 event.setReminder(reminderMinutes, onceOnly);
1195 if (!audioFile.isEmpty())
1196 event.setAudioFile(audioFile, audioVolume, -1, 0, (flags & KAEvent::REPEAT_SOUND) ? 0 : -1);
1197 if (!mailAddresses.isEmpty())
1198 event.setEmail(mailFromID, mailAddresses, mailSubject, mailAttachments);
1199 event.setRecurrence(recurrence);
1200 event.setFirstRecurrence();
1201 event.setRepetition(Repetition(repeatInterval, repeatCount - 1));
1202 event.endChanges();
1203 if (alarmTime <= now)
1205 // Alarm is due for display already.
1206 // First execute it once without adding it to the calendar file.
1207 if (!mInitialised)
1208 mDcopQueue.enqueue(DcopQEntry(event, EVENT_TRIGGER));
1209 else
1210 execAlarm(event, event.firstAlarm(), false);
1211 // If it's a recurring alarm, reschedule it for its next occurrence
1212 if (!event.recurs()
1213 || event.setNextOccurrence(now) == KAEvent::NO_OCCURRENCE)
1214 return true;
1215 // It has recurrences in the future
1218 // Queue the alarm for insertion into the calendar file
1219 mDcopQueue.enqueue(DcopQEntry(event));
1220 if (mInitialised)
1221 QTimer::singleShot(0, this, SLOT(processQueue()));
1222 return true;
1225 /******************************************************************************
1226 * Called in response to a D-Bus request to trigger or cancel an event.
1227 * Optionally display the event. Delete the event from the calendar file and
1228 * from every main window instance.
1230 bool KAlarmApp::dbusHandleEvent(const QString& eventID, EventFunc function)
1232 kDebug() << eventID;
1233 mDcopQueue.append(DcopQEntry(function, eventID));
1234 if (mInitialised)
1235 QTimer::singleShot(0, this, SLOT(processQueue()));
1236 return true;
1239 /******************************************************************************
1240 * Either:
1241 * a) Display the event and then delete it if it has no outstanding repetitions.
1242 * b) Delete the event.
1243 * c) Reschedule the event for its next repetition. If none remain, delete it.
1244 * If the event is deleted, it is removed from the calendar file and from every
1245 * main window instance.
1246 * Reply = false if event ID not found, or if more than one event with the same
1247 * ID is found.
1249 bool KAlarmApp::handleEvent(const QString& eventID, EventFunc function)
1251 // Delete any expired wake-on-suspend config data
1252 KAlarm::checkRtcWakeConfig();
1254 #ifdef USE_AKONADI
1255 KAEvent::List events = AlarmCalendar::resources()->events(eventID);
1256 if (events.count() > 1)
1258 kWarning() << "Multiple events found with ID" << eventID;
1259 return false;
1261 if (events.isEmpty())
1262 #else
1263 KAEvent* event = AlarmCalendar::resources()->event(eventID);
1264 if (!event)
1265 #endif
1267 kWarning() << "Event ID not found:" << eventID;
1268 return false;
1270 #ifdef USE_AKONADI
1271 KAEvent* event = events[0];
1272 #endif
1273 switch (function)
1275 case EVENT_CANCEL:
1276 kDebug() << eventID << ", CANCEL";
1277 KAlarm::deleteEvent(*event, true);
1278 break;
1280 case EVENT_TRIGGER: // handle it if it's due, else execute it regardless
1281 case EVENT_HANDLE: // handle it if it's due
1283 KDateTime now = KDateTime::currentUtcDateTime();
1284 kDebug() << eventID << "," << (function==EVENT_TRIGGER?"TRIGGER:":"HANDLE:") << qPrintable(now.dateTime().toString("yyyy-MM-dd hh:mm")) << "UTC";
1285 bool updateCalAndDisplay = false;
1286 bool alarmToExecuteValid = false;
1287 KAAlarm alarmToExecute;
1288 bool restart = false;
1289 // Check all the alarms in turn.
1290 // Note that the main alarm is fetched before any other alarms.
1291 for (KAAlarm alarm = event->firstAlarm();
1292 alarm.isValid();
1293 alarm = (restart ? event->firstAlarm() : event->nextAlarm(alarm)), restart = false)
1295 // Check if the alarm is due yet.
1296 KDateTime nextDT = alarm.dateTime(true).effectiveKDateTime();
1297 int secs = nextDT.secsTo(now);
1298 if (secs < 0)
1300 // The alarm appears to be in the future.
1301 // Check if it's an invalid local clock time during a daylight
1302 // saving time shift, which has actually passed.
1303 if (alarm.dateTime().timeSpec() != KDateTime::ClockTime
1304 || nextDT > now.toTimeSpec(KDateTime::ClockTime))
1306 // This alarm is definitely not due yet
1307 kDebug() << "Alarm" << alarm.type() << "at" << nextDT.dateTime() << ": not due";
1308 continue;
1311 bool reschedule = false;
1312 bool rescheduleWork = false;
1313 if ((event->workTimeOnly() || event->holidaysExcluded()) && !alarm.deferred())
1315 // The alarm is restricted to working hours and/or non-holidays
1316 // (apart from deferrals). This needs to be re-evaluated every
1317 // time it triggers, since working hours could change.
1318 if (alarm.dateTime().isDateOnly())
1320 KDateTime dt(nextDT);
1321 dt.setDateOnly(true);
1322 reschedule = !event->isWorkingTime(dt);
1324 else
1325 reschedule = !event->isWorkingTime(nextDT);
1326 rescheduleWork = reschedule;
1327 if (reschedule)
1328 kDebug() << "Alarm" << alarm.type() << "at" << nextDT.dateTime() << ": not during working hours";
1330 if (!reschedule && alarm.repeatAtLogin())
1332 // Alarm is to be displayed at every login.
1333 kDebug() << "REPEAT_AT_LOGIN";
1334 // Check if the main alarm is already being displayed.
1335 // (We don't want to display both at the same time.)
1336 if (alarmToExecute.isValid())
1337 continue;
1339 // Set the time to display if it's a display alarm
1340 alarm.setTime(now);
1342 if (!reschedule && event->lateCancel())
1344 // Alarm is due, and it is to be cancelled if too late.
1345 kDebug() << "LATE_CANCEL";
1346 bool cancel = false;
1347 if (alarm.dateTime().isDateOnly())
1349 // The alarm has no time, so cancel it if its date is too far past
1350 int maxlate = event->lateCancel() / 1440; // maximum lateness in days
1351 KDateTime limit(DateTime(nextDT.addDays(maxlate + 1)).effectiveKDateTime());
1352 if (now >= limit)
1354 // It's too late to display the scheduled occurrence.
1355 // Find the last previous occurrence of the alarm.
1356 DateTime next;
1357 KAEvent::OccurType type = event->previousOccurrence(now, next, true);
1358 switch (type & ~KAEvent::OCCURRENCE_REPEAT)
1360 case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
1361 case KAEvent::RECURRENCE_DATE:
1362 case KAEvent::RECURRENCE_DATE_TIME:
1363 case KAEvent::LAST_RECURRENCE:
1364 limit.setDate(next.date().addDays(maxlate + 1));
1365 if (now >= limit)
1367 if (type == KAEvent::LAST_RECURRENCE
1368 || (type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event->recurs()))
1369 cancel = true; // last occurrence (and there are no repetitions)
1370 else
1371 reschedule = true;
1373 break;
1374 case KAEvent::NO_OCCURRENCE:
1375 default:
1376 reschedule = true;
1377 break;
1381 else
1383 // The alarm is timed. Allow it to be the permitted amount late before cancelling it.
1384 int maxlate = maxLateness(event->lateCancel());
1385 if (secs > maxlate)
1387 // It's over the maximum interval late.
1388 // Find the most recent occurrence of the alarm.
1389 DateTime next;
1390 KAEvent::OccurType type = event->previousOccurrence(now, next, true);
1391 switch (type & ~KAEvent::OCCURRENCE_REPEAT)
1393 case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
1394 case KAEvent::RECURRENCE_DATE:
1395 case KAEvent::RECURRENCE_DATE_TIME:
1396 case KAEvent::LAST_RECURRENCE:
1397 if (next.effectiveKDateTime().secsTo(now) > maxlate)
1399 if (type == KAEvent::LAST_RECURRENCE
1400 || (type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event->recurs()))
1401 cancel = true; // last occurrence (and there are no repetitions)
1402 else
1403 reschedule = true;
1405 break;
1406 case KAEvent::NO_OCCURRENCE:
1407 default:
1408 reschedule = true;
1409 break;
1414 if (cancel)
1416 // All recurrences are finished, so cancel the event
1417 event->setArchive();
1418 if (cancelAlarm(*event, alarm.type(), false))
1419 return true; // event has been deleted
1420 updateCalAndDisplay = true;
1421 continue;
1424 if (reschedule)
1426 // The latest repetition was too long ago, so schedule the next one
1427 switch (rescheduleAlarm(*event, alarm, false, (rescheduleWork ? nextDT : KDateTime())))
1429 case 1:
1430 // A working-time-only alarm has been rescheduled and the
1431 // rescheduled time is already due. Start processing the
1432 // event again.
1433 alarmToExecuteValid = false;
1434 restart = true;
1435 break;
1436 case -1:
1437 return true; // event has been deleted
1438 default:
1439 break;
1441 updateCalAndDisplay = true;
1442 continue;
1444 if (!alarmToExecuteValid)
1446 kDebug() << "Alarm" << alarm.type() << ": execute";
1447 alarmToExecute = alarm; // note the alarm to be displayed
1448 alarmToExecuteValid = true; // only trigger one alarm for the event
1450 else
1451 kDebug() << "Alarm" << alarm.type() << ": skip";
1454 // If there is an alarm to execute, do this last after rescheduling/cancelling
1455 // any others. This ensures that the updated event is only saved once to the calendar.
1456 if (alarmToExecute.isValid())
1457 execAlarm(*event, alarmToExecute, true, !alarmToExecute.repeatAtLogin());
1458 else
1460 if (function == EVENT_TRIGGER)
1462 // The alarm is to be executed regardless of whether it's due.
1463 // Only trigger one alarm from the event - we don't want multiple
1464 // identical messages, for example.
1465 KAAlarm alarm = event->firstAlarm();
1466 if (alarm.isValid())
1467 execAlarm(*event, alarm, false);
1469 if (updateCalAndDisplay)
1470 KAlarm::updateEvent(*event); // update the window lists and calendar file
1471 else if (function != EVENT_TRIGGER) { kDebug() << "No action"; }
1473 break;
1476 return true;
1479 /******************************************************************************
1480 * Called when an alarm action has completed, to perform any post-alarm actions.
1482 void KAlarmApp::alarmCompleted(const KAEvent& event)
1484 if (!event.postAction().isEmpty())
1486 // doShellCommand() will error if the user is not authorised to run
1487 // shell commands.
1488 QString command = event.postAction();
1489 kDebug() << event.id() << ":" << command;
1490 doShellCommand(command, event, 0, ProcData::POST_ACTION);
1494 /******************************************************************************
1495 * Reschedule the alarm for its next recurrence after now. If none remain,
1496 * delete it. If the alarm is deleted and it is the last alarm for its event,
1497 * the event is removed from the calendar file and from every main window
1498 * instance.
1499 * If 'nextDt' is valid, the event is rescheduled for the next non-working
1500 * time occurrence after that.
1501 * Reply = 1 if 'nextDt' is valid and the rescheduled event is already due
1502 * = -1 if the event has been deleted
1503 * = 0 otherwise.
1505 int KAlarmApp::rescheduleAlarm(KAEvent& event, const KAAlarm& alarm, bool updateCalAndDisplay, const KDateTime& nextDt)
1507 kDebug() << "Alarm type:" << alarm.type();
1508 int reply = 0;
1509 bool update = false;
1510 event.startChanges();
1511 if (alarm.repeatAtLogin())
1513 // Leave an alarm which repeats at every login until its main alarm triggers
1514 if (!event.reminderActive() && event.reminderMinutes() < 0)
1516 // Executing an at-login alarm: first schedule the reminder
1517 // which occurs AFTER the main alarm.
1518 event.activateReminderAfter(KDateTime::currentUtcDateTime());
1519 update = true;
1522 else if (alarm.isReminder() || alarm.deferred())
1524 // It's a reminder alarm or an extra deferred alarm, so delete it
1525 event.removeExpiredAlarm(alarm.type());
1526 update = true;
1528 else
1530 // Reschedule the alarm for its next occurrence.
1531 bool cancelled = false;
1532 DateTime last = event.mainDateTime(false); // note this trigger time
1533 if (last != event.mainDateTime(true))
1534 last = DateTime(); // but ignore sub-repetition triggers
1535 bool next = nextDt.isValid();
1536 KDateTime next_dt = nextDt;
1537 KDateTime now = KDateTime::currentUtcDateTime();
1540 KAEvent::OccurType type = event.setNextOccurrence(next ? next_dt : now);
1541 switch (type)
1543 case KAEvent::NO_OCCURRENCE:
1544 // All repetitions are finished, so cancel the event
1545 kDebug() << "No occurrence";
1546 if (event.reminderMinutes() < 0 && last.isValid()
1547 && alarm.type() != KAAlarm::AT_LOGIN_ALARM && !event.mainExpired())
1549 // Set the reminder which is now due after the last main alarm trigger.
1550 // Note that at-login reminders are scheduled in execAlarm().
1551 event.activateReminderAfter(last);
1552 updateCalAndDisplay = true;
1554 if (cancelAlarm(event, alarm.type(), updateCalAndDisplay))
1555 return -1;
1556 break;
1557 default:
1558 if (!(type & KAEvent::OCCURRENCE_REPEAT))
1559 break;
1560 // Next occurrence is a repeat, so fall through to recurrence handling
1561 case KAEvent::RECURRENCE_DATE:
1562 case KAEvent::RECURRENCE_DATE_TIME:
1563 case KAEvent::LAST_RECURRENCE:
1564 // The event is due by now and repetitions still remain, so rewrite the event
1565 if (updateCalAndDisplay)
1566 update = true;
1567 break;
1568 case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
1569 // The first occurrence is still due?!?, so don't do anything
1570 break;
1572 if (cancelled)
1573 break;
1574 if (event.deferred())
1576 // Just in case there's also a deferred alarm, ensure it's removed
1577 event.removeExpiredAlarm(KAAlarm::DEFERRED_ALARM);
1578 update = true;
1580 if (next)
1582 // The alarm is restricted to working hours and/or non-holidays.
1583 // Check if the calculated next time is valid.
1584 next_dt = event.mainDateTime(true).effectiveKDateTime();
1585 if (event.mainDateTime(false).isDateOnly())
1587 KDateTime dt(next_dt);
1588 dt.setDateOnly(true);
1589 next = !event.isWorkingTime(dt);
1591 else
1592 next = !event.isWorkingTime(next_dt);
1594 } while (next && next_dt <= now);
1595 reply = (!cancelled && next_dt.isValid() && (next_dt <= now)) ? 1 : 0;
1597 if (event.reminderMinutes() < 0 && last.isValid()
1598 && alarm.type() != KAAlarm::AT_LOGIN_ALARM)
1600 // Set the reminder which is now due after the last main alarm trigger.
1601 // Note that at-login reminders are scheduled in execAlarm().
1602 event.activateReminderAfter(last);
1605 event.endChanges();
1606 if (update)
1607 KAlarm::updateEvent(event); // update the window lists and calendar file
1608 return reply;
1611 /******************************************************************************
1612 * Delete the alarm. If it is the last alarm for its event, the event is removed
1613 * from the calendar file and from every main window instance.
1614 * Reply = true if event has been deleted.
1616 bool KAlarmApp::cancelAlarm(KAEvent& event, KAAlarm::Type alarmType, bool updateCalAndDisplay)
1618 kDebug();
1619 if (alarmType == KAAlarm::MAIN_ALARM && !event.displaying() && event.toBeArchived())
1621 // The event is being deleted. Save it in the archived resources first.
1622 KAEvent ev(event);
1623 KAlarm::addArchivedEvent(ev);
1625 event.removeExpiredAlarm(alarmType);
1626 if (!event.alarmCount())
1628 KAlarm::deleteEvent(event, false);
1629 return true;
1631 if (updateCalAndDisplay)
1632 KAlarm::updateEvent(event); // update the window lists and calendar file
1633 return false;
1636 /******************************************************************************
1637 * Cancel any reminder or deferred alarms in an repeat-at-login event.
1638 * This should be called when the event is first loaded.
1639 * If there are no more alarms left in the event, the event is removed from the
1640 * calendar file and from every main window instance.
1641 * Reply = true if event has been deleted.
1643 bool KAlarmApp::cancelReminderAndDeferral(KAEvent& event)
1645 return cancelAlarm(event, KAAlarm::REMINDER_ALARM, false)
1646 || cancelAlarm(event, KAAlarm::DEFERRED_REMINDER_ALARM, false)
1647 || cancelAlarm(event, KAAlarm::DEFERRED_ALARM, true);
1650 /******************************************************************************
1651 * Execute an alarm by displaying its message or file, or executing its command.
1652 * Reply = ShellProcess instance if a command alarm
1653 * = MessageWin if an audio alarm
1654 * != 0 if successful
1655 * = -1 if execution has not completed
1656 * = 0 if the alarm is disabled, or if an error message was output.
1658 void* KAlarmApp::execAlarm(KAEvent& event, const KAAlarm& alarm, bool reschedule, bool allowDefer, bool noPreAction)
1660 if (!mAlarmsEnabled || !event.enabled())
1662 // The event (or all events) is disabled
1663 kDebug() << event.id() << ": disabled";
1664 if (reschedule)
1665 rescheduleAlarm(event, alarm, true);
1666 return 0;
1669 void* result = (void*)1;
1670 event.setArchive();
1672 switch (alarm.action())
1674 case KAAlarm::COMMAND:
1675 if (!event.commandDisplay())
1677 // execCommandAlarm() will error if the user is not authorised
1678 // to run shell commands.
1679 result = execCommandAlarm(event, alarm);
1680 if (reschedule)
1681 rescheduleAlarm(event, alarm, true);
1682 break;
1684 // fall through to MESSAGE
1685 case KAAlarm::MESSAGE:
1686 case KAAlarm::FILE:
1688 // Display a message, file or command output, provided that the same event
1689 // isn't already being displayed
1690 #ifdef USE_AKONADI
1691 MessageWin* win = MessageWin::findEvent(EventId(event));
1692 #else
1693 MessageWin* win = MessageWin::findEvent(event.id());
1694 #endif
1695 // Find if we're changing a reminder message to the real message
1696 bool reminder = (alarm.type() & KAAlarm::REMINDER_ALARM);
1697 bool replaceReminder = !reminder && win && (win->alarmType() & KAAlarm::REMINDER_ALARM);
1698 if (!reminder
1699 && (!event.deferred() || (event.extraActionOptions() & KAEvent::ExecPreActOnDeferral))
1700 && (replaceReminder || !win) && !noPreAction
1701 && !event.preAction().isEmpty())
1703 // It's not a reminder alarm, and it's not a deferred alarm unless the
1704 // pre-alarm action applies to deferred alarms, and there is no message
1705 // window (other than a reminder window) currently displayed for this
1706 // alarm, and we need to execute a command before displaying the new window.
1708 // NOTE: The pre-action is not executed for a recurring alarm if an
1709 // alarm message window for a previous occurrence is still visible.
1710 // Check whether the command is already being executed for this alarm.
1711 for (int i = 0, end = mCommandProcesses.count(); i < end; ++i)
1713 ProcData* pd = mCommandProcesses[i];
1714 if (pd->event->id() == event.id() && (pd->flags & ProcData::PRE_ACTION))
1716 kDebug() << "Already executing pre-DISPLAY command";
1717 return pd->process; // already executing - don't duplicate the action
1721 // doShellCommand() will error if the user is not authorised to run
1722 // shell commands.
1723 QString command = event.preAction();
1724 kDebug() << "Pre-DISPLAY command:" << command;
1725 int flags = (reschedule ? ProcData::RESCHEDULE : 0) | (allowDefer ? ProcData::ALLOW_DEFER : 0);
1726 if (doShellCommand(command, event, &alarm, (flags | ProcData::PRE_ACTION)))
1728 AlarmCalendar::resources()->setAlarmPending(&event);
1729 return result; // display the message after the command completes
1731 // Error executing command
1732 if (event.cancelOnPreActionError())
1734 // Cancel the rest of the alarm execution
1735 kDebug() << event.id() << ": pre-action failed: cancelled";
1736 if (reschedule)
1737 rescheduleAlarm(event, alarm, true);
1738 return 0;
1740 // Display the message even though it failed
1743 if (!win)
1745 // There isn't already a message for this event
1746 int flags = (reschedule ? 0 : MessageWin::NO_RESCHEDULE) | (allowDefer ? 0 : MessageWin::NO_DEFER);
1747 (new MessageWin(&event, alarm, flags))->show();
1749 else if (replaceReminder)
1751 // The caption needs to be changed from "Reminder" to "Message"
1752 win->cancelReminder(event, alarm);
1754 else if (!win->hasDefer() && !alarm.repeatAtLogin())
1756 // It's a repeat-at-login message with no Defer button,
1757 // which has now reached its final trigger time and needs
1758 // to be replaced with a new message.
1759 win->showDefer();
1760 win->showDateTime(event, alarm);
1762 else
1764 // Use the existing message window
1766 if (win)
1768 // Raise the existing message window and replay any sound
1769 win->repeat(alarm); // N.B. this reschedules the alarm
1771 break;
1773 case KAAlarm::EMAIL:
1775 kDebug() << "EMAIL to:" << event.emailAddresses(",");
1776 QStringList errmsgs;
1777 KAMail::JobData data(event, alarm, reschedule, (reschedule || allowDefer));
1778 data.queued = true;
1779 int ans = KAMail::send(data, errmsgs);
1780 if (ans)
1782 // The email has either been sent or failed - not queued
1783 if (ans < 0)
1784 result = 0; // failure
1785 data.queued = false;
1786 emailSent(data, errmsgs, (ans > 0));
1788 else
1790 result = (void*)-1; // email has been queued
1792 if (reschedule)
1793 rescheduleAlarm(event, alarm, true);
1794 break;
1796 case KAAlarm::AUDIO:
1798 // Play the sound, provided that the same event
1799 // isn't already playing
1800 #ifdef USE_AKONADI
1801 MessageWin* win = MessageWin::findEvent(EventId(event));
1802 #else
1803 MessageWin* win = MessageWin::findEvent(event.id());
1804 #endif
1805 if (!win)
1807 // There isn't already a message for this event.
1808 int flags = (reschedule ? 0 : MessageWin::NO_RESCHEDULE) | MessageWin::ALWAYS_HIDE;
1809 win = new MessageWin(&event, alarm, flags);
1811 else
1813 // There's an existing message window: replay the sound
1814 win->repeat(alarm); // N.B. this reschedules the alarm
1816 return win;
1818 default:
1819 return 0;
1821 return result;
1824 /******************************************************************************
1825 * Called when sending an email has completed.
1827 void KAlarmApp::emailSent(KAMail::JobData& data, const QStringList& errmsgs, bool copyerr)
1829 if (!errmsgs.isEmpty())
1831 // Some error occurred, although the email may have been sent successfully
1832 if (errmsgs.count() > 1)
1833 kDebug() << (copyerr ? "Copy error:" : "Failed:") << errmsgs[1];
1834 MessageWin::showError(data.event, data.alarm.dateTime(), errmsgs);
1836 else if (data.queued)
1837 emit execAlarmSuccess();
1840 /******************************************************************************
1841 * Execute the command specified in a command alarm.
1842 * To connect to the output ready signals of the process, specify a slot to be
1843 * called by supplying 'receiver' and 'slot' parameters.
1845 ShellProcess* KAlarmApp::execCommandAlarm(const KAEvent& event, const KAAlarm& alarm, const QObject* receiver, const char* slot)
1847 // doShellCommand() will error if the user is not authorised to run
1848 // shell commands.
1849 int flags = (event.commandXterm() ? ProcData::EXEC_IN_XTERM : 0)
1850 | (event.commandDisplay() ? ProcData::DISP_OUTPUT : 0);
1851 QString command = event.cleanText();
1852 if (event.commandScript())
1854 // Store the command script in a temporary file for execution
1855 kDebug() << "Script";
1856 QString tmpfile = createTempScriptFile(command, false, event, alarm);
1857 if (tmpfile.isEmpty())
1859 setEventCommandError(event, KAEvent::CMD_ERROR);
1860 return 0;
1862 return doShellCommand(tmpfile, event, &alarm, (flags | ProcData::TEMP_FILE), receiver, slot);
1864 else
1866 kDebug() << command;
1867 return doShellCommand(command, event, &alarm, flags, receiver, slot);
1871 /******************************************************************************
1872 * Execute a shell command line specified by an alarm.
1873 * If the PRE_ACTION bit of 'flags' is set, the alarm will be executed via
1874 * execAlarm() once the command completes, the execAlarm() parameters being
1875 * derived from the remaining bits in 'flags'.
1876 * 'flags' must contain the bit PRE_ACTION or POST_ACTION if and only if it is
1877 * a pre- or post-alarm action respectively.
1878 * To connect to the output ready signals of the process, specify a slot to be
1879 * called by supplying 'receiver' and 'slot' parameters.
1881 * Note that if shell access is not authorised, the attempt to run the command
1882 * will be errored.
1884 ShellProcess* KAlarmApp::doShellCommand(const QString& command, const KAEvent& event, const KAAlarm* alarm, int flags, const QObject* receiver, const char* slot)
1886 kDebug() << command << "," << event.id();
1887 QIODevice::OpenMode mode = QIODevice::WriteOnly;
1888 QString cmd;
1889 QString tmpXtermFile;
1890 if (flags & ProcData::EXEC_IN_XTERM)
1892 // Execute the command in a terminal window.
1893 cmd = composeXTermCommand(command, event, alarm, flags, tmpXtermFile);
1895 else
1897 cmd = command;
1898 mode = QIODevice::ReadWrite;
1901 ProcData* pd = 0;
1902 ShellProcess* proc = 0;
1903 if (!cmd.isEmpty())
1905 // Use ShellProcess, which automatically checks whether the user is
1906 // authorised to run shell commands.
1907 proc = new ShellProcess(cmd);
1908 proc->setEnv(QLatin1String("KALARM_UID"), event.id(), true);
1909 proc->setOutputChannelMode(KProcess::MergedChannels); // combine stdout & stderr
1910 connect(proc, SIGNAL(shellExited(ShellProcess*)), SLOT(slotCommandExited(ShellProcess*)));
1911 if ((flags & ProcData::DISP_OUTPUT) && receiver && slot)
1913 connect(proc, SIGNAL(receivedStdout(ShellProcess*)), receiver, slot);
1914 connect(proc, SIGNAL(receivedStderr(ShellProcess*)), receiver, slot);
1916 if (mode == QIODevice::ReadWrite && !event.logFile().isEmpty())
1918 // Output is to be appended to a log file.
1919 // Set up a logging process to write the command's output to.
1920 QString heading;
1921 if (alarm && alarm->dateTime().isValid())
1923 QString dateTime = alarm->dateTime().formatLocale();
1924 heading.sprintf("\n******* KAlarm %s *******\n", dateTime.toLatin1().data());
1926 else
1927 heading = QLatin1String("\n******* KAlarm *******\n");
1928 QFile logfile(event.logFile());
1929 if (logfile.open(QIODevice::Append | QIODevice::Text))
1931 QTextStream out(&logfile);
1932 out << heading;
1933 logfile.close();
1935 proc->setStandardOutputFile(event.logFile(), QIODevice::Append);
1937 pd = new ProcData(proc, new KAEvent(event), (alarm ? new KAAlarm(*alarm) : 0), flags);
1938 if (flags & ProcData::TEMP_FILE)
1939 pd->tempFiles += command;
1940 if (!tmpXtermFile.isEmpty())
1941 pd->tempFiles += tmpXtermFile;
1942 mCommandProcesses.append(pd);
1943 if (proc->start(mode))
1944 return proc;
1947 // Error executing command - report it
1948 kWarning() << "Command failed to start";
1949 commandErrorMsg(proc, event, alarm, flags);
1950 if (pd)
1952 mCommandProcesses.removeAt(mCommandProcesses.indexOf(pd));
1953 delete pd;
1955 return 0;
1958 /******************************************************************************
1959 * Compose a command line to execute the given command in a terminal window.
1960 * 'tempScriptFile' receives the name of a temporary script file which is
1961 * invoked by the command line, if applicable.
1962 * Reply = command line, or empty string if error.
1964 QString KAlarmApp::composeXTermCommand(const QString& command, const KAEvent& event, const KAAlarm* alarm, int flags, QString& tempScriptFile) const
1966 kDebug() << command << "," << event.id();
1967 tempScriptFile.clear();
1968 QString cmd = Preferences::cmdXTermCommand();
1969 cmd.replace("%t", KGlobal::mainComponent().aboutData()->programName()); // set the terminal window title
1970 if (cmd.indexOf("%C") >= 0)
1972 // Execute the command from a temporary script file
1973 if (flags & ProcData::TEMP_FILE)
1974 cmd.replace("%C", command); // the command is already calling a temporary file
1975 else
1977 tempScriptFile = createTempScriptFile(command, true, event, *alarm);
1978 if (tempScriptFile.isEmpty())
1979 return QString();
1980 cmd.replace("%C", tempScriptFile); // %C indicates where to insert the command
1983 else if (cmd.indexOf("%W") >= 0)
1985 // Execute the command from a temporary script file,
1986 // with a sleep after the command is executed
1987 tempScriptFile = createTempScriptFile(command + QLatin1String("\nsleep 86400\n"), true, event, *alarm);
1988 if (tempScriptFile.isEmpty())
1989 return QString();
1990 cmd.replace("%W", tempScriptFile); // %w indicates where to insert the command
1992 else if (cmd.indexOf("%w") >= 0)
1994 // Append a sleep to the command.
1995 // Quote the command in case it contains characters such as [>|;].
1996 QString exec = KShell::quoteArg(command + QLatin1String("; sleep 86400"));
1997 cmd.replace("%w", exec); // %w indicates where to insert the command string
1999 else
2001 // Set the command to execute.
2002 // Put it in quotes in case it contains characters such as [>|;].
2003 QString exec = KShell::quoteArg(command);
2004 if (cmd.indexOf("%c") >= 0)
2005 cmd.replace("%c", exec); // %c indicates where to insert the command string
2006 else
2007 cmd.append(exec); // otherwise, simply append the command string
2009 return cmd;
2012 /******************************************************************************
2013 * Create a temporary script file containing the specified command string.
2014 * Reply = path of temporary file, or null string if error.
2016 QString KAlarmApp::createTempScriptFile(const QString& command, bool insertShell, const KAEvent& event, const KAAlarm& alarm) const
2018 KTemporaryFile tmpFile;
2019 tmpFile.setAutoRemove(false); // don't delete file when it is destructed
2020 if (!tmpFile.open())
2021 kError() << "Unable to create a temporary script file";
2022 else
2024 tmpFile.setPermissions(QFile::ReadUser | QFile::WriteUser | QFile::ExeUser);
2025 QTextStream stream(&tmpFile);
2026 if (insertShell)
2027 stream << "#!" << ShellProcess::shellPath() << "\n";
2028 stream << command;
2029 stream.flush();
2030 if (tmpFile.error() != QFile::NoError)
2031 kError() << "Error" << tmpFile.errorString() << " writing to temporary script file";
2032 else
2033 return tmpFile.fileName();
2036 QStringList errmsgs(i18nc("@info", "Error creating temporary script file"));
2037 MessageWin::showError(event, alarm.dateTime(), errmsgs, QLatin1String("Script"));
2038 return QString();
2041 /******************************************************************************
2042 * Called when a command alarm's execution completes.
2044 void KAlarmApp::slotCommandExited(ShellProcess* proc)
2046 kDebug();
2047 // Find this command in the command list
2048 for (int i = 0, end = mCommandProcesses.count(); i < end; ++i)
2050 ProcData* pd = mCommandProcesses[i];
2051 if (pd->process == proc)
2053 // Found the command. Check its exit status.
2054 bool executeAlarm = pd->preAction();
2055 ShellProcess::Status status = proc->status();
2056 if (status == ShellProcess::SUCCESS && !proc->exitCode())
2058 kDebug() << pd->event->id() << ": SUCCESS";
2059 clearEventCommandError(*pd->event, pd->preAction() ? KAEvent::CMD_ERROR_PRE
2060 : pd->postAction() ? KAEvent::CMD_ERROR_POST
2061 : KAEvent::CMD_ERROR);
2063 else
2065 QString errmsg = proc->errorMessage();
2066 if (status == ShellProcess::SUCCESS || status == ShellProcess::NOT_FOUND)
2067 kWarning() << pd->event->id() << ":" << errmsg << "exit status =" << status << ", code =" << proc->exitCode();
2068 else
2069 kWarning() << pd->event->id() << ":" << errmsg << "exit status =" << status;
2070 if (pd->messageBoxParent)
2072 // Close the existing informational KMessageBox for this process
2073 QList<KDialog*> dialogs = pd->messageBoxParent->findChildren<KDialog*>();
2074 if (!dialogs.isEmpty())
2075 delete dialogs[0];
2076 setEventCommandError(*pd->event, pd->preAction() ? KAEvent::CMD_ERROR_PRE
2077 : pd->postAction() ? KAEvent::CMD_ERROR_POST
2078 : KAEvent::CMD_ERROR);
2079 if (!pd->tempFile())
2081 errmsg += '\n';
2082 errmsg += proc->command();
2084 KAMessageBox::error(pd->messageBoxParent, errmsg);
2086 else
2087 commandErrorMsg(proc, *pd->event, pd->alarm, pd->flags);
2089 if (executeAlarm && pd->event->cancelOnPreActionError())
2091 kDebug() << pd->event->id() << ": pre-action failed: cancelled";
2092 if (pd->reschedule())
2093 rescheduleAlarm(*pd->event, *pd->alarm, true);
2094 executeAlarm = false;
2097 if (pd->preAction())
2098 AlarmCalendar::resources()->setAlarmPending(pd->event, false);
2099 if (executeAlarm)
2100 execAlarm(*pd->event, *pd->alarm, pd->reschedule(), pd->allowDefer(), true);
2101 mCommandProcesses.removeAt(i);
2102 delete pd;
2103 break;
2107 // If there are now no executing shell commands, quit if a quit was queued
2108 if (mPendingQuit && mCommandProcesses.isEmpty())
2109 quitIf(mPendingQuitCode);
2112 /******************************************************************************
2113 * Output an error message for a shell command, and record the alarm's error status.
2115 void KAlarmApp::commandErrorMsg(const ShellProcess* proc, const KAEvent& event, const KAAlarm* alarm, int flags)
2117 KAEvent::CmdErrType cmderr;
2118 QStringList errmsgs;
2119 QString dontShowAgain;
2120 if (flags & ProcData::PRE_ACTION)
2122 if (event.dontShowPreActionError())
2123 return; // don't notify user of any errors for the alarm
2124 errmsgs += i18nc("@info", "Pre-alarm action:");
2125 dontShowAgain = QLatin1String("Pre");
2126 cmderr = KAEvent::CMD_ERROR_PRE;
2128 else if (flags & ProcData::POST_ACTION)
2130 errmsgs += i18nc("@info", "Post-alarm action:");
2131 dontShowAgain = QLatin1String("Post");
2132 cmderr = (event.commandError() == KAEvent::CMD_ERROR_PRE)
2133 ? KAEvent::CMD_ERROR_PRE_POST : KAEvent::CMD_ERROR_POST;
2135 else
2137 dontShowAgain = QLatin1String("Exec");
2138 cmderr = KAEvent::CMD_ERROR;
2141 // Record the alarm's error status
2142 setEventCommandError(event, cmderr);
2143 // Display an error message
2144 if (proc)
2146 errmsgs += proc->errorMessage();
2147 if (!(flags & ProcData::TEMP_FILE))
2148 errmsgs += proc->command();
2149 dontShowAgain += QString::number(proc->status());
2151 MessageWin::showError(event, (alarm ? alarm->dateTime() : DateTime()), errmsgs, dontShowAgain);
2154 /******************************************************************************
2155 * Notes that an informational KMessageBox is displayed for this process.
2157 void KAlarmApp::commandMessage(ShellProcess* proc, QWidget* parent)
2159 // Find this command in the command list
2160 for (int i = 0, end = mCommandProcesses.count(); i < end; ++i)
2162 ProcData* pd = mCommandProcesses[i];
2163 if (pd->process == proc)
2165 pd->messageBoxParent = parent;
2166 break;
2171 /******************************************************************************
2172 * Return a D-Bus interface object for KSpeech.
2173 * The KTTSD D-Bus service is started if necessary.
2174 * If the service cannot be started, 'error' is set to an error text.
2176 OrgKdeKSpeechInterface* KAlarmApp::kspeechInterface(QString& error) const
2178 error.clear();
2179 QDBusConnection client = QDBusConnection::sessionBus();
2180 if (!client.interface()->isServiceRegistered(KTTSD_DBUS_SERVICE))
2182 // kttsd is not running, so start it
2183 delete mKSpeech;
2184 mKSpeech = 0;
2185 if (KToolInvocation::startServiceByDesktopName(QLatin1String("kttsd"), QStringList(), &error))
2187 kDebug() << "Failed to start kttsd:" << error;
2188 return 0;
2191 if (!mKSpeech)
2193 mKSpeech = new OrgKdeKSpeechInterface(KTTSD_DBUS_SERVICE, KTTDS_DBUS_PATH, QDBusConnection::sessionBus());
2194 mKSpeech->setParent(theApp());
2195 mKSpeech->setApplicationName(KGlobal::mainComponent().aboutData()->programName());
2196 mKSpeech->setDefaultPriority(KSpeech::jpMessage);
2198 return mKSpeech;
2201 /******************************************************************************
2202 * Called when a D-Bus service unregisters.
2203 * If it's the KTTSD service, delete the KSpeech interface object.
2205 void KAlarmApp::slotDBusServiceUnregistered(const QString& serviceName)
2207 if (serviceName == KTTSD_DBUS_SERVICE)
2209 delete mKSpeech;
2210 mKSpeech = 0;
2214 /******************************************************************************
2215 * If this is the first time through, open the calendar file, and start
2216 * processing the execution queue.
2218 bool KAlarmApp::initCheck(bool calendarOnly)
2220 static bool firstTime = true;
2221 if (firstTime)
2223 kDebug() << "first time";
2225 /* Need to open the display calendar now, since otherwise if display
2226 * alarms are immediately due, they will often be processed while
2227 * MessageWin::redisplayAlarms() is executing open() (but before open()
2228 * completes), which causes problems!!
2230 AlarmCalendar::displayCalendar()->open();
2232 if (!AlarmCalendar::resources()->open())
2233 return false;
2234 setArchivePurgeDays();
2236 firstTime = false;
2239 if (!calendarOnly)
2240 startProcessQueue(); // start processing the execution queue
2241 return true;
2244 /******************************************************************************
2245 * Called when an audio thread starts or stops.
2247 void KAlarmApp::notifyAudioPlaying(bool playing)
2249 emit audioPlaying(playing);
2252 /******************************************************************************
2253 * Stop audio play.
2255 void KAlarmApp::stopAudio()
2257 MessageWin::stopAudio();
2261 void setEventCommandError(const KAEvent& event, KAEvent::CmdErrType err)
2263 if (err == KAEvent::CMD_ERROR_POST && event.commandError() == KAEvent::CMD_ERROR_PRE)
2264 err = KAEvent::CMD_ERROR_PRE_POST;
2265 event.setCommandError(err);
2266 #ifdef USE_AKONADI
2267 KAEvent* ev = AlarmCalendar::resources()->event(EventId(event));
2268 #else
2269 KAEvent* ev = AlarmCalendar::resources()->event(event.id());
2270 #endif
2271 if (ev && ev->commandError() != err)
2272 ev->setCommandError(err);
2273 #ifdef USE_AKONADI
2274 AkonadiModel::instance()->updateCommandError(event);
2275 #else
2276 EventListModel::alarms()->updateCommandError(event.id());
2277 #endif
2280 void clearEventCommandError(const KAEvent& event, KAEvent::CmdErrType err)
2282 KAEvent::CmdErrType newerr = static_cast<KAEvent::CmdErrType>(event.commandError() & ~err);
2283 event.setCommandError(newerr);
2284 #ifdef USE_AKONADI
2285 KAEvent* ev = AlarmCalendar::resources()->event(EventId(event));
2286 #else
2287 KAEvent* ev = AlarmCalendar::resources()->event(event.id());
2288 #endif
2289 if (ev)
2291 newerr = static_cast<KAEvent::CmdErrType>(ev->commandError() & ~err);
2292 ev->setCommandError(newerr);
2294 #ifdef USE_AKONADI
2295 AkonadiModel::instance()->updateCommandError(event);
2296 #else
2297 EventListModel::alarms()->updateCommandError(event.id());
2298 #endif
2302 KAlarmApp::ProcData::ProcData(ShellProcess* p, KAEvent* e, KAAlarm* a, int f)
2303 : process(p),
2304 event(e),
2305 alarm(a),
2306 messageBoxParent(0),
2307 flags(f)
2310 KAlarmApp::ProcData::~ProcData()
2312 while (!tempFiles.isEmpty())
2314 // Delete the temporary file called by the XTerm command
2315 QFile f(tempFiles.first());
2316 f.remove();
2317 tempFiles.removeFirst();
2319 delete process;
2320 delete event;
2321 delete alarm;
2324 // vim: et sw=4: