kalarm

kalarmapp.cpp

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