kalarm

kalarmapp.cpp

00001 /*
00002  *  kalarmapp.cpp  -  the KAlarm application object
00003  *  Program:  kalarm
00004  *  Copyright © 2001-2006 by David Jarvie <software@astrojar.org.uk>
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License along
00017  *  with this program; if not, write to the Free Software Foundation, Inc.,
00018  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019  */
00020 
00021 #include "kalarm.h"
00022 
00023 #include <stdlib.h>
00024 #include <ctype.h>
00025 #include <iostream>
00026 
00027 #include <qobjectlist.h>
00028 #include <qtimer.h>
00029 #include <qregexp.h>
00030 #include <qfile.h>
00031 
00032 #include <kcmdlineargs.h>
00033 #include <klocale.h>
00034 #include <kstandarddirs.h>
00035 #include <kconfig.h>
00036 #include <kaboutdata.h>
00037 #include <dcopclient.h>
00038 #include <kprocess.h>
00039 #include <ktempfile.h>
00040 #include <kfileitem.h>
00041 #include <kstdguiitem.h>
00042 #include <ktrader.h>
00043 #include <kstaticdeleter.h>
00044 #include <kdebug.h>
00045 
00046 #include <libkcal/calformat.h>
00047 
00048 #include <kalarmd/clientinfo.h>
00049 
00050 #include "alarmcalendar.h"
00051 #include "alarmlistview.h"
00052 #include "editdlg.h"
00053 #include "daemon.h"
00054 #include "dcophandler.h"
00055 #include "functions.h"
00056 #include "kamail.h"
00057 #include "karecurrence.h"
00058 #include "mainwindow.h"
00059 #include "messagebox.h"
00060 #include "messagewin.h"
00061 #include "preferences.h"
00062 #include "prefdlg.h"
00063 #include "shellprocess.h"
00064 #include "traywindow.h"
00065 #include "kalarmapp.moc"
00066 
00067 #include <netwm.h>
00068 
00069 
00070 static bool convWakeTime(const QCString timeParam, QDateTime&, bool& noTime);
00071 static bool convInterval(QCString timeParam, KARecurrence::Type&, int& timeInterval, bool allowMonthYear = true);
00072 
00073 /******************************************************************************
00074 * Find the maximum number of seconds late which a late-cancel alarm is allowed
00075 * to be. This is calculated as the alarm daemon's check interval, plus a few
00076 * seconds leeway to cater for any timing irregularities.
00077 */
00078 static inline int maxLateness(int lateCancel)
00079 {
00080     static const int LATENESS_LEEWAY = 5;
00081     int lc = (lateCancel >= 1) ? (lateCancel - 1)*60 : 0;
00082     return Daemon::maxTimeSinceCheck() + LATENESS_LEEWAY + lc;
00083 }
00084 
00085 
00086 KAlarmApp*  KAlarmApp::theInstance  = 0;
00087 int         KAlarmApp::mActiveCount = 0;
00088 int         KAlarmApp::mFatalError  = 0;
00089 QString     KAlarmApp::mFatalMessage;
00090 
00091 
00092 /******************************************************************************
00093 * Construct the application.
00094 */
00095 KAlarmApp::KAlarmApp()
00096     : KUniqueApplication(),
00097       mInitialised(false),
00098       mDcopHandler(new DcopHandler()),
00099 #ifdef OLD_DCOP
00100       mDcopHandlerOld(new DcopHandlerOld()),
00101 #endif
00102       mTrayWindow(0),
00103       mPendingQuit(false),
00104       mProcessingQueue(false),
00105       mCheckingSystemTray(false),
00106       mSessionClosingDown(false),
00107       mRefreshExpiredAlarms(false),
00108       mSpeechEnabled(false)
00109 {
00110     Preferences::initialise();
00111     Preferences::connect(SIGNAL(preferencesChanged()), this, SLOT(slotPreferencesChanged()));
00112     KCal::CalFormat::setApplication(aboutData()->programName(),
00113                                     QString::fromLatin1("-//K Desktop Environment//NONSGML KAlarm " KALARM_VERSION "//EN"));
00114     KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type());
00115 
00116     // Check if the system tray is supported by this window manager
00117     mHaveSystemTray = true;   // assume yes in lieu of a test which works
00118 
00119     if (AlarmCalendar::initialiseCalendars())
00120     {
00121         connect(AlarmCalendar::expiredCalendar(), SIGNAL(purged()), SLOT(slotExpiredPurged()));
00122 
00123         KConfig* config = kapp->config();
00124         config->setGroup(QString::fromLatin1("General"));
00125         mNoSystemTray           = config->readBoolEntry(QString::fromLatin1("NoSystemTray"), false);
00126         mSavedNoSystemTray      = mNoSystemTray;
00127         mOldRunInSystemTray     = wantRunInSystemTray();
00128         mDisableAlarmsIfStopped = mOldRunInSystemTray && !mNoSystemTray && Preferences::disableAlarmsIfStopped();
00129         mStartOfDay             = Preferences::startOfDay();
00130         if (Preferences::hasStartOfDayChanged())
00131             mStartOfDay.setHMS(100,0,0);    // start of day time has changed: flag it as invalid
00132         mPrefsExpiredColour   = Preferences::expiredColour();
00133         mPrefsExpiredKeepDays = Preferences::expiredKeepDays();
00134         mPrefsShowTime        = Preferences::showAlarmTime();
00135         mPrefsShowTimeTo      = Preferences::showTimeToAlarm();
00136     }
00137 
00138     // Check if the speech synthesis daemon is installed
00139     mSpeechEnabled = (KTrader::self()->query("DCOP/Text-to-Speech", "Name == 'KTTSD'").count() > 0);
00140     if (!mSpeechEnabled)
00141         kdDebug(5950) << "KAlarmApp::KAlarmApp(): speech synthesis disabled (KTTSD not found)" << endl;
00142     // Check if KOrganizer is installed
00143     QString korg = QString::fromLatin1("korganizer");
00144     mKOrganizerEnabled = !locate("exe", korg).isNull()  ||  !KStandardDirs::findExe(korg).isNull();
00145     if (!mKOrganizerEnabled)
00146         kdDebug(5950) << "KAlarmApp::KAlarmApp(): KOrganizer options disabled (KOrganizer not found)" << endl;
00147 }
00148 
00149 /******************************************************************************
00150 */
00151 KAlarmApp::~KAlarmApp()
00152 {
00153     while (!mCommandProcesses.isEmpty())
00154     {
00155         ProcData* pd = mCommandProcesses.first();
00156         mCommandProcesses.pop_front();
00157         delete pd;
00158     }
00159     AlarmCalendar::terminateCalendars();
00160 }
00161 
00162 /******************************************************************************
00163 * Return the one and only KAlarmApp instance.
00164 * If it doesn't already exist, it is created first.
00165 */
00166 KAlarmApp* KAlarmApp::getInstance()
00167 {
00168     if (!theInstance)
00169     {
00170         theInstance = new KAlarmApp;
00171 
00172         if (mFatalError)
00173             theInstance->quitFatal();
00174         else
00175         {
00176             // This is here instead of in the constructor to avoid recursion
00177             Daemon::initialise();    // calendars must be initialised before calling this
00178         }
00179     }
00180     return theInstance;
00181 }
00182 
00183 /******************************************************************************
00184 * Restore the saved session if required.
00185 */
00186 bool KAlarmApp::restoreSession()
00187 {
00188     if (!isRestored())
00189         return false;
00190     if (mFatalError)
00191     {
00192         quitFatal();
00193         return false;
00194     }
00195 
00196     // Process is being restored by session management.
00197     kdDebug(5950) << "KAlarmApp::restoreSession(): Restoring\n";
00198     ++mActiveCount;
00199     if (!initCheck(true))     // open the calendar file (needed for main windows)
00200     {
00201         --mActiveCount;
00202         quitIf(1, true);    // error opening the main calendar - quit
00203         return true;
00204     }
00205     MainWindow* trayParent = 0;
00206     for (int i = 1;  KMainWindow::canBeRestored(i);  ++i)
00207     {
00208         QString type = KMainWindow::classNameOfToplevel(i);
00209         if (type == QString::fromLatin1("MainWindow"))
00210         {
00211             MainWindow* win = MainWindow::create(true);
00212             win->restore(i, false);
00213             if (win->isHiddenTrayParent())
00214                 trayParent = win;
00215             else
00216                 win->show();
00217         }
00218         else if (type == QString::fromLatin1("MessageWin"))
00219         {
00220             MessageWin* win = new MessageWin;
00221             win->restore(i, false);
00222             if (win->isValid())
00223                 win->show();
00224             else
00225                 delete win;
00226         }
00227     }
00228     initCheck();           // register with the alarm daemon
00229 
00230     // Try to display the system tray icon if it is configured to be autostarted,
00231     // or if we're in run-in-system-tray mode.
00232     if (Preferences::autostartTrayIcon()
00233     ||  MainWindow::count()  &&  wantRunInSystemTray())
00234     {
00235         displayTrayIcon(true, trayParent);
00236         // Occasionally for no obvious reason, the main main window is
00237         // shown when it should be hidden, so hide it just to be sure.
00238         if (trayParent)
00239             trayParent->hide();
00240     }
00241 
00242     --mActiveCount;
00243     quitIf(0);           // quit if no windows are open
00244     return true;
00245 }
00246 
00247 /******************************************************************************
00248 * Called for a KUniqueApplication when a new instance of the application is
00249 * started.
00250 */
00251 int KAlarmApp::newInstance()
00252 {
00253     kdDebug(5950)<<"KAlarmApp::newInstance()\n";
00254     if (mFatalError)
00255     {
00256         quitFatal();
00257         return 1;
00258     }
00259     ++mActiveCount;
00260     int exitCode = 0;               // default = success
00261     static bool firstInstance = true;
00262     bool dontRedisplay = false;
00263     if (!firstInstance || !isRestored())
00264     {
00265         QString usage;
00266         KCmdLineArgs* args = KCmdLineArgs::parsedArgs();
00267 
00268         // Use a 'do' loop which is executed only once to allow easy error exits.
00269         // Errors use 'break' to skip to the end of the function.
00270 
00271         // Note that DCOP handling is only set up once the command line parameters
00272         // have been checked, since we mustn't register with the alarm daemon only
00273         // to quit immediately afterwards.
00274         do
00275         {
00276             #define USAGE(message)  { usage = message; break; }
00277             if (args->isSet("stop"))
00278             {
00279                 // Stop the alarm daemon
00280                 kdDebug(5950)<<"KAlarmApp::newInstance(): stop\n";
00281                 args->clear();         // free up memory
00282                 if (!Daemon::stop())
00283                 {
00284                     exitCode = 1;
00285                     break;
00286                 }
00287                 dontRedisplay = true;  // exit program if no other instances running
00288             }
00289             else
00290             if (args->isSet("reset"))
00291             {
00292                 // Reset the alarm daemon, if it's running.
00293                 // (If it's not running, it will reset automatically when it eventually starts.)
00294                 kdDebug(5950)<<"KAlarmApp::newInstance(): reset\n";
00295                 args->clear();         // free up memory
00296                 Daemon::reset();
00297                 dontRedisplay = true;  // exit program if no other instances running
00298             }
00299             else
00300             if (args->isSet("tray"))
00301             {
00302                 // Display only the system tray icon
00303                 kdDebug(5950)<<"KAlarmApp::newInstance(): tray\n";
00304                 args->clear();      // free up memory
00305                 if (!mHaveSystemTray)
00306                 {
00307                     exitCode = 1;
00308                     break;
00309                 }
00310                 if (!initCheck())   // open the calendar, register with daemon
00311                 {
00312                     exitCode = 1;
00313                     break;
00314                 }
00315                 if (!displayTrayIcon(true))
00316                 {
00317                     exitCode = 1;
00318                     break;
00319                 }
00320             }
00321             else
00322             if (args->isSet("handleEvent")  ||  args->isSet("triggerEvent")  ||  args->isSet("cancelEvent")  ||  args->isSet("calendarURL"))
00323             {
00324                 // Display or delete the event with the specified event ID
00325                 kdDebug(5950)<<"KAlarmApp::newInstance(): handle event\n";
00326                 EventFunc function = EVENT_HANDLE;
00327                 int count = 0;
00328                 const char* option = 0;
00329                 if (args->isSet("handleEvent"))   { function = EVENT_HANDLE;   option = "handleEvent";   ++count; }
00330                 if (args->isSet("triggerEvent"))  { function = EVENT_TRIGGER;  option = "triggerEvent";  ++count; }
00331                 if (args->isSet("cancelEvent"))   { function = EVENT_CANCEL;   option = "cancelEvent";   ++count; }
00332                 if (!count)
00333                     USAGE(i18n("%1 requires %2, %3 or %4").arg(QString::fromLatin1("--calendarURL")).arg(QString::fromLatin1("--handleEvent")).arg(QString::fromLatin1("--triggerEvent")).arg(QString::fromLatin1("--cancelEvent")))
00334                 if (count > 1)
00335                     USAGE(i18n("%1, %2, %3 mutually exclusive").arg(QString::fromLatin1("--handleEvent")).arg(QString::fromLatin1("--triggerEvent")).arg(QString::fromLatin1("--cancelEvent")));
00336                 if (!initCheck(true))   // open the calendar, don't register with daemon yet
00337                 {
00338                     exitCode = 1;
00339                     break;
00340                 }
00341                 if (args->isSet("calendarURL"))
00342                 {
00343                     QString calendarUrl = args->getOption("calendarURL");
00344                     if (KURL(calendarUrl).url() != AlarmCalendar::activeCalendar()->urlString())
00345                         USAGE(i18n("%1: wrong calendar file").arg(QString::fromLatin1("--calendarURL")))
00346                 }
00347                 QString eventID = args->getOption(option);
00348                 args->clear();      // free up memory
00349                 if (eventID.startsWith(QString::fromLatin1("ad:")))
00350                 {
00351                     // It's a notification from the alarm deamon
00352                     eventID = eventID.mid(3);
00353                     Daemon::queueEvent(eventID);
00354                 }
00355                 setUpDcop();        // start processing DCOP calls
00356                 if (!handleEvent(eventID, function))
00357                 {
00358                     exitCode = 1;
00359                     break;
00360                 }
00361             }
00362             else
00363             if (args->isSet("edit"))
00364             {
00365                 QString eventID = args->getOption("edit");
00366                 if (!initCheck())
00367                 {
00368                     exitCode = 1;
00369                     break;
00370                 }
00371                 if (!KAlarm::edit(eventID))
00372                 {
00373                     USAGE(i18n("%1: Event %2 not found, or not editable").arg(QString::fromLatin1("--edit")).arg(eventID))
00374                     exitCode = 1;
00375                     break;
00376                 }
00377             }
00378             else
00379             if (args->isSet("edit-new")  ||  args->isSet("edit-new-preset"))
00380             {
00381                 QString templ;
00382                 if (args->isSet("edit-new-preset"))
00383                     templ = args->getOption("edit-new-preset");
00384                 if (!initCheck())
00385                 {
00386                     exitCode = 1;
00387                     break;
00388                 }
00389                 KAlarm::editNew(templ);
00390             }
00391             else
00392             if (args->isSet("file")  ||  args->isSet("exec")  ||  args->isSet("mail")  ||  args->count())
00393             {
00394                 // Display a message or file, execute a command, or send an email
00395                 KAEvent::Action action = KAEvent::MESSAGE;
00396                 QCString         alMessage;
00397                 QCString         alFromID;
00398                 EmailAddressList alAddresses;
00399                 QStringList      alAttachments;
00400                 QCString         alSubject;
00401                 if (args->isSet("file"))
00402                 {
00403                     kdDebug(5950)<<"KAlarmApp::newInstance(): file\n";
00404                     if (args->isSet("exec"))
00405                         USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--exec")).arg(QString::fromLatin1("--file")))
00406                     if (args->isSet("mail"))
00407                         USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--mail")).arg(QString::fromLatin1("--file")))
00408                     if (args->count())
00409                         USAGE(i18n("message incompatible with %1").arg(QString::fromLatin1("--file")))
00410                     alMessage = args->getOption("file");
00411                     action = KAEvent::FILE;
00412                 }
00413                 else if (args->isSet("exec"))
00414                 {
00415                     kdDebug(5950)<<"KAlarmApp::newInstance(): exec\n";
00416                     if (args->isSet("mail"))
00417                         USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--mail")).arg(QString::fromLatin1("--exec")))
00418                     alMessage = args->getOption("exec");
00419                     int n = args->count();
00420                     for (int i = 0;  i < n;  ++i)
00421                     {
00422                         alMessage += ' ';
00423                         alMessage += args->arg(i);
00424                     }
00425                     action = KAEvent::COMMAND;
00426                 }
00427                 else if (args->isSet("mail"))
00428                 {
00429                     kdDebug(5950)<<"KAlarmApp::newInstance(): mail\n";
00430                     if (args->isSet("subject"))
00431                         alSubject = args->getOption("subject");
00432                     if (args->isSet("from-id"))
00433                         alFromID = args->getOption("from-id");
00434                     QCStringList params = args->getOptionList("mail");
00435                     for (QCStringList::Iterator i = params.begin();  i != params.end();  ++i)
00436                     {
00437                         QString addr = QString::fromLocal8Bit(*i);
00438                         if (!KAMail::checkAddress(addr))
00439                             USAGE(i18n("%1: invalid email address").arg(QString::fromLatin1("--mail")))
00440                         alAddresses += KCal::Person(QString::null, addr);
00441                     }
00442                     params = args->getOptionList("attach");
00443                     for (QCStringList::Iterator i = params.begin();  i != params.end();  ++i)
00444                         alAttachments += QString::fromLocal8Bit(*i);
00445                     alMessage = args->arg(0);
00446                     action = KAEvent::EMAIL;
00447                 }
00448                 else
00449                 {
00450                     kdDebug(5950)<<"KAlarmApp::newInstance(): message\n";
00451                     alMessage = args->arg(0);
00452                 }
00453 
00454                 if (action != KAEvent::EMAIL)
00455                 {
00456                     if (args->isSet("subject"))
00457                         USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--subject")).arg(QString::fromLatin1("--mail")))
00458                     if (args->isSet("from-id"))
00459                         USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--from-id")).arg(QString::fromLatin1("--mail")))
00460                     if (args->isSet("attach"))
00461                         USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--attach")).arg(QString::fromLatin1("--mail")))
00462                     if (args->isSet("bcc"))
00463                         USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--bcc")).arg(QString::fromLatin1("--mail")))
00464                 }
00465 
00466                 bool      alarmNoTime = false;
00467                 QDateTime alarmTime, endTime;
00468                 QColor    bgColour = Preferences::defaultBgColour();
00469                 QColor    fgColour = Preferences::defaultFgColour();
00470                 KARecurrence recurrence;
00471                 int       repeatCount    = 0;
00472                 int       repeatInterval = 0;
00473                 if (args->isSet("color"))
00474                 {
00475                     // Background colour is specified
00476                     QCString colourText = args->getOption("color");
00477                     if (static_cast<const char*>(colourText)[0] == '0'
00478                     &&  tolower(static_cast<const char*>(colourText)[1]) == 'x')
00479                         colourText.replace(0, 2, "#");
00480                     bgColour.setNamedColor(colourText);
00481                     if (!bgColour.isValid())
00482                         USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--color")))
00483                 }
00484                 if (args->isSet("colorfg"))
00485                 {
00486                     // Foreground colour is specified
00487                     QCString colourText = args->getOption("colorfg");
00488                     if (static_cast<const char*>(colourText)[0] == '0'
00489                     &&  tolower(static_cast<const char*>(colourText)[1]) == 'x')
00490                         colourText.replace(0, 2, "#");
00491                     fgColour.setNamedColor(colourText);
00492                     if (!fgColour.isValid())
00493                         USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--colorfg")))
00494                 }
00495 
00496                 if (args->isSet("time"))
00497                 {
00498                     QCString dateTime = args->getOption("time");
00499                     if (!convWakeTime(dateTime, alarmTime, alarmNoTime))
00500                         USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--time")))
00501                 }
00502                 else
00503                     alarmTime = QDateTime::currentDateTime();
00504 
00505                 bool haveRecurrence = args->isSet("recurrence");
00506                 if (haveRecurrence)
00507                 {
00508                     if (args->isSet("login"))
00509                         USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--login")).arg(QString::fromLatin1("--recurrence")))
00510                     if (args->isSet("until"))
00511                         USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--until")).arg(QString::fromLatin1("--recurrence")))
00512                     QCString rule = args->getOption("recurrence");
00513                     recurrence.set(QString::fromLocal8Bit(static_cast<const char*>(rule)));
00514                 }
00515                 if (args->isSet("interval"))
00516                 {
00517                     // Repeat count is specified
00518                     int count;
00519                     if (args->isSet("login"))
00520                         USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--login")).arg(QString::fromLatin1("--interval")))
00521                     bool ok;
00522                     if (args->isSet("repeat"))
00523                     {
00524                         count = args->getOption("repeat").toInt(&ok);
00525                         if (!ok || !count || count < -1 || (count < 0 && haveRecurrence))
00526                             USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--repeat")))
00527                     }
00528                     else if (haveRecurrence)
00529                         USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--interval")).arg(QString::fromLatin1("--repeat")))
00530                     else if (args->isSet("until"))
00531                     {
00532                         count = 0;
00533                         QCString dateTime = args->getOption("until");
00534                         if (!convWakeTime(dateTime, endTime, alarmNoTime))
00535                             USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--until")))
00536                         if (endTime < alarmTime)
00537                             USAGE(i18n("%1 earlier than %2").arg(QString::fromLatin1("--until")).arg(QString::fromLatin1("--time")))
00538                     }
00539                     else
00540                         count = -1;
00541 
00542                     // Get the recurrence interval
00543                     int interval;
00544                     KARecurrence::Type recurType;
00545                     if (!convInterval(args->getOption("interval"), recurType, interval, !haveRecurrence)
00546                     ||  interval < 0)
00547                         USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--interval")))
00548                     if (alarmNoTime  &&  recurType == KARecurrence::MINUTELY)
00549                         USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(QString::fromLatin1("--interval")))
00550 
00551                     if (haveRecurrence)
00552                     {
00553                         // There is a also a recurrence specified, so set up a simple repetition
00554                         int longestInterval = recurrence.longestInterval();
00555                         if (count * interval > longestInterval)
00556                             USAGE(i18n("Invalid %1 and %2 parameters: repetition is longer than %3 interval").arg(QString::fromLatin1("--interval")).arg(QString::fromLatin1("--repeat")).arg(QString::fromLatin1("--recurrence")));
00557                         repeatCount    = count;
00558                         repeatInterval = interval;
00559                     }
00560                     else
00561                     {
00562                         // There is no other recurrence specified, so convert the repetition
00563                         // parameters into a KCal::Recurrence
00564                         recurrence.set(recurType, interval, count, DateTime(alarmTime, alarmNoTime), endTime);
00565                     }
00566                 }
00567                 else
00568                 {
00569                     if (args->isSet("repeat"))
00570                         USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--repeat")).arg(QString::fromLatin1("--interval")))
00571                     if (args->isSet("until"))
00572                         USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--until")).arg(QString::fromLatin1("--interval")))
00573                 }
00574 
00575                 QCString audioFile;
00576                 float    audioVolume = -1;
00577 #ifdef WITHOUT_ARTS
00578                 bool     audioRepeat = false;
00579 #else
00580                 bool     audioRepeat = args->isSet("play-repeat");
00581 #endif
00582                 if (audioRepeat  ||  args->isSet("play"))
00583                 {
00584                     // Play a sound with the alarm
00585                     if (audioRepeat  &&  args->isSet("play"))
00586                         USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--play")).arg(QString::fromLatin1("--play-repeat")))
00587                     if (args->isSet("beep"))
00588                         USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--beep")).arg(QString::fromLatin1(audioRepeat ? "--play-repeat" : "--play")))
00589                     if (args->isSet("speak"))
00590                         USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--speak")).arg(QString::fromLatin1(audioRepeat ? "--play-repeat" : "--play")))
00591                     audioFile = args->getOption(audioRepeat ? "play-repeat" : "play");
00592 #ifndef WITHOUT_ARTS
00593                     if (args->isSet("volume"))
00594                     {
00595                         bool ok;
00596                         int volumepc = args->getOption("volume").toInt(&ok);
00597                         if (!ok  ||  volumepc < 0  ||  volumepc > 100)
00598                             USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--volume")))
00599                         audioVolume = static_cast<float>(volumepc) / 100;
00600                     }
00601 #endif
00602                 }
00603 #ifndef WITHOUT_ARTS
00604                 else if (args->isSet("volume"))
00605                     USAGE(i18n("%1 requires %2 or %3").arg(QString::fromLatin1("--volume")).arg(QString::fromLatin1("--play")).arg(QString::fromLatin1("--play-repeat")))
00606 #endif
00607                 if (args->isSet("speak"))
00608                 {
00609                     if (args->isSet("beep"))
00610                         USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--beep")).arg(QString::fromLatin1("--speak")))
00611                     if (!mSpeechEnabled)
00612                         USAGE(i18n("%1 requires speech synthesis to be configured using KTTSD").arg(QString::fromLatin1("--speak")))
00613                 }
00614                 int reminderMinutes = 0;
00615                 bool onceOnly = args->isSet("reminder-once");
00616                 if (args->isSet("reminder")  ||  onceOnly)
00617                 {
00618                     // Issue a reminder alarm in advance of the main alarm
00619                     if (onceOnly  &&  args->isSet("reminder"))
00620                         USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--reminder")).arg(QString::fromLatin1("--reminder-once")))
00621                     QString opt = onceOnly ? QString::fromLatin1("--reminder-once") : QString::fromLatin1("--reminder");
00622                     if (args->isSet("exec"))
00623                         USAGE(i18n("%1 incompatible with %2").arg(opt).arg(QString::fromLatin1("--exec")))
00624                     if (args->isSet("mail"))
00625                         USAGE(i18n("%1 incompatible with %2").arg(opt).arg(QString::fromLatin1("--mail")))
00626                     KARecurrence::Type recurType;
00627                     QString optval = args->getOption(onceOnly ? "reminder-once" : "reminder");
00628                     bool ok = convInterval(args->getOption(onceOnly ? "reminder-once" : "reminder"), recurType, reminderMinutes);
00629                     if (ok)
00630                     {
00631                         switch (recurType)
00632                         {
00633                             case KARecurrence::MINUTELY:
00634                                 if (alarmNoTime)
00635                                     USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(opt))
00636                                 break;
00637                             case KARecurrence::DAILY:     reminderMinutes *= 1440;  break;
00638                             case KARecurrence::WEEKLY:    reminderMinutes *= 7*1440;  break;
00639                             default:   ok = false;  break;
00640                         }
00641                     }
00642                     if (!ok)
00643                         USAGE(i18n("Invalid %1 parameter").arg(opt))
00644                 }
00645 
00646                 int lateCancel = 0;
00647                 if (args->isSet("late-cancel"))
00648                 {
00649                     KARecurrence::Type recurType;
00650                     bool ok = convInterval(args->getOption("late-cancel"), recurType, lateCancel, false);
00651                     if (!ok  ||  lateCancel <= 0)
00652                         USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("late-cancel")))
00653                 }
00654                 else if (args->isSet("auto-close"))
00655                     USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--auto-close")).arg(QString::fromLatin1("--late-cancel")))
00656 
00657                 int flags = KAEvent::DEFAULT_FONT;
00658                 if (args->isSet("ack-confirm"))
00659                     flags |= KAEvent::CONFIRM_ACK;
00660                 if (args->isSet("auto-close"))
00661                     flags |= KAEvent::AUTO_CLOSE;
00662                 if (args->isSet("beep"))
00663                     flags |= KAEvent::BEEP;
00664                 if (args->isSet("speak"))
00665                     flags |= KAEvent::SPEAK;
00666                 if (args->isSet("korganizer"))
00667                     flags |= KAEvent::COPY_KORGANIZER;
00668                 if (args->isSet("disable"))
00669                     flags |= KAEvent::DISABLED;
00670                 if (audioRepeat)
00671                     flags |= KAEvent::REPEAT_SOUND;
00672                 if (args->isSet("login"))
00673                     flags |= KAEvent::REPEAT_AT_LOGIN;
00674                 if (args->isSet("bcc"))
00675                     flags |= KAEvent::EMAIL_BCC;
00676                 if (alarmNoTime)
00677                     flags |= KAEvent::ANY_TIME;
00678                 args->clear();      // free up memory
00679 
00680                 // Display or schedule the event
00681                 if (!initCheck())
00682                 {
00683                     exitCode = 1;
00684                     break;
00685                 }
00686                 if (!scheduleEvent(action, alMessage, alarmTime, lateCancel, flags, bgColour, fgColour, QFont(), audioFile,
00687                                    audioVolume, reminderMinutes, recurrence, repeatInterval, repeatCount,
00688                                    alFromID, alAddresses, alSubject, alAttachments))
00689                 {
00690                     exitCode = 1;
00691                     break;
00692                 }
00693             }
00694             else
00695             {
00696                 // No arguments - run interactively & display the main window
00697                 kdDebug(5950)<<"KAlarmApp::newInstance(): interactive\n";
00698                 if (args->isSet("ack-confirm"))
00699                     usage += QString::fromLatin1("--ack-confirm ");
00700                 if (args->isSet("attach"))
00701                     usage += QString::fromLatin1("--attach ");
00702                 if (args->isSet("auto-close"))
00703                     usage += QString::fromLatin1("--auto-close ");
00704                 if (args->isSet("bcc"))
00705                     usage += QString::fromLatin1("--bcc ");
00706                 if (args->isSet("beep"))
00707                     usage += QString::fromLatin1("--beep ");
00708                 if (args->isSet("color"))
00709                     usage += QString::fromLatin1("--color ");
00710                 if (args->isSet("colorfg"))
00711                     usage += QString::fromLatin1("--colorfg ");
00712                 if (args->isSet("disable"))
00713                     usage += QString::fromLatin1("--disable ");
00714                 if (args->isSet("from-id"))
00715                     usage += QString::fromLatin1("--from-id ");
00716                 if (args->isSet("korganizer"))
00717                     usage += QString::fromLatin1("--korganizer ");
00718                 if (args->isSet("late-cancel"))
00719                     usage += QString::fromLatin1("--late-cancel ");
00720                 if (args->isSet("login"))
00721                     usage += QString::fromLatin1("--login ");
00722                 if (args->isSet("play"))
00723                     usage += QString::fromLatin1("--play ");
00724 #ifndef WITHOUT_ARTS
00725                 if (args->isSet("play-repeat"))
00726                     usage += QString::fromLatin1("--play-repeat ");
00727 #endif
00728                 if (args->isSet("reminder"))
00729                     usage += QString::fromLatin1("--reminder ");
00730                 if (args->isSet("reminder-once"))
00731                     usage += QString::fromLatin1("--reminder-once ");
00732                 if (args->isSet("speak"))
00733                     usage += QString::fromLatin1("--speak ");
00734                 if (args->isSet("subject"))
00735                     usage += QString::fromLatin1("--subject ");
00736                 if (args->isSet("time"))
00737                     usage += QString::fromLatin1("--time ");
00738 #ifndef WITHOUT_ARTS
00739                 if (args->isSet("volume"))
00740                     usage += QString::fromLatin1("--volume ");
00741 #endif
00742                 if (!usage.isEmpty())
00743                 {
00744                     usage += i18n(": option(s) only valid with a message/%1/%2").arg(QString::fromLatin1("--file")).arg(QString::fromLatin1("--exec"));
00745                     break;
00746                 }
00747 
00748                 args->clear();      // free up memory
00749                 /* Instead of calling initCheck (), call initCheck (true) to check the calendar only.
00750                  * This works because it doesn't change the return value:
00751                  * If the return value is false, it doesn't matter because we exit.
00752                  * If the return value is true, the first thing MainWindow() does, is to call initCheck(), so we do the same tests. 
00753                  *              Jonas Wustrack - jwustrack@mandriva.com                        */
00754                 if (!initCheck(true))
00755                 {
00756                     exitCode = 1;
00757                     break;
00758                 }
00759 
00760                 (MainWindow::create())->show();
00761             }
00762         } while (0);    // only execute once
00763 
00764         if (!usage.isEmpty())
00765         {
00766             // Note: we can't use args->usage() since that also quits any other
00767             // running 'instances' of the program.
00768             std::cerr << usage.local8Bit().data()
00769                       << i18n("\nUse --help to get a list of available command line options.\n").local8Bit().data();
00770             exitCode = 1;
00771         }
00772     }
00773     if (firstInstance  &&  !dontRedisplay  &&  !exitCode)
00774         redisplayAlarms();
00775 
00776     --mActiveCount;
00777     firstInstance = false;
00778 
00779     // Quit the application if this was the last/only running "instance" of the program.
00780     // Executing 'return' doesn't work very well since the program continues to
00781     // run if no windows were created.
00782     quitIf(exitCode);
00783     return exitCode;
00784 }
00785 
00786 /******************************************************************************
00787 * Quit the program, optionally only if there are no more "instances" running.
00788 */
00789 void KAlarmApp::quitIf(int exitCode, bool force)
00790 {
00791     if (force)
00792     {
00793         // Quit regardless, except for message windows
00794         MainWindow::closeAll();
00795         displayTrayIcon(false);
00796         if (MessageWin::instanceCount())
00797             return;
00798     }
00799     else
00800     {
00801         // Quit only if there are no more "instances" running
00802         mPendingQuit = false;
00803         if (mActiveCount > 0  ||  MessageWin::instanceCount())
00804             return;
00805         int mwcount = MainWindow::count();
00806         MainWindow* mw = mwcount ? MainWindow::firstWindow() : 0;
00807         if (mwcount > 1  ||  mwcount && (!mw->isHidden() || !mw->isTrayParent()))
00808             return;
00809         // There are no windows left except perhaps a main window which is a hidden tray icon parent
00810         if (mTrayWindow)
00811         {
00812             // There is a system tray icon.
00813             // Don't exit unless the system tray doesn't seem to exist.
00814             if (checkSystemTray())
00815                 return;
00816         }
00817         if (!mDcopQueue.isEmpty()  ||  !mCommandProcesses.isEmpty())
00818         {
00819             // Don't quit yet if there are outstanding actions on the DCOP queue
00820             mPendingQuit = true;
00821             mPendingQuitCode = exitCode;
00822             return;
00823         }
00824     }
00825 
00826     // This was the last/only running "instance" of the program, so exit completely.
00827     kdDebug(5950) << "KAlarmApp::quitIf(" << exitCode << "): quitting" << endl;
00828     exit(exitCode);
00829 }
00830 
00831 /******************************************************************************
00832 * Called when the Quit menu item is selected.
00833 * Closes the system tray window and all main windows, but does not exit the
00834 * program if other windows are still open.
00835 */
00836 void KAlarmApp::doQuit(QWidget* parent)
00837 {
00838     kdDebug(5950) << "KAlarmApp::doQuit()\n";
00839     if (mDisableAlarmsIfStopped
00840     &&  MessageBox::warningContinueCancel(parent, KMessageBox::Cancel,
00841                                           i18n("Quitting will disable alarms\n(once any alarm message windows are closed)."),
00842                                           QString::null, KStdGuiItem::quit(), Preferences::QUIT_WARN
00843                                          ) != KMessageBox::Yes)
00844         return;
00845     quitIf(0, true);
00846 }
00847 
00848 /******************************************************************************
00849 * Called when the session manager is about to close down the application.
00850 */
00851 void KAlarmApp::commitData(QSessionManager& sm)
00852 {
00853     mSessionClosingDown = true;
00854     KUniqueApplication::commitData(sm);
00855     mSessionClosingDown = false;         // reset in case shutdown is cancelled
00856 }
00857 
00858 /******************************************************************************
00859 * Display an error message for a fatal error. Prevent further actions since
00860 * the program state is unsafe.
00861 */
00862 void KAlarmApp::displayFatalError(const QString& message)
00863 {
00864     if (!mFatalError)
00865     {
00866         mFatalError = 1;
00867         mFatalMessage = message;
00868         if (theInstance)
00869             QTimer::singleShot(0, theInstance, SLOT(quitFatal()));
00870     }
00871 }
00872 
00873 /******************************************************************************
00874 * Quit the program, once the fatal error message has been acknowledged.
00875 */
00876 void KAlarmApp::quitFatal()
00877 {
00878     switch (mFatalError)
00879     {
00880         case 0:
00881         case 2:
00882             return;
00883         case 1:
00884             mFatalError = 2;
00885             KMessageBox::error(0, mFatalMessage);
00886             mFatalError = 3;
00887             // fall through to '3'
00888         case 3:
00889             if (theInstance)
00890                 theInstance->quitIf(1, true);
00891             break;
00892     }
00893     QTimer::singleShot(1000, this, SLOT(quitFatal()));
00894 }
00895 
00896 /******************************************************************************
00897 * The main processing loop for KAlarm.
00898 * All KAlarm operations involving opening or updating calendar files are called
00899 * from this loop to ensure that only one operation is active at any one time.
00900 * This precaution is necessary because KAlarm's activities are mostly
00901 * asynchronous, being in response to DCOP calls from the alarm daemon (or other
00902 * programs) or timer events, any of which can be received in the middle of
00903 * performing another operation. If a calendar file is opened or updated while
00904 * another calendar operation is in progress, the program has been observed to
00905 * hang, or the first calendar call has failed with data loss - clearly
00906 * unacceptable!!
00907 */
00908 void KAlarmApp::processQueue()
00909 {
00910     if (mInitialised  &&  !mProcessingQueue)
00911     {
00912         kdDebug(5950) << "KAlarmApp::processQueue()\n";
00913         mProcessingQueue = true;
00914 
00915         // Reset the alarm daemon if it's been queued
00916         KAlarm::resetDaemonIfQueued();
00917 
00918         // Process DCOP calls
00919         while (!mDcopQueue.isEmpty())
00920         {
00921             DcopQEntry& entry = mDcopQueue.first();
00922             if (entry.eventId.isEmpty())
00923             {
00924                 // It's a new alarm
00925                 switch (entry.function)
00926                 {
00927                 case EVENT_TRIGGER:
00928                     execAlarm(entry.event, entry.event.firstAlarm(), false);
00929                     break;
00930                 case EVENT_HANDLE:
00931                     KAlarm::addEvent(entry.event, 0);
00932                     break;
00933                 case EVENT_CANCEL:
00934                     break;
00935                 }
00936             }
00937             else
00938                 handleEvent(entry.eventId, entry.function);
00939             mDcopQueue.pop_front();
00940         }
00941 
00942         // Purge the expired alarms calendar if it's time to do so
00943         AlarmCalendar::expiredCalendar()->purgeIfQueued();
00944 
00945         // Now that the queue has been processed, quit if a quit was queued
00946         if (mPendingQuit)
00947             quitIf(mPendingQuitCode);
00948 
00949         mProcessingQueue = false;
00950     }
00951 }
00952 
00953 /******************************************************************************
00954 *  Redisplay alarms which were being shown when the program last exited.
00955 *  Normally, these alarms will have been displayed by session restoration, but
00956 *  if the program crashed or was killed, we can redisplay them here so that
00957 *  they won't be lost.
00958 */
00959 void KAlarmApp::redisplayAlarms()
00960 {
00961     AlarmCalendar* cal = AlarmCalendar::displayCalendar();
00962     if (cal->isOpen())
00963     {
00964         KCal::Event::List events = cal->events();
00965         for (KCal::Event::List::ConstIterator it = events.begin();  it != events.end();  ++it)
00966         {
00967                         KCal::Event* kcalEvent = *it;
00968             KAEvent event(*kcalEvent);
00969             event.setUid(KAEvent::ACTIVE);
00970             if (!MessageWin::findEvent(event.id()))
00971             {
00972                 // This event should be displayed, but currently isn't being
00973                 kdDebug(5950) << "KAlarmApp::redisplayAlarms(): " << event.id() << endl;
00974                 KAAlarm alarm = event.convertDisplayingAlarm();
00975                 (new MessageWin(event, alarm, false, !alarm.repeatAtLogin()))->show();
00976             }
00977         }
00978     }
00979 }
00980 
00981 /******************************************************************************
00982 * Called when the system tray main window is closed.
00983 */
00984 void KAlarmApp::removeWindow(TrayWindow*)
00985 {
00986     mTrayWindow = 0;
00987     quitIf();
00988 }
00989 
00990 /******************************************************************************
00991 *  Display or close the system tray icon.
00992 */
00993 bool KAlarmApp::displayTrayIcon(bool show, MainWindow* parent)
00994 {
00995     static bool creating = false;
00996     if (show)
00997     {
00998         if (!mTrayWindow  &&  !creating)
00999         {
01000             if (!mHaveSystemTray)
01001                 return false;
01002             if (!MainWindow::count()  &&  wantRunInSystemTray())
01003             {
01004                 creating = true;    // prevent main window constructor from creating an additional tray icon
01005                 parent = MainWindow::create();
01006                 creating = false;
01007             }
01008             mTrayWindow = new TrayWindow(parent ? parent : MainWindow::firstWindow());
01009             connect(mTrayWindow, SIGNAL(deleted()), SIGNAL(trayIconToggled()));
01010             mTrayWindow->show();
01011             emit trayIconToggled();
01012 
01013             // Set up a timer so that we can check after all events in the window system's
01014             // event queue have been processed, whether the system tray actually exists
01015             mCheckingSystemTray = true;
01016             mSavedNoSystemTray  = mNoSystemTray;
01017             mNoSystemTray       = false;
01018             QTimer::singleShot(0, this, SLOT(slotSystemTrayTimer()));
01019         }
01020     }
01021     else if (mTrayWindow)
01022     {
01023         delete mTrayWindow;
01024         mTrayWindow = 0;
01025     }
01026     return true;
01027 }
01028 
01029 /******************************************************************************
01030 *  Called by a timer to check whether the system tray icon has been housed in
01031 *  the system tray. Because there is a delay between the system tray icon show
01032 *  event and the icon being reparented by the system tray, we have to use a
01033 *  timer to check whether the system tray has actually grabbed it, or whether
01034 *  the system tray probably doesn't exist.
01035 */
01036 void KAlarmApp::slotSystemTrayTimer()
01037 {
01038     mCheckingSystemTray = false;
01039     if (!checkSystemTray())
01040         quitIf(0);    // exit the application if there are no open windows
01041 }
01042 
01043 /******************************************************************************
01044 *  Check whether the system tray icon has been housed in the system tray.
01045 *  If the system tray doesn't seem to exist, tell the alarm daemon to notify us
01046 *  of alarms regardless of whether we're running.
01047 */
01048 bool KAlarmApp::checkSystemTray()
01049 {
01050     if (mCheckingSystemTray  ||  !mTrayWindow)
01051         return true;
01052     if (mTrayWindow->inSystemTray() != !mSavedNoSystemTray)
01053     {
01054         kdDebug(5950) << "KAlarmApp::checkSystemTray(): changed -> " << mSavedNoSystemTray << endl;
01055         mNoSystemTray = mSavedNoSystemTray = !mSavedNoSystemTray;
01056 
01057         // Store the new setting in the config file, so that if KAlarm exits and is then
01058         // next activated by the daemon to display a message, it will register with the
01059         // daemon with the correct NOTIFY type. If that happened when there was no system
01060         // tray and alarms are disabled when KAlarm is not running, registering with
01061         // NO_START_NOTIFY could result in alarms never being seen.
01062         KConfig* config = kapp->config();
01063         config->setGroup(QString::fromLatin1("General"));
01064         config->writeEntry(QString::fromLatin1("NoSystemTray"), mNoSystemTray);
01065         config->sync();
01066 
01067         // Update other settings and reregister with the alarm daemon
01068         slotPreferencesChanged();
01069     }
01070     else
01071     {
01072         kdDebug(5950) << "KAlarmApp::checkSystemTray(): no change = " << !mSavedNoSystemTray << endl;
01073         mNoSystemTray = mSavedNoSystemTray;
01074     }
01075     return !mNoSystemTray;
01076 }
01077 
01078 /******************************************************************************
01079 * Return the main window associated with the system tray icon.
01080 */
01081 MainWindow* KAlarmApp::trayMainWindow() const
01082 {
01083     return mTrayWindow ? mTrayWindow->assocMainWindow() : 0;
01084 }
01085 
01086 /******************************************************************************
01087 *  Called when KAlarm preferences have changed.
01088 */
01089 void KAlarmApp::slotPreferencesChanged()
01090 {
01091     bool newRunInSysTray = wantRunInSystemTray();
01092     if (newRunInSysTray != mOldRunInSystemTray)
01093     {
01094         // The system tray run mode has changed
01095         ++mActiveCount;         // prevent the application from quitting
01096         MainWindow* win = mTrayWindow ? mTrayWindow->assocMainWindow() : 0;
01097         delete mTrayWindow;     // remove the system tray icon if it is currently shown
01098         mTrayWindow = 0;
01099         mOldRunInSystemTray = newRunInSysTray;
01100         if (!newRunInSysTray)
01101         {
01102             if (win  &&  win->isHidden())
01103                 delete win;
01104         }
01105         displayTrayIcon(true);
01106         --mActiveCount;
01107     }
01108 
01109     bool newDisableIfStopped = wantRunInSystemTray() && !mNoSystemTray && Preferences::disableAlarmsIfStopped();
01110     if (newDisableIfStopped != mDisableAlarmsIfStopped)
01111     {
01112         mDisableAlarmsIfStopped = newDisableIfStopped;    // N.B. this setting is used by Daemon::reregister()
01113         Preferences::setQuitWarn(true);   // since mode has changed, re-allow warning messages on Quit
01114         Daemon::reregister();           // re-register with the alarm daemon
01115     }
01116 
01117     // Change alarm times for date-only alarms if the start of day time has changed
01118     if (Preferences::startOfDay() != mStartOfDay)
01119         changeStartOfDay();
01120 
01121     // In case the date for February 29th recurrences has changed
01122     KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type());
01123 
01124     if (Preferences::showAlarmTime()   != mPrefsShowTime
01125     ||  Preferences::showTimeToAlarm() != mPrefsShowTimeTo)
01126     {
01127         // The default alarm list time columns selection has changed
01128         MainWindow::updateTimeColumns(mPrefsShowTime, mPrefsShowTimeTo);
01129         mPrefsShowTime   = Preferences::showAlarmTime();
01130         mPrefsShowTimeTo = Preferences::showTimeToAlarm();
01131     }
01132 
01133     if (Preferences::expiredColour() != mPrefsExpiredColour)
01134     {
01135         // The expired alarms text colour has changed
01136         mRefreshExpiredAlarms = true;
01137         mPrefsExpiredColour = Preferences::expiredColour();
01138     }
01139 
01140     if (Preferences::expiredKeepDays() != mPrefsExpiredKeepDays)
01141     {
01142         // How long expired alarms are being kept has changed.
01143         // N.B. This also adjusts for any change in start-of-day time.
01144         mPrefsExpiredKeepDays = Preferences::expiredKeepDays();
01145         AlarmCalendar::expiredCalendar()->setPurgeDays(mPrefsExpiredKeepDays);
01146     }
01147 
01148     if (mRefreshExpiredAlarms)
01149     {
01150         mRefreshExpiredAlarms = false;
01151         MainWindow::updateExpired();
01152     }
01153 }
01154 
01155 /******************************************************************************
01156 *  Change alarm times for date-only alarms after the start of day time has changed.
01157 */
01158 void KAlarmApp::changeStartOfDay()
01159 {
01160     QTime sod = Preferences::startOfDay();
01161     DateTime::setStartOfDay(sod);
01162     AlarmCalendar* cal = AlarmCalendar::activeCalendar();
01163     if (KAEvent::adjustStartOfDay(cal->events()))
01164         cal->save();
01165     Preferences::updateStartOfDayCheck();  // now that calendar is updated, set OK flag in config file
01166     mStartOfDay = sod;
01167 }
01168 
01169 /******************************************************************************
01170 *  Called when the expired alarms calendar has been purged.
01171 *  Updates the alarm list in all main windows.
01172 */
01173 void KAlarmApp::slotExpiredPurged()
01174 {
01175     mRefreshExpiredAlarms = false;
01176     MainWindow::updateExpired();
01177 }
01178 
01179 /******************************************************************************
01180 *  Return whether the program is configured to be running in the system tray.
01181 */
01182 bool KAlarmApp::wantRunInSystemTray() const
01183 {
01184     return Preferences::runInSystemTray()  &&  mHaveSystemTray;
01185 }
01186 
01187 /******************************************************************************
01188 * Called to schedule a new alarm, either in response to a DCOP notification or
01189 * to command line options.
01190 * Reply = true unless there was a parameter error or an error opening calendar file.
01191 */
01192 bool KAlarmApp::scheduleEvent(KAEvent::Action action, const QString& text, const QDateTime& dateTime,
01193                               int lateCancel, int flags, const QColor& bg, const QColor& fg, const QFont& font,
01194                               const QString& audioFile, float audioVolume, int reminderMinutes,
01195                               const KARecurrence& recurrence, int repeatInterval, int repeatCount,
01196                               const QString& mailFromID, const EmailAddressList& mailAddresses,
01197                               const QString& mailSubject, const QStringList& mailAttachments)
01198 {
01199     kdDebug(5950) << "KAlarmApp::scheduleEvent(): " << text << endl;
01200     if (!dateTime.isValid())
01201         return false;
01202     QDateTime now = QDateTime::currentDateTime();
01203     if (lateCancel  &&  dateTime < now.addSecs(-maxLateness(lateCancel)))
01204         return true;               // alarm time was already expired too long ago
01205     QDateTime alarmTime = dateTime;
01206     // Round down to the nearest minute to avoid scheduling being messed up
01207     alarmTime.setTime(QTime(alarmTime.time().hour(), alarmTime.time().minute(), 0));
01208 
01209     KAEvent event(alarmTime, text, bg, fg, font, action, lateCancel, flags);
01210     if (reminderMinutes)
01211     {
01212         bool onceOnly = (reminderMinutes < 0);
01213         event.setReminder((onceOnly ? -reminderMinutes : reminderMinutes), onceOnly);
01214     }
01215     if (!audioFile.isEmpty())
01216         event.setAudioFile(audioFile, audioVolume, -1, 0);
01217     if (!mailAddresses.isEmpty())
01218         event.setEmail(mailFromID, mailAddresses, mailSubject, mailAttachments);
01219     event.setRecurrence(recurrence);
01220     event.setFirstRecurrence();
01221     event.setRepetition(repeatInterval, repeatCount - 1);
01222     if (alarmTime <= now)
01223     {
01224         // Alarm is due for display already.
01225         // First execute it once without adding it to the calendar file.
01226         if (!mInitialised)
01227             mDcopQueue.append(DcopQEntry(event, EVENT_TRIGGER));
01228         else
01229             execAlarm(event, event.firstAlarm(), false);
01230         // If it's a recurring alarm, reschedule it for its next occurrence
01231         if (!event.recurs()
01232         ||  event.setNextOccurrence(now, true) == KAEvent::NO_OCCURRENCE)
01233             return true;
01234         // It has recurrences in the future
01235     }
01236 
01237     // Queue the alarm for insertion into the calendar file
01238     mDcopQueue.append(DcopQEntry(event));
01239     if (mInitialised)
01240         QTimer::singleShot(0, this, SLOT(processQueue()));
01241     return true;
01242 }
01243 
01244 /******************************************************************************
01245 * Called in response to a DCOP notification by the alarm daemon that an event
01246 * should be handled, i.e. displayed or cancelled.
01247 * Optionally display the event. Delete the event from the calendar file and
01248 * from every main window instance.
01249 */
01250 bool KAlarmApp::handleEvent(const QString& urlString, const QString& eventID, EventFunc function)
01251 {
01252     kdDebug(5950) << "KAlarmApp::handleEvent(DCOP): " << eventID << endl;
01253     AlarmCalendar* cal = AlarmCalendar::activeCalendar();     // this can be called before calendars have been initialised
01254     if (cal  &&  KURL(urlString).url() != cal->urlString())
01255     {
01256         kdError(5950) << "KAlarmApp::handleEvent(DCOP): wrong calendar file " << urlString << endl;
01257         Daemon::eventHandled(eventID, false);
01258         return false;
01259     }
01260     mDcopQueue.append(DcopQEntry(function, eventID));
01261     if (mInitialised)
01262         QTimer::singleShot(0, this, SLOT(processQueue()));
01263     return true;
01264 }
01265 
01266 /******************************************************************************
01267 * Either:
01268 * a) Display the event and then delete it if it has no outstanding repetitions.
01269 * b) Delete the event.
01270 * c) Reschedule the event for its next repetition. If none remain, delete it.
01271 * If the event is deleted, it is removed from the calendar file and from every
01272 * main window instance.
01273 */
01274 bool KAlarmApp::handleEvent(const QString& eventID, EventFunc function)
01275 {
01276     kdDebug(5950) << "KAlarmApp::handleEvent(): " << eventID << ", " << (function==EVENT_TRIGGER?"TRIGGER":function==EVENT_CANCEL?"CANCEL":function==EVENT_HANDLE?"HANDLE":"?") << endl;
01277     KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(eventID);
01278     if (!kcalEvent)
01279     {
01280         kdError(5950) << "KAlarmApp::handleEvent(): event ID not found: " << eventID << endl;
01281         Daemon::eventHandled(eventID, false);
01282         return false;
01283     }
01284     KAEvent event(*kcalEvent);
01285     switch (function)
01286     {
01287         case EVENT_CANCEL:
01288             KAlarm::deleteEvent(event, true);
01289             break;
01290 
01291         case EVENT_TRIGGER:    // handle it if it's due, else execute it regardless
01292         case EVENT_HANDLE:     // handle it if it's due
01293         {
01294             QDateTime now = QDateTime::currentDateTime();
01295             DateTime  repeatDT;
01296             bool updateCalAndDisplay = false;
01297             bool alarmToExecuteValid = false;
01298             KAAlarm alarmToExecute;
01299             // Check all the alarms in turn.
01300             // Note that the main alarm is fetched before any other alarms.
01301             for (KAAlarm alarm = event.firstAlarm();  alarm.valid();  alarm = event.nextAlarm(alarm))
01302             {
01303                 if (alarm.deferred()  &&  event.repeatCount()
01304                 &&  repeatDT.isValid()  &&  alarm.dateTime() > repeatDT)
01305                 {
01306                     // This deferral of a repeated alarm is later than the last occurrence
01307                     // of the main alarm, so use the deferral alarm instead.
01308                     // If the deferral is not yet due, this prevents the main alarm being
01309                     // triggered repeatedly. If the deferral is due, this triggers it
01310                     // in preference to the main alarm.
01311                     alarmToExecute      = KAAlarm();
01312                     alarmToExecuteValid = false;
01313                     updateCalAndDisplay = false;
01314                 }
01315                 // Check if the alarm is due yet.
01316                 // Just in case it's an invalid time during a daylight savings time
01317                 // shift, check more carefully if it's today.
01318                 int secs = alarm.dateTime().secsTo(now);
01319                 if (secs < 0
01320                 &&  (alarm.date() != now.date() || alarm.time() > now.time()))
01321                 {
01322                     // This alarm is definitely not due yet
01323                     kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": not due\n";
01324                     continue;
01325                 }
01326                 if (alarm.repeatAtLogin())
01327                 {
01328                     // Alarm is to be displayed at every login.
01329                     // Check if the alarm has only just been set up.
01330                     // (The alarm daemon will immediately notify that it is due
01331                     //  since it is set up with a time in the past.)
01332                     kdDebug(5950) << "KAlarmApp::handleEvent(): REPEAT_AT_LOGIN\n";
01333                     if (secs < maxLateness(1))
01334                         continue;
01335 
01336                     // Check if the main alarm is already being displayed.
01337                     // (We don't want to display both at the same time.)
01338                     if (alarmToExecute.valid())
01339                         continue;
01340 
01341                     // Set the time to be shown if it's a display alarm
01342                     alarm.setTime(now);
01343                 }
01344                 if (event.repeatCount()  &&  alarm.type() == KAAlarm::MAIN_ALARM)
01345                 {
01346                     // Alarm has a simple repetition. Since its time in the calendr remains the same
01347                     // until its repetitions are finished, adjust its time to the correct repetition
01348                     KAEvent::OccurType type = event.previousOccurrence(now.addSecs(1), repeatDT, true);
01349                     if (type & KAEvent::OCCURRENCE_REPEAT)
01350                     {
01351                         alarm.setTime(repeatDT);
01352                         secs = repeatDT.secsTo(now);
01353                     }
01354                 }
01355                 if (alarm.lateCancel())
01356                 {
01357                     // Alarm is due, and it is to be cancelled if too late.
01358                     kdDebug(5950) << "KAlarmApp::handleEvent(): LATE_CANCEL\n";
01359                     bool late = false;
01360                     bool cancel = false;
01361                     if (alarm.dateTime().isDateOnly())
01362                     {
01363                         // The alarm has no time, so cancel it if its date is too far past
01364                         int maxlate = alarm.lateCancel() / 1440;    // maximum lateness in days
01365                         QDateTime limit(alarm.date().addDays(maxlate + 1), Preferences::startOfDay());
01366                         if (now >= limit)
01367                         {
01368                             // It's too late to display the scheduled occurrence.
01369                             // Find the last previous occurrence of the alarm.
01370                             DateTime next;
01371                             KAEvent::OccurType type = event.previousOccurrence(now, next, true);
01372                             switch (type & ~KAEvent::OCCURRENCE_REPEAT)
01373                             {
01374                                 case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
01375                                 case KAEvent::RECURRENCE_DATE:
01376                                 case KAEvent::RECURRENCE_DATE_TIME:
01377                                 case KAEvent::LAST_RECURRENCE:
01378                                     limit.setDate(next.date().addDays(maxlate + 1));
01379                                     limit.setTime(Preferences::startOfDay());
01380                                     if (now >= limit)
01381                                     {
01382                                         if (type == KAEvent::LAST_RECURRENCE
01383                                         ||  type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs())
01384                                             cancel = true;   // last ocurrence (and there are no repetitions)
01385                                         else
01386                                             late = true;
01387                                     }
01388                                     break;
01389                                 case KAEvent::NO_OCCURRENCE:
01390                                 default:
01391                                     late = true;
01392                                     break;
01393                             }
01394                         }
01395                     }
01396                     else
01397                     {
01398                         // The alarm is timed. Allow it to be the permitted amount late before cancelling it.
01399                         int maxlate = maxLateness(alarm.lateCancel());
01400                         if (secs > maxlate)
01401                         {
01402                             // It's over the maximum interval late.
01403                             // Find the most recent occurrence of the alarm.
01404                             DateTime next;
01405                             KAEvent::OccurType type = event.previousOccurrence(now, next, true);
01406                             switch (type & ~KAEvent::OCCURRENCE_REPEAT)
01407                             {
01408                                 case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
01409                                 case KAEvent::RECURRENCE_DATE:
01410                                 case KAEvent::RECURRENCE_DATE_TIME:
01411                                 case KAEvent::LAST_RECURRENCE:
01412                                     if (next.dateTime().secsTo(now) > maxlate)
01413                                     {
01414                                         if (type == KAEvent::LAST_RECURRENCE
01415                                         ||  type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs())
01416                                             cancel = true;   // last ocurrence (and there are no repetitions)
01417                                         else
01418                                             late = true;
01419                                     }
01420                                     break;
01421                                 case KAEvent::NO_OCCURRENCE:
01422                                 default:
01423                                     late = true;
01424                                     break;
01425                             }
01426                         }
01427                     }
01428 
01429                     if (cancel)
01430                     {
01431                         // All recurrences are finished, so cancel the event
01432                         event.setArchive();
01433                         cancelAlarm(event, alarm.type(), false);
01434                         updateCalAndDisplay = true;
01435                         continue;
01436                     }
01437                     if (late)
01438                     {
01439                         // The latest repetition was too long ago, so schedule the next one
01440                         rescheduleAlarm(event, alarm, false);
01441                         updateCalAndDisplay = true;
01442                         continue;
01443                     }
01444                 }
01445                 if (!alarmToExecuteValid)
01446                 {
01447                     kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": execute\n";
01448                     alarmToExecute = alarm;             // note the alarm to be executed
01449                     alarmToExecuteValid = true;         // only trigger one alarm for the event
01450                 }
01451                 else
01452                     kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": skip\n";
01453             }
01454 
01455             // If there is an alarm to execute, do this last after rescheduling/cancelling
01456             // any others. This ensures that the updated event is only saved once to the calendar.
01457             if (alarmToExecute.valid())
01458                 execAlarm(event, alarmToExecute, true, !alarmToExecute.repeatAtLogin());
01459             else
01460             {
01461                 if (function == EVENT_TRIGGER)
01462                 {
01463                     // The alarm is to be executed regardless of whether it's due.
01464                     // Only trigger one alarm from the event - we don't want multiple
01465                     // identical messages, for example.
01466                     KAAlarm alarm = event.firstAlarm();
01467                     if (alarm.valid())
01468                         execAlarm(event, alarm, false);
01469                 }
01470                 if (updateCalAndDisplay)
01471                     KAlarm::updateEvent(event, 0);     // update the window lists and calendar file
01472                 else if (function != EVENT_TRIGGER)
01473                 {
01474                     kdDebug(5950) << "KAlarmApp::handleEvent(): no action\n";
01475                     Daemon::eventHandled(eventID, false);
01476                 }
01477             }
01478             break;
01479         }
01480     }
01481     return true;
01482 }
01483 
01484 /******************************************************************************
01485 * Called when an alarm is currently being displayed, to store a copy of the
01486 * alarm in the displaying calendar, and to reschedule it for its next repetition.
01487 * If no repetitions remain, cancel it.
01488 */
01489 void KAlarmApp::alarmShowing(KAEvent& event, KAAlarm::Type alarmType, const DateTime& alarmTime)
01490 {
01491     kdDebug(5950) << "KAlarmApp::alarmShowing(" << event.id() << ", " << KAAlarm::debugType(alarmType) << ")\n";
01492     KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(event.id());
01493     if (!kcalEvent)
01494         kdError(5950) << "KAlarmApp::alarmShowing(): event ID not found: " << event.id() << endl;
01495     else
01496     {
01497         KAAlarm alarm = event.alarm(alarmType);
01498         if (!alarm.valid())
01499             kdError(5950) << "KAlarmApp::alarmShowing(): alarm type not found: " << event.id() << ":" << alarmType << endl;
01500         else
01501         {
01502             // Copy the alarm to the displaying calendar in case of a crash, etc.
01503             KAEvent dispEvent;
01504             dispEvent.setDisplaying(event, alarmType, alarmTime.dateTime());
01505             AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen();
01506             if (cal)
01507             {
01508                 cal->deleteEvent(dispEvent.id());   // in case it already exists
01509                 cal->addEvent(dispEvent);
01510                 cal->save();
01511             }
01512 
01513             rescheduleAlarm(event, alarm, true);
01514             return;
01515         }
01516     }
01517     Daemon::eventHandled(event.id(), false);
01518 }
01519 
01520 /******************************************************************************
01521 * Called when an alarm action has completed, to perform any post-alarm actions.
01522 */
01523 void KAlarmApp::alarmCompleted(const KAEvent& event)
01524 {
01525     if (!event.postAction().isEmpty()  &&  ShellProcess::authorised())
01526     {
01527         QString command = event.postAction();
01528         kdDebug(5950) << "KAlarmApp::alarmCompleted(" << event.id() << "): " << command << endl;
01529         doShellCommand(command, event, 0, ProcData::POST_ACTION);
01530     }
01531 }
01532 
01533 /******************************************************************************
01534 * Reschedule the alarm for its next recurrence. If none remain, delete it.
01535 * If the alarm is deleted and it is the last alarm for its event, the event is
01536 * removed from the calendar file and from every main window instance.
01537 */
01538 void KAlarmApp::rescheduleAlarm(KAEvent& event, const KAAlarm& alarm, bool updateCalAndDisplay)
01539 {
01540     kdDebug(5950) << "KAlarmApp::rescheduleAlarm()" << endl;
01541     bool update        = false;
01542     bool updateDisplay = false;
01543     if (alarm.reminder()  ||  alarm.deferred())
01544     {
01545         // It's an advance warning alarm or an extra deferred alarm, so delete it
01546         event.removeExpiredAlarm(alarm.type());
01547         update = true;
01548     }
01549     else if (alarm.repeatAtLogin())
01550     {
01551         // Leave an alarm which repeats at every login until its main alarm is deleted
01552         if (updateCalAndDisplay  &&  event.updated())
01553             update = true;
01554     }
01555     else
01556     {
01557         QDateTime now = QDateTime::currentDateTime();
01558         if (event.repeatCount()  &&  event.mainEndRepeatTime() > now)
01559             updateDisplay = true;    // there are more repetitions to come, so just update time in alarm list
01560         else
01561         {
01562             // The alarm's repetitions (if any) are finished.
01563             // Reschedule it for its next recurrence.
01564             switch (event.setNextOccurrence(now))
01565             {
01566                 case KAEvent::NO_OCCURRENCE:
01567                     // All repetitions are finished, so cancel the event
01568                     cancelAlarm(event, alarm.type(), updateCalAndDisplay);
01569                     break;
01570                 case KAEvent::RECURRENCE_DATE:
01571                 case KAEvent::RECURRENCE_DATE_TIME:
01572                 case KAEvent::LAST_RECURRENCE:
01573                     // The event is due by now and repetitions still remain, so rewrite the event
01574                     if (updateCalAndDisplay)
01575                         update = true;
01576                     else
01577                     {
01578                         event.cancelCancelledDeferral();
01579                         event.setUpdated();    // note that the calendar file needs to be updated
01580                     }
01581                     break;
01582                 case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
01583                     // The first occurrence is still due?!?, so don't do anything
01584                 default:
01585                     break;
01586             }
01587         }
01588         if (event.deferred())
01589         {
01590             // Just in case there's also a deferred alarm, ensure it's removed
01591             event.removeExpiredAlarm(KAAlarm::DEFERRED_ALARM);
01592             update = true;
01593         }
01594     }
01595     if (update)
01596     {
01597         event.cancelCancelledDeferral();
01598         KAlarm::updateEvent(event, 0);     // update the window lists and calendar file
01599     }
01600     else if (updateDisplay)
01601     {
01602         Daemon::eventHandled(event.id(), false);
01603         AlarmListView::modifyEvent(event, 0);
01604     }
01605 }
01606 
01607 /******************************************************************************
01608 * Delete the alarm. If it is the last alarm for its event, the event is removed
01609 * from the calendar file and from every main window instance.
01610 */
01611 void KAlarmApp::cancelAlarm(KAEvent& event, KAAlarm::Type alarmType, bool updateCalAndDisplay)
01612 {
01613     kdDebug(5950) << "KAlarmApp::cancelAlarm()" << endl;
01614     event.cancelCancelledDeferral();
01615     if (alarmType == KAAlarm::MAIN_ALARM  &&  !event.displaying()  &&  event.toBeArchived())
01616     {
01617         // The event is being deleted. Save it in the expired calendar file first.
01618         QString id = event.id();    // save event ID since KAlarm::addExpiredEvent() changes it
01619         KAlarm::addExpiredEvent(event);
01620         event.setEventID(id);       // restore event ID
01621     }
01622     event.removeExpiredAlarm(alarmType);
01623     if (!event.alarmCount())
01624         KAlarm::deleteEvent(event, false);
01625     else if (updateCalAndDisplay)
01626         KAlarm::updateEvent(event, 0);    // update the window lists and calendar file
01627 }
01628 
01629 /******************************************************************************
01630 * Execute an alarm by displaying its message or file, or executing its command.
01631 * Reply = ShellProcess instance if a command alarm
01632 *       != 0 if successful
01633 *       = 0 if the alarm is disabled, or if an error message was output.
01634 */
01635 void* KAlarmApp::execAlarm(KAEvent& event, const KAAlarm& alarm, bool reschedule, bool allowDefer, bool noPreAction)
01636 {
01637     if (!event.enabled())
01638     {
01639         // The event is disabled.
01640         if (reschedule)
01641             rescheduleAlarm(event, alarm, true);
01642         return 0;
01643     }
01644 
01645     void* result = (void*)1;
01646     event.setArchive();
01647     switch (alarm.action())
01648     {
01649         case KAAlarm::MESSAGE:
01650         case KAAlarm::FILE:
01651         {
01652             // Display a message or file, provided that the same event isn't already being displayed
01653             MessageWin* win = MessageWin::findEvent(event.id());
01654             if (!win  &&  !noPreAction  &&  !event.preAction().isEmpty()  &&  ShellProcess::authorised())
01655             {
01656                 // There is no message window currently displayed for this alarm,
01657                 // and we need to execute a command before displaying the new window.
01658                 QString command = event.preAction();
01659                 kdDebug(5950) << "KAlarmApp::execAlarm(): pre-DISPLAY command: " << command << endl;
01660                 int flags = (reschedule ? ProcData::RESCHEDULE : 0) | (allowDefer ? ProcData::ALLOW_DEFER : 0);
01661                 if (doShellCommand(command, event, &alarm, (flags | ProcData::PRE_ACTION)))
01662                     return result;     // display the message after the command completes
01663                 // Error executing command - display the message even though it failed
01664             }
01665             if (!event.enabled())
01666                 delete win;        // event is disabled - close its window
01667             else if (!win
01668                  ||  !win->hasDefer() && !alarm.repeatAtLogin()
01669                  ||  (win->alarmType() & KAAlarm::REMINDER_ALARM) && !(alarm.type() & KAAlarm::REMINDER_ALARM))
01670             {
01671                 // Either there isn't already a message for this event,
01672                 // or there is a repeat-at-login message with no Defer
01673                 // button, which needs to be replaced with a new message,
01674                 // or the caption needs to be changed from "Reminder" to "Message".
01675                 if (win)
01676                     win->setRecreating();    // prevent post-alarm actions
01677                 delete win;
01678                 (new MessageWin(event, alarm, reschedule, allowDefer))->show();
01679             }
01680             else
01681             {
01682                 // Raise the existing message window and replay any sound
01683                 win->repeat(alarm);    // N.B. this reschedules the alarm
01684             }
01685             break;
01686         }
01687         case KAAlarm::COMMAND:
01688         {
01689             int flags = event.commandXterm() ? ProcData::EXEC_IN_XTERM : 0;
01690             QString command = event.cleanText();
01691             if (event.commandScript())
01692             {
01693                 // Store the command script in a temporary file for execution
01694                 kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: (script)" << endl;
01695                 QString tmpfile = createTempScriptFile(command, false, event, alarm);
01696                 if (tmpfile.isEmpty())
01697                 {
01698                     QStringList errmsgs(i18n("Error creating temporary script file"));
01699                     (new MessageWin(event, alarm.dateTime(), errmsgs))->show();
01700                     result = 0;
01701                 }
01702                 else
01703                     result = doShellCommand(tmpfile, event, &alarm, (flags | ProcData::TEMP_FILE));
01704             }
01705             else
01706             {
01707                 kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: " << command << endl;
01708                 result = doShellCommand(command, event, &alarm, flags);
01709             }
01710             if (reschedule)
01711                 rescheduleAlarm(event, alarm, true);
01712             break;
01713         }
01714         case KAAlarm::EMAIL:
01715         {
01716             kdDebug(5950) << "KAlarmApp::execAlarm(): EMAIL to: " << event.emailAddresses(", ") << endl;
01717             QStringList errmsgs;
01718             if (!KAMail::send(event, errmsgs, (reschedule || allowDefer)))
01719                 result = 0;
01720             if (!errmsgs.isEmpty())
01721             {
01722                 // Some error occurred, although the email may have been sent successfully
01723                 if (result)
01724                     kdDebug(5950) << "KAlarmApp::execAlarm(): copy error: " << errmsgs[1] << endl;
01725                 else
01726                     kdDebug(5950) << "KAlarmApp::execAlarm(): failed: " << errmsgs[1] << endl;
01727                 (new MessageWin(event, alarm.dateTime(), errmsgs))->show();
01728             }
01729             if (reschedule)
01730                 rescheduleAlarm(event, alarm, true);
01731             break;
01732         }
01733         default:
01734             return 0;
01735     }
01736     return result;
01737 }
01738 
01739 /******************************************************************************
01740 * Execute a shell command line specified by an alarm.
01741 * If the PRE_ACTION bit of 'flags' is set, the alarm will be executed via
01742 * execAlarm() once the command completes, the execAlarm() parameters being
01743 * derived from the remaining bits in 'flags'.
01744 */
01745 ShellProcess* KAlarmApp::doShellCommand(const QString& command, const KAEvent& event, const KAAlarm* alarm, int flags)
01746 {
01747     KProcess::Communication comms = KProcess::NoCommunication;
01748     QString cmd;
01749     QString tmpXtermFile;
01750     if (flags & ProcData::EXEC_IN_XTERM)
01751     {
01752         // Execute the command in a terminal window.
01753         cmd = Preferences::cmdXTermCommand();
01754         cmd.replace("%t", aboutData()->programName());     // set the terminal window title
01755         if (cmd.find("%C") >= 0)
01756         {
01757             // Execute the command from a temporary script file
01758             if (flags & ProcData::TEMP_FILE)
01759                 cmd.replace("%C", command);    // the command is already calling a temporary file
01760             else
01761             {
01762                 tmpXtermFile = createTempScriptFile(command, true, event, *alarm);
01763                 if (tmpXtermFile.isEmpty())
01764                     return 0;
01765                 cmd.replace("%C", tmpXtermFile);    // %C indicates where to insert the command
01766             }
01767         }
01768         else if (cmd.find("%W") >= 0)
01769         {
01770             // Execute the command from a temporary script file,
01771             // with a sleep after the command is executed
01772             tmpXtermFile = createTempScriptFile(command + QString::fromLatin1("\nsleep 86400\n"), true, event, *alarm);
01773             if (tmpXtermFile.isEmpty())
01774                 return 0;
01775             cmd.replace("%W", tmpXtermFile);    // %w indicates where to insert the command
01776         }
01777         else if (cmd.find("%w") >= 0)
01778         {
01779             // Append a sleep to the command.
01780             // Quote the command in case it contains characters such as [>|;].
01781             QString exec = KShellProcess::quote(command + QString::fromLatin1("; sleep 86400"));
01782             cmd.replace("%w", exec);    // %w indicates where to insert the command string
01783         }
01784         else
01785         {
01786             // Set the command to execute.
01787             // Put it in quotes in case it contains characters such as [>|;].
01788             QString exec = KShellProcess::quote(command);
01789             if (cmd.find("%c") >= 0)
01790                 cmd.replace("%c", exec);    // %c indicates where to insert the command string
01791             else
01792                 cmd.append(exec);           // otherwise, simply append the command string
01793         }
01794     }
01795     else
01796     {
01797         cmd = command;
01798         comms = KProcess::AllOutput;
01799     }
01800     ShellProcess* proc = new ShellProcess(cmd);
01801     connect(proc, SIGNAL(shellExited(ShellProcess*)), SLOT(slotCommandExited(ShellProcess*)));
01802     QGuardedPtr<ShellProcess> logproc = 0;
01803     if (comms == KProcess::AllOutput  &&  !event.logFile().isEmpty())
01804     {
01805         // Output is to be appended to a log file.
01806         // Set up a logging process to write the command's output to.
01807         connect(proc, SIGNAL(receivedStdout(KProcess*,char*,int)), SLOT(slotCommandOutput(KProcess*,char*,int)));
01808         connect(proc, SIGNAL(receivedStderr(KProcess*,char*,int)), SLOT(slotCommandOutput(KProcess*,char*,int)));
01809         logproc = new ShellProcess(QString::fromLatin1("cat >>%1").arg(event.logFile()));
01810         connect(logproc, SIGNAL(shellExited(ShellProcess*)), SLOT(slotLogProcExited(ShellProcess*)));
01811         logproc->start(KProcess::Stdin);
01812         QCString heading;
01813         if (alarm  &&  alarm->dateTime().isValid())
01814         {
01815             QString dateTime = alarm->dateTime().isDateOnly()
01816                              ? KGlobal::locale()->formatDate(alarm->dateTime().date(), true)
01817                              : KGlobal::locale()->formatDateTime(alarm->dateTime().dateTime());
01818             heading.sprintf("\n******* KAlarm %s *******\n", dateTime.latin1());
01819         }
01820         else
01821             heading = "\n******* KAlarm *******\n";
01822         logproc->writeStdin(heading, heading.length()+1);
01823     }
01824     ProcData* pd = new ProcData(proc, logproc, new KAEvent(event), (alarm ? new KAAlarm(*alarm) : 0), flags);
01825     if (flags & ProcData::TEMP_FILE)
01826         pd->tempFiles += command;
01827     if (!tmpXtermFile.isEmpty())
01828         pd->tempFiles += tmpXtermFile;
01829     mCommandProcesses.append(pd);
01830     if (proc->start(comms))
01831         return proc;
01832 
01833     // Error executing command - report it
01834     kdError(5950) << "KAlarmApp::doShellCommand(): command failed to start\n";
01835     commandErrorMsg(proc, event, alarm, flags);
01836     mCommandProcesses.remove(pd);
01837     delete pd;
01838     return 0;
01839 }
01840 
01841 /******************************************************************************
01842 * Create a temporary script file containing the specified command string.
01843 * Reply = path of temporary file, or null string if error.
01844 */
01845 QString KAlarmApp::createTempScriptFile(const QString& command, bool insertShell, const KAEvent& event, const KAAlarm& alarm)
01846 {
01847     KTempFile tmpFile(QString::null, QString::null, 0700);
01848     tmpFile.setAutoDelete(false);     // don't delete file when it is destructed
01849     QTextStream* stream = tmpFile.textStream();
01850     if (!stream)
01851         kdError(5950) << "KAlarmApp::createTempScript(): Unable to create a temporary script file" << endl;
01852     else
01853     {
01854         if (insertShell)
01855             *stream << "#!" << ShellProcess::shellPath() << "\n";
01856         *stream << command;
01857         tmpFile.close();
01858         if (tmpFile.status())
01859             kdError(5950) << "KAlarmApp::createTempScript(): Error " << tmpFile.status() << " writing to temporary script file" << endl;
01860         else
01861             return tmpFile.name();
01862     }
01863 
01864     QStringList errmsgs(i18n("Error creating temporary script file"));
01865     (new MessageWin(event, alarm.dateTime(), errmsgs))->show();
01866     return QString::null;
01867 }
01868 
01869 /******************************************************************************
01870 * Called when an executing command alarm sends output to stdout or stderr.
01871 */
01872 void KAlarmApp::slotCommandOutput(KProcess* proc, char* buffer, int bufflen)
01873 {
01874 //kdDebug(5950) << "KAlarmApp::slotCommandOutput(): '" << QCString(buffer, bufflen+1) << "'\n";
01875     // Find this command in the command list
01876     for (QValueList<ProcData*>::Iterator it = mCommandProcesses.begin();  it != mCommandProcesses.end();  ++it)
01877     {
01878         ProcData* pd = *it;
01879         if (pd->process == proc  &&  pd->logProcess)
01880         {
01881             pd->logProcess->writeStdin(buffer, bufflen);
01882             break;
01883         }
01884     }
01885 }
01886 
01887 /******************************************************************************
01888 * Called when a logging process completes.
01889 */
01890 void KAlarmApp::slotLogProcExited(ShellProcess* proc)
01891 {
01892     // Because it's held as a guarded pointer in the ProcData structure,
01893     // we don't need to set any pointers to zero.
01894     delete proc;
01895 }
01896 
01897 /******************************************************************************
01898 * Called when a command alarm's execution completes.
01899 */
01900 void KAlarmApp::slotCommandExited(ShellProcess* proc)
01901 {
01902     kdDebug(5950) << "KAlarmApp::slotCommandExited()\n";
01903     // Find this command in the command list
01904     for (QValueList<ProcData*>::Iterator it = mCommandProcesses.begin();  it != mCommandProcesses.end();  ++it)
01905     {
01906         ProcData* pd = *it;
01907         if (pd->process == proc)
01908         {
01909             // Found the command
01910             if (pd->logProcess)
01911                 pd->logProcess->stdinExit();   // terminate the logging process
01912 
01913             // Check its exit status
01914             if (!proc->normalExit())
01915             {
01916                 QString errmsg = proc->errorMessage();
01917                 kdWarning(5950) << "KAlarmApp::slotCommandExited(" << pd->event->cleanText() << "): " << errmsg << endl;
01918                 if (pd->messageBoxParent)
01919                 {
01920                     // Close the existing informational KMessageBox for this process
01921                     QObjectList* dialogs = pd->messageBoxParent->queryList("KDialogBase", 0, false, true);
01922                     KDialogBase* dialog = (KDialogBase*)dialogs->getFirst();
01923                     delete dialog;
01924                     delete dialogs;
01925                     if (!pd->tempFile())
01926                     {
01927                         errmsg += "\n";
01928                         errmsg += proc->command();
01929                     }
01930                     KMessageBox::error(pd->messageBoxParent, errmsg);
01931                 }
01932                 else
01933                     commandErrorMsg(proc, *pd->event, pd->alarm, pd->flags);
01934             }
01935             if (pd->preAction())
01936                 execAlarm(*pd->event, *pd->alarm, pd->reschedule(), pd->allowDefer(), true);
01937             mCommandProcesses.remove(it);
01938             delete pd;
01939             break;
01940         }
01941     }
01942 
01943     // If there are now no executing shell commands, quit if a quit was queued
01944     if (mPendingQuit  &&  mCommandProcesses.isEmpty())
01945         quitIf(mPendingQuitCode);
01946 }
01947 
01948 /******************************************************************************
01949 * Output an error message for a shell command.
01950 */
01951 void KAlarmApp::commandErrorMsg(const ShellProcess* proc, const KAEvent& event, const KAAlarm* alarm, int flags)
01952 {
01953     QStringList errmsgs;
01954     if (flags & ProcData::PRE_ACTION)
01955         errmsgs += i18n("Pre-alarm action:");
01956     else if (flags & ProcData::POST_ACTION)
01957         errmsgs += i18n("Post-alarm action:");
01958     errmsgs += proc->errorMessage();
01959     if (!(flags & ProcData::TEMP_FILE))
01960         errmsgs += proc->command();
01961     (new MessageWin(event, (alarm ? alarm->dateTime() : DateTime()), errmsgs))->show();
01962 }
01963 
01964 /******************************************************************************
01965 * Notes that an informational KMessageBox is displayed for this process.
01966 */
01967 void KAlarmApp::commandMessage(ShellProcess* proc, QWidget* parent)
01968 {
01969     // Find this command in the command list
01970     for (QValueList<ProcData*>::Iterator it = mCommandProcesses.begin();  it != mCommandProcesses.end();  ++it)
01971     {
01972         ProcData* pd = *it;
01973         if (pd->process == proc)
01974         {
01975             pd->messageBoxParent = parent;
01976             break;
01977         }
01978     }
01979 }
01980 
01981 /******************************************************************************
01982 * Set up remaining DCOP handlers and start processing DCOP calls.
01983 */
01984 void KAlarmApp::setUpDcop()
01985 {
01986     if (!mInitialised)
01987     {
01988         mInitialised = true;      // we're now ready to handle DCOP calls
01989         Daemon::createDcopHandler();
01990         QTimer::singleShot(0, this, SLOT(processQueue()));    // process anything already queued
01991     }
01992 }
01993 
01994 /******************************************************************************
01995 * If this is the first time through, open the calendar file, optionally start
01996 * the alarm daemon and register with it, and set up the DCOP handler.
01997 */
01998 bool KAlarmApp::initCheck(bool calendarOnly)
01999 {
02000     bool startdaemon;
02001     AlarmCalendar* cal = AlarmCalendar::activeCalendar();
02002     if (!cal->isOpen())
02003     {
02004         kdDebug(5950) << "KAlarmApp::initCheck(): opening active calendar\n";
02005 
02006         // First time through. Open the calendar file.
02007         if (!cal->open())
02008             return false;
02009 
02010         if (!mStartOfDay.isValid())
02011             changeStartOfDay();     // start of day time has changed, so adjust date-only alarms
02012 
02013         /* Need to open the display calendar now, since otherwise if the daemon
02014          * immediately notifies display alarms, they will often be processed while
02015          * redisplayAlarms() is executing open() (but before open() completes),
02016          * which causes problems!!
02017          */
02018         AlarmCalendar::displayCalendar()->open();
02019 
02020         /* Need to open the expired alarm calendar now, since otherwise if the daemon
02021          * immediately notifies multiple alarms, the second alarm is likely to be
02022          * processed while the calendar is executing open() (but before open() completes),
02023          * which causes a hang!!
02024          */
02025         AlarmCalendar::expiredCalendar()->open();
02026         AlarmCalendar::expiredCalendar()->setPurgeDays(theInstance->mPrefsExpiredKeepDays);
02027 
02028         startdaemon = true;
02029     }
02030     else
02031         startdaemon = !Daemon::isRegistered();
02032 
02033     if (!calendarOnly)
02034     {
02035         setUpDcop();      // start processing DCOP calls
02036         if (startdaemon)
02037             Daemon::start();  // make sure the alarm daemon is running
02038     }
02039     return true;
02040 }
02041 
02042 /******************************************************************************
02043 *  Convert the --time parameter string into a date/time or date value.
02044 *  The parameter is in the form [[[yyyy-]mm-]dd-]hh:mm or yyyy-mm-dd.
02045 *  Reply = true if successful.
02046 */
02047 static bool convWakeTime(const QCString timeParam, QDateTime& dateTime, bool& noTime)
02048 {
02049     if (timeParam.length() > 19)
02050         return false;
02051     char timeStr[20];
02052     strcpy(timeStr, timeParam);
02053     int dt[5] = { -1, -1, -1, -1, -1 };
02054     char* s;
02055     char* end;
02056     // Get the minute value
02057     if ((s = strchr(timeStr, ':')) == 0)
02058         noTime = true;
02059     else
02060     {
02061         noTime = false;
02062         *s++ = 0;
02063         dt[4] = strtoul(s, &end, 10);
02064         if (end == s  ||  *end  ||  dt[4] >= 60)
02065             return false;
02066         // Get the hour value
02067         if ((s = strrchr(timeStr, '-')) == 0)
02068             s = timeStr;
02069         else
02070             *s++ = 0;
02071         dt[3] = strtoul(s, &end, 10);
02072         if (end == s  ||  *end  ||  dt[3] >= 24)
02073             return false;
02074     }
02075     bool dateSet = false;
02076     if (s != timeStr)
02077     {
02078         dateSet = true;
02079         // Get the day value
02080         if ((s = strrchr(timeStr, '-')) == 0)
02081             s = timeStr;
02082         else
02083             *s++ = 0;
02084         dt[2] = strtoul(s, &end, 10);
02085         if (end == s  ||  *end  ||  dt[2] == 0  ||  dt[2] > 31)
02086             return false;
02087         if (s != timeStr)
02088         {
02089             // Get the month value
02090             if ((s = strrchr(timeStr, '-')) == 0)
02091                 s = timeStr;
02092             else
02093                 *s++ = 0;
02094             dt[1] = strtoul(s, &end, 10);
02095             if (end == s  ||  *end  ||  dt[1] == 0  ||  dt[1] > 12)
02096                 return false;
02097             if (s != timeStr)
02098             {
02099                 // Get the year value
02100                 dt[0] = strtoul(timeStr, &end, 10);
02101                 if (end == timeStr  ||  *end)
02102                     return false;
02103             }
02104         }
02105     }
02106 
02107     QDate date(dt[0], dt[1], dt[2]);
02108     QTime time(0, 0, 0);
02109     if (noTime)
02110     {
02111         // No time was specified, so the full date must have been specified
02112         if (dt[0] < 0)
02113             return false;
02114     }
02115     else
02116     {
02117         // Compile the values into a date/time structure
02118         QDateTime now = QDateTime::currentDateTime();
02119         if (dt[0] < 0)
02120             date.setYMD(now.date().year(),
02121                         (dt[1] < 0 ? now.date().month() : dt[1]),
02122                         (dt[2] < 0 ? now.date().day() : dt[2]));
02123         time.setHMS(dt[3], dt[4], 0);
02124         if (!dateSet  &&  time < now.time())
02125             date = date.addDays(1);
02126     }
02127     if (!date.isValid())
02128         return false;
02129     dateTime.setDate(date);
02130     dateTime.setTime(time);
02131     return true;
02132 }
02133 
02134 /******************************************************************************
02135 *  Convert a time interval command line parameter.
02136 *  Reply = true if successful.
02137 */
02138 static bool convInterval(QCString timeParam, KARecurrence::Type& recurType, int& timeInterval, bool allowMonthYear)
02139 {
02140     // Get the recurrence interval
02141     bool ok = true;
02142     uint interval = 0;
02143     bool negative = (timeParam[0] == '-');
02144     if (negative)
02145         timeParam = timeParam.right(1);
02146     uint length = timeParam.length();
02147     switch (timeParam[length - 1])
02148     {
02149         case 'Y':
02150             if (!allowMonthYear)
02151                 ok = false;
02152             recurType = KARecurrence::ANNUAL_DATE;
02153             timeParam = timeParam.left(length - 1);
02154             break;
02155         case 'W':
02156             recurType = KARecurrence::WEEKLY;
02157             timeParam = timeParam.left(length - 1);
02158             break;
02159         case 'D':
02160             recurType = KARecurrence::DAILY;
02161             timeParam = timeParam.left(length - 1);
02162             break;
02163         case 'M':
02164         {
02165             int i = timeParam.find('H');
02166             if (i < 0)
02167             {
02168                 if (!allowMonthYear)
02169                     ok = false;
02170                 recurType = KARecurrence::MONTHLY_DAY;
02171                 timeParam = timeParam.left(length - 1);
02172             }
02173             else
02174             {
02175                 recurType = KARecurrence::MINUTELY;
02176                 interval = timeParam.left(i).toUInt(&ok) * 60;
02177                 timeParam = timeParam.mid(i + 1, length - i - 2);
02178             }
02179             break;
02180         }
02181         default:       // should be a digit
02182             recurType = KARecurrence::MINUTELY;
02183             break;
02184     }
02185     if (ok)
02186         interval += timeParam.toUInt(&ok);
02187     timeInterval = static_cast<int>(interval);
02188     if (negative)
02189         timeInterval = -timeInterval;
02190     return ok;
02191 }
02192 
02193 KAlarmApp::ProcData::ProcData(ShellProcess* p, ShellProcess* logp, KAEvent* e, KAAlarm* a, int f)
02194     : process(p),
02195       logProcess(logp),
02196       event(e),
02197       alarm(a),
02198       messageBoxParent(0),
02199       flags(f)
02200 { }
02201 
02202 KAlarmApp::ProcData::~ProcData()
02203 {
02204     while (!tempFiles.isEmpty())
02205     {
02206         // Delete the temporary file called by the XTerm command
02207         QFile f(tempFiles.first());
02208         f.remove();
02209         tempFiles.remove(tempFiles.begin());
02210     }
02211     delete process;
02212     delete event;
02213     delete alarm;
02214 }
KDE Home | KDE Accessibility Home | Description of Access Keys