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                 if (!initCheck())
00736                 {
00737                     exitCode = 1;
00738                     break;
00739                 }
00740 
00741                 (MainWindow::create())->show();
00742             }
00743         } while (0);    // only execute once
00744 
00745         if (!usage.isEmpty())
00746         {
00747             // Note: we can't use args->usage() since that also quits any other
00748             // running 'instances' of the program.
00749             std::cerr << usage.local8Bit().data()
00750                       << i18n("\nUse --help to get a list of available command line options.\n").local8Bit().data();
00751             exitCode = 1;
00752         }
00753     }
00754     if (firstInstance  &&  !dontRedisplay  &&  !exitCode)
00755         redisplayAlarms();
00756 
00757     --mActiveCount;
00758     firstInstance = false;
00759 
00760     // Quit the application if this was the last/only running "instance" of the program.
00761     // Executing 'return' doesn't work very well since the program continues to
00762     // run if no windows were created.
00763     quitIf(exitCode);
00764     return exitCode;
00765 }
00766 
00767 /******************************************************************************
00768 * Quit the program, optionally only if there are no more "instances" running.
00769 */
00770 void KAlarmApp::quitIf(int exitCode, bool force)
00771 {
00772     if (force)
00773     {
00774         // Quit regardless, except for message windows
00775         MainWindow::closeAll();
00776         displayTrayIcon(false);
00777         if (MessageWin::instanceCount())
00778             return;
00779     }
00780     else
00781     {
00782         // Quit only if there are no more "instances" running
00783         mPendingQuit = false;
00784         if (mActiveCount > 0  ||  MessageWin::instanceCount())
00785             return;
00786         int mwcount = MainWindow::count();
00787         MainWindow* mw = mwcount ? MainWindow::firstWindow() : 0;
00788         if (mwcount > 1  ||  mwcount && (!mw->isHidden() || !mw->isTrayParent()))
00789             return;
00790         // There are no windows left except perhaps a main window which is a hidden tray icon parent
00791         if (mTrayWindow)
00792         {
00793             // There is a system tray icon.
00794             // Don't exit unless the system tray doesn't seem to exist.
00795             if (checkSystemTray())
00796                 return;
00797         }
00798         if (!mDcopQueue.isEmpty()  ||  !mCommandProcesses.isEmpty())
00799         {
00800             // Don't quit yet if there are outstanding actions on the DCOP queue
00801             mPendingQuit = true;
00802             mPendingQuitCode = exitCode;
00803             return;
00804         }
00805     }
00806 
00807     // This was the last/only running "instance" of the program, so exit completely.
00808     kdDebug(5950) << "KAlarmApp::quitIf(" << exitCode << "): quitting" << endl;
00809     exit(exitCode);
00810 }
00811 
00812 /******************************************************************************
00813 * Called when the Quit menu item is selected.
00814 * Closes the system tray window and all main windows, but does not exit the
00815 * program if other windows are still open.
00816 */
00817 void KAlarmApp::doQuit(QWidget* parent)
00818 {
00819     kdDebug(5950) << "KAlarmApp::doQuit()\n";
00820     if (mDisableAlarmsIfStopped
00821     &&  MessageBox::warningContinueCancel(parent, KMessageBox::Cancel,
00822                                           i18n("Quitting will disable alarms\n(once any alarm message windows are closed)."),
00823                                           QString::null, KStdGuiItem::quit(), Preferences::QUIT_WARN
00824                                          ) != KMessageBox::Yes)
00825         return;
00826     quitIf(0, true);
00827 }
00828 
00829 /******************************************************************************
00830 * Called when the session manager is about to close down the application.
00831 */
00832 void KAlarmApp::commitData(QSessionManager& sm)
00833 {
00834     mSessionClosingDown = true;
00835     KUniqueApplication::commitData(sm);
00836     mSessionClosingDown = false;         // reset in case shutdown is cancelled
00837 }
00838 
00839 /******************************************************************************
00840 * Display an error message for a fatal error. Prevent further actions since
00841 * the program state is unsafe.
00842 */
00843 void KAlarmApp::displayFatalError(const QString& message)
00844 {
00845     if (!mFatalError)
00846     {
00847         mFatalError = 1;
00848         mFatalMessage = message;
00849         if (theInstance)
00850             QTimer::singleShot(0, theInstance, SLOT(quitFatal()));
00851     }
00852 }
00853 
00854 /******************************************************************************
00855 * Quit the program, once the fatal error message has been acknowledged.
00856 */
00857 void KAlarmApp::quitFatal()
00858 {
00859     switch (mFatalError)
00860     {
00861         case 0:
00862         case 2:
00863             return;
00864         case 1:
00865             mFatalError = 2;
00866             KMessageBox::error(0, mFatalMessage);
00867             mFatalError = 3;
00868             // fall through to '3'
00869         case 3:
00870             if (theInstance)
00871                 theInstance->quitIf(1, true);
00872             break;
00873     }
00874     QTimer::singleShot(1000, this, SLOT(quitFatal()));
00875 }
00876 
00877 /******************************************************************************
00878 * The main processing loop for KAlarm.
00879 * All KAlarm operations involving opening or updating calendar files are called
00880 * from this loop to ensure that only one operation is active at any one time.
00881 * This precaution is necessary because KAlarm's activities are mostly
00882 * asynchronous, being in response to DCOP calls from the alarm daemon (or other
00883 * programs) or timer events, any of which can be received in the middle of
00884 * performing another operation. If a calendar file is opened or updated while
00885 * another calendar operation is in progress, the program has been observed to
00886 * hang, or the first calendar call has failed with data loss - clearly
00887 * unacceptable!!
00888 */
00889 void KAlarmApp::processQueue()
00890 {
00891     if (mInitialised  &&  !mProcessingQueue)
00892     {
00893         kdDebug(5950) << "KAlarmApp::processQueue()\n";
00894         mProcessingQueue = true;
00895 
00896         // Reset the alarm daemon if it's been queued
00897         KAlarm::resetDaemonIfQueued();
00898 
00899         // Process DCOP calls
00900         while (!mDcopQueue.isEmpty())
00901         {
00902             DcopQEntry& entry = mDcopQueue.first();
00903             if (entry.eventId.isEmpty())
00904             {
00905                 // It's a new alarm
00906                 switch (entry.function)
00907                 {
00908                 case EVENT_TRIGGER:
00909                     execAlarm(entry.event, entry.event.firstAlarm(), false);
00910                     break;
00911                 case EVENT_HANDLE:
00912                     KAlarm::addEvent(entry.event, 0);
00913                     break;
00914                 case EVENT_CANCEL:
00915                     break;
00916                 }
00917             }
00918             else
00919                 handleEvent(entry.eventId, entry.function);
00920             mDcopQueue.pop_front();
00921         }
00922 
00923         // Purge the expired alarms calendar if it's time to do so
00924         AlarmCalendar::expiredCalendar()->purgeIfQueued();
00925 
00926         // Now that the queue has been processed, quit if a quit was queued
00927         if (mPendingQuit)
00928             quitIf(mPendingQuitCode);
00929 
00930         mProcessingQueue = false;
00931     }
00932 }
00933 
00934 /******************************************************************************
00935 *  Redisplay alarms which were being shown when the program last exited.
00936 *  Normally, these alarms will have been displayed by session restoration, but
00937 *  if the program crashed or was killed, we can redisplay them here so that
00938 *  they won't be lost.
00939 */
00940 void KAlarmApp::redisplayAlarms()
00941 {
00942     AlarmCalendar* cal = AlarmCalendar::displayCalendar();
00943     if (cal->isOpen())
00944     {
00945         KCal::Event::List events = cal->events();
00946         for (KCal::Event::List::ConstIterator it = events.begin();  it != events.end();  ++it)
00947         {
00948                         KCal::Event* kcalEvent = *it;
00949             KAEvent event(*kcalEvent);
00950             event.setUid(KAEvent::ACTIVE);
00951             if (!MessageWin::findEvent(event.id()))
00952             {
00953                 // This event should be displayed, but currently isn't being
00954                 kdDebug(5950) << "KAlarmApp::redisplayAlarms(): " << event.id() << endl;
00955                 KAAlarm alarm = event.convertDisplayingAlarm();
00956                 (new MessageWin(event, alarm, false, !alarm.repeatAtLogin()))->show();
00957             }
00958         }
00959     }
00960 }
00961 
00962 /******************************************************************************
00963 * Called when the system tray main window is closed.
00964 */
00965 void KAlarmApp::removeWindow(TrayWindow*)
00966 {
00967     mTrayWindow = 0;
00968     quitIf();
00969 }
00970 
00971 /******************************************************************************
00972 *  Display or close the system tray icon.
00973 */
00974 bool KAlarmApp::displayTrayIcon(bool show, MainWindow* parent)
00975 {
00976     static bool creating = false;
00977     if (show)
00978     {
00979         if (!mTrayWindow  &&  !creating)
00980         {
00981             if (!mHaveSystemTray)
00982                 return false;
00983             if (!MainWindow::count()  &&  wantRunInSystemTray())
00984             {
00985                 creating = true;    // prevent main window constructor from creating an additional tray icon
00986                 parent = MainWindow::create();
00987                 creating = false;
00988             }
00989             mTrayWindow = new TrayWindow(parent ? parent : MainWindow::firstWindow());
00990             connect(mTrayWindow, SIGNAL(deleted()), SIGNAL(trayIconToggled()));
00991             mTrayWindow->show();
00992             emit trayIconToggled();
00993 
00994             // Set up a timer so that we can check after all events in the window system's
00995             // event queue have been processed, whether the system tray actually exists
00996             mCheckingSystemTray = true;
00997             mSavedNoSystemTray  = mNoSystemTray;
00998             mNoSystemTray       = false;
00999             QTimer::singleShot(0, this, SLOT(slotSystemTrayTimer()));
01000         }
01001     }
01002     else if (mTrayWindow)
01003     {
01004         delete mTrayWindow;
01005         mTrayWindow = 0;
01006     }
01007     return true;
01008 }
01009 
01010 /******************************************************************************
01011 *  Called by a timer to check whether the system tray icon has been housed in
01012 *  the system tray. Because there is a delay between the system tray icon show
01013 *  event and the icon being reparented by the system tray, we have to use a
01014 *  timer to check whether the system tray has actually grabbed it, or whether
01015 *  the system tray probably doesn't exist.
01016 */
01017 void KAlarmApp::slotSystemTrayTimer()
01018 {
01019     mCheckingSystemTray = false;
01020     if (!checkSystemTray())
01021         quitIf(0);    // exit the application if there are no open windows
01022 }
01023 
01024 /******************************************************************************
01025 *  Check whether the system tray icon has been housed in the system tray.
01026 *  If the system tray doesn't seem to exist, tell the alarm daemon to notify us
01027 *  of alarms regardless of whether we're running.
01028 */
01029 bool KAlarmApp::checkSystemTray()
01030 {
01031     if (mCheckingSystemTray  ||  !mTrayWindow)
01032         return true;
01033     if (mTrayWindow->inSystemTray() != !mSavedNoSystemTray)
01034     {
01035         kdDebug(5950) << "KAlarmApp::checkSystemTray(): changed -> " << mSavedNoSystemTray << endl;
01036         mNoSystemTray = mSavedNoSystemTray = !mSavedNoSystemTray;
01037 
01038         // Store the new setting in the config file, so that if KAlarm exits and is then
01039         // next activated by the daemon to display a message, it will register with the
01040         // daemon with the correct NOTIFY type. If that happened when there was no system
01041         // tray and alarms are disabled when KAlarm is not running, registering with
01042         // NO_START_NOTIFY could result in alarms never being seen.
01043         KConfig* config = kapp->config();
01044         config->setGroup(QString::fromLatin1("General"));
01045         config->writeEntry(QString::fromLatin1("NoSystemTray"), mNoSystemTray);
01046         config->sync();
01047 
01048         // Update other settings and reregister with the alarm daemon
01049         slotPreferencesChanged();
01050     }
01051     else
01052     {
01053         kdDebug(5950) << "KAlarmApp::checkSystemTray(): no change = " << !mSavedNoSystemTray << endl;
01054         mNoSystemTray = mSavedNoSystemTray;
01055     }
01056     return !mNoSystemTray;
01057 }
01058 
01059 /******************************************************************************
01060 * Return the main window associated with the system tray icon.
01061 */
01062 MainWindow* KAlarmApp::trayMainWindow() const
01063 {
01064     return mTrayWindow ? mTrayWindow->assocMainWindow() : 0;
01065 }
01066 
01067 /******************************************************************************
01068 *  Called when KAlarm preferences have changed.
01069 */
01070 void KAlarmApp::slotPreferencesChanged()
01071 {
01072     bool newRunInSysTray = wantRunInSystemTray();
01073     if (newRunInSysTray != mOldRunInSystemTray)
01074     {
01075         // The system tray run mode has changed
01076         ++mActiveCount;         // prevent the application from quitting
01077         MainWindow* win = mTrayWindow ? mTrayWindow->assocMainWindow() : 0;
01078         delete mTrayWindow;     // remove the system tray icon if it is currently shown
01079         mTrayWindow = 0;
01080         mOldRunInSystemTray = newRunInSysTray;
01081         if (!newRunInSysTray)
01082         {
01083             if (win  &&  win->isHidden())
01084                 delete win;
01085         }
01086         displayTrayIcon(true);
01087         --mActiveCount;
01088     }
01089 
01090     bool newDisableIfStopped = wantRunInSystemTray() && !mNoSystemTray && Preferences::disableAlarmsIfStopped();
01091     if (newDisableIfStopped != mDisableAlarmsIfStopped)
01092     {
01093         mDisableAlarmsIfStopped = newDisableIfStopped;    // N.B. this setting is used by Daemon::reregister()
01094         Preferences::setQuitWarn(true);   // since mode has changed, re-allow warning messages on Quit
01095         Daemon::reregister();           // re-register with the alarm daemon
01096     }
01097 
01098     // Change alarm times for date-only alarms if the start of day time has changed
01099     if (Preferences::startOfDay() != mStartOfDay)
01100         changeStartOfDay();
01101 
01102     // In case the date for February 29th recurrences has changed
01103     KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type());
01104 
01105     if (Preferences::expiredColour() != mPrefsExpiredColour)
01106     {
01107         // The expired alarms text colour has changed
01108         mRefreshExpiredAlarms = true;
01109         mPrefsExpiredColour = Preferences::expiredColour();
01110     }
01111 
01112     if (Preferences::expiredKeepDays() != mPrefsExpiredKeepDays)
01113     {
01114         // How long expired alarms are being kept has changed.
01115         // N.B. This also adjusts for any change in start-of-day time.
01116         mPrefsExpiredKeepDays = Preferences::expiredKeepDays();
01117         AlarmCalendar::expiredCalendar()->setPurgeDays(mPrefsExpiredKeepDays);
01118     }
01119 
01120     if (mRefreshExpiredAlarms)
01121     {
01122         mRefreshExpiredAlarms = false;
01123         MainWindow::updateExpired();
01124     }
01125 }
01126 
01127 /******************************************************************************
01128 *  Change alarm times for date-only alarms after the start of day time has changed.
01129 */
01130 void KAlarmApp::changeStartOfDay()
01131 {
01132     Daemon::notifyTimeChanged();   // tell the alarm daemon the new time
01133     QTime sod = Preferences::startOfDay();
01134     DateTime::setStartOfDay(sod);
01135     AlarmCalendar* cal = AlarmCalendar::activeCalendar();
01136     if (KAEvent::adjustStartOfDay(cal->events()))
01137         cal->save();
01138     Preferences::updateStartOfDayCheck();  // now that calendar is updated, set OK flag in config file
01139     mStartOfDay = sod;
01140 }
01141 
01142 /******************************************************************************
01143 *  Called when the expired alarms calendar has been purged.
01144 *  Updates the alarm list in all main windows.
01145 */
01146 void KAlarmApp::slotExpiredPurged()
01147 {
01148     mRefreshExpiredAlarms = false;
01149     MainWindow::updateExpired();
01150 }
01151 
01152 /******************************************************************************
01153 *  Return whether the program is configured to be running in the system tray.
01154 */
01155 bool KAlarmApp::wantRunInSystemTray() const
01156 {
01157     return Preferences::runInSystemTray()  &&  mHaveSystemTray;
01158 }
01159 
01160 /******************************************************************************
01161 * Called to schedule a new alarm, either in response to a DCOP notification or
01162 * to command line options.
01163 * Reply = true unless there was a parameter error or an error opening calendar file.
01164 */
01165 bool KAlarmApp::scheduleEvent(KAEvent::Action action, const QString& text, const QDateTime& dateTime,
01166                               int lateCancel, int flags, const QColor& bg, const QColor& fg, const QFont& font,
01167                               const QString& audioFile, float audioVolume, int reminderMinutes,
01168                               const KARecurrence& recurrence, int repeatInterval, int repeatCount,
01169                               uint mailFromID, const EmailAddressList& mailAddresses,
01170                               const QString& mailSubject, const QStringList& mailAttachments)
01171 {
01172     kdDebug(5950) << "KAlarmApp::scheduleEvent(): " << text << endl;
01173     if (!dateTime.isValid())
01174         return false;
01175     QDateTime now = QDateTime::currentDateTime();
01176     if (lateCancel  &&  dateTime < now.addSecs(-maxLateness(lateCancel)))
01177         return true;               // alarm time was already expired too long ago
01178     QDateTime alarmTime = dateTime;
01179     // Round down to the nearest minute to avoid scheduling being messed up
01180     alarmTime.setTime(QTime(alarmTime.time().hour(), alarmTime.time().minute(), 0));
01181 
01182     KAEvent event(alarmTime, text, bg, fg, font, action, lateCancel, flags);
01183     if (reminderMinutes)
01184     {
01185         bool onceOnly = (reminderMinutes < 0);
01186         event.setReminder((onceOnly ? -reminderMinutes : reminderMinutes), onceOnly);
01187     }
01188     if (!audioFile.isEmpty())
01189         event.setAudioFile(audioFile, audioVolume, -1, 0);
01190     if (!mailAddresses.isEmpty())
01191         event.setEmail(mailFromID, mailAddresses, mailSubject, mailAttachments);
01192     event.setRecurrence(recurrence);
01193     event.setFirstRecurrence();
01194     event.setRepetition(repeatInterval, repeatCount - 1);
01195     if (alarmTime <= now)
01196     {
01197         // Alarm is due for display already.
01198         // First execute it once without adding it to the calendar file.
01199         if (!mInitialised)
01200             mDcopQueue.append(DcopQEntry(event, EVENT_TRIGGER));
01201         else
01202             execAlarm(event, event.firstAlarm(), false);
01203         // If it's a recurring alarm, reschedule it for its next occurrence
01204         if (!event.recurs()
01205         ||  event.setNextOccurrence(now) == KAEvent::NO_OCCURRENCE)
01206             return true;
01207         // It has recurrences in the future
01208     }
01209 
01210     // Queue the alarm for insertion into the calendar file
01211     mDcopQueue.append(DcopQEntry(event));
01212     if (mInitialised)
01213         QTimer::singleShot(0, this, SLOT(processQueue()));
01214     return true;
01215 }
01216 
01217 /******************************************************************************
01218 * Called in response to a DCOP notification by the alarm daemon that an event
01219 * should be handled, i.e. displayed or cancelled.
01220 * Optionally display the event. Delete the event from the calendar file and
01221 * from every main window instance.
01222 */
01223 bool KAlarmApp::handleEvent(const QString& urlString, const QString& eventID, EventFunc function)
01224 {
01225     kdDebug(5950) << "KAlarmApp::handleEvent(DCOP): " << eventID << endl;
01226     AlarmCalendar* cal = AlarmCalendar::activeCalendar();     // this can be called before calendars have been initialised
01227     if (cal  &&  KURL(urlString).url() != cal->urlString())
01228     {
01229         kdError(5950) << "KAlarmApp::handleEvent(DCOP): wrong calendar file " << urlString << endl;
01230         Daemon::eventHandled(eventID, false);
01231         return false;
01232     }
01233     mDcopQueue.append(DcopQEntry(function, eventID));
01234     if (mInitialised)
01235         QTimer::singleShot(0, this, SLOT(processQueue()));
01236     return true;
01237 }
01238 
01239 /******************************************************************************
01240 * Either:
01241 * a) Display the event and then delete it if it has no outstanding repetitions.
01242 * b) Delete the event.
01243 * c) Reschedule the event for its next repetition. If none remain, delete it.
01244 * If the event is deleted, it is removed from the calendar file and from every
01245 * main window instance.
01246 */
01247 bool KAlarmApp::handleEvent(const QString& eventID, EventFunc function)
01248 {
01249     kdDebug(5950) << "KAlarmApp::handleEvent(): " << eventID << ", " << (function==EVENT_TRIGGER?"TRIGGER":function==EVENT_CANCEL?"CANCEL":function==EVENT_HANDLE?"HANDLE":"?") << endl;
01250     KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(eventID);
01251     if (!kcalEvent)
01252     {
01253         kdError(5950) << "KAlarmApp::handleEvent(): event ID not found: " << eventID << endl;
01254         Daemon::eventHandled(eventID, false);
01255         return false;
01256     }
01257     KAEvent event(*kcalEvent);
01258     switch (function)
01259     {
01260         case EVENT_CANCEL:
01261             KAlarm::deleteEvent(event, true);
01262             break;
01263 
01264         case EVENT_TRIGGER:    // handle it if it's due, else execute it regardless
01265         case EVENT_HANDLE:     // handle it if it's due
01266         {
01267             QDateTime now = QDateTime::currentDateTime();
01268             bool updateCalAndDisplay = false;
01269             bool alarmToExecuteValid = false;
01270             KAAlarm alarmToExecute;
01271             // Check all the alarms in turn.
01272             // Note that the main alarm is fetched before any other alarms.
01273             for (KAAlarm alarm = event.firstAlarm();  alarm.valid();  alarm = event.nextAlarm(alarm))
01274             {
01275                 // Check if the alarm is due yet.
01276                 int secs = alarm.dateTime(true).dateTime().secsTo(now);
01277                 if (secs < 0)
01278                 {
01279                     // This alarm is definitely not due yet
01280                     kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": not due\n";
01281                     continue;
01282                 }
01283                 if (alarm.repeatAtLogin())
01284                 {
01285                     // Alarm is to be displayed at every login.
01286                     // Check if the alarm has only just been set up.
01287                     // (The alarm daemon will immediately notify that it is due
01288                     //  since it is set up with a time in the past.)
01289                     kdDebug(5950) << "KAlarmApp::handleEvent(): REPEAT_AT_LOGIN\n";
01290                     if (secs < maxLateness(1))
01291                         continue;
01292 
01293                     // Check if the main alarm is already being displayed.
01294                     // (We don't want to display both at the same time.)
01295                     if (alarmToExecute.valid())
01296                         continue;
01297 
01298                     // Set the time to display if it's a display alarm
01299                     alarm.setTime(now);
01300                 }
01301                 if (alarm.lateCancel())
01302                 {
01303                     // Alarm is due, and it is to be cancelled if too late.
01304                     kdDebug(5950) << "KAlarmApp::handleEvent(): LATE_CANCEL\n";
01305                     bool late = false;
01306                     bool cancel = false;
01307                     if (alarm.dateTime().isDateOnly())
01308                     {
01309                         // The alarm has no time, so cancel it if its date is too far past
01310                         int maxlate = alarm.lateCancel() / 1440;    // maximum lateness in days
01311                         QDateTime limit(alarm.date().addDays(maxlate + 1), Preferences::startOfDay());
01312                         if (now >= limit)
01313                         {
01314                             // It's too late to display the scheduled occurrence.
01315                             // Find the last previous occurrence of the alarm.
01316                             DateTime next;
01317                             KAEvent::OccurType type = event.previousOccurrence(now, next, true);
01318                             switch (type & ~KAEvent::OCCURRENCE_REPEAT)
01319                             {
01320                                 case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
01321                                 case KAEvent::RECURRENCE_DATE:
01322                                 case KAEvent::RECURRENCE_DATE_TIME:
01323                                 case KAEvent::LAST_RECURRENCE:
01324                                     limit.setDate(next.date().addDays(maxlate + 1));
01325                                     limit.setTime(Preferences::startOfDay());
01326                                     if (now >= limit)
01327                                     {
01328                                         if (type == KAEvent::LAST_RECURRENCE
01329                                         ||  type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs())
01330                                             cancel = true;   // last occurrence (and there are no repetitions)
01331                                         else
01332                                             late = true;
01333                                     }
01334                                     break;
01335                                 case KAEvent::NO_OCCURRENCE:
01336                                 default:
01337                                     late = true;
01338                                     break;
01339                             }
01340                         }
01341                     }
01342                     else
01343                     {
01344                         // The alarm is timed. Allow it to be the permitted amount late before cancelling it.
01345                         int maxlate = maxLateness(alarm.lateCancel());
01346                         if (secs > maxlate)
01347                         {
01348                             // It's over the maximum interval late.
01349                             // Find the most recent occurrence of the alarm.
01350                             DateTime next;
01351                             KAEvent::OccurType type = event.previousOccurrence(now, next, true);
01352                             switch (type & ~KAEvent::OCCURRENCE_REPEAT)
01353                             {
01354                                 case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
01355                                 case KAEvent::RECURRENCE_DATE:
01356                                 case KAEvent::RECURRENCE_DATE_TIME:
01357                                 case KAEvent::LAST_RECURRENCE:
01358                                     if (next.dateTime().secsTo(now) > maxlate)
01359                                     {
01360                                         if (type == KAEvent::LAST_RECURRENCE
01361                                         ||  type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs())
01362                                             cancel = true;   // last occurrence (and there are no repetitions)
01363                                         else
01364                                             late = true;
01365                                     }
01366                                     break;
01367                                 case KAEvent::NO_OCCURRENCE:
01368                                 default:
01369                                     late = true;
01370                                     break;
01371                             }
01372                         }
01373                     }
01374 
01375                     if (cancel)
01376                     {
01377                         // All recurrences are finished, so cancel the event
01378                         event.setArchive();
01379                         cancelAlarm(event, alarm.type(), false);
01380                         updateCalAndDisplay = true;
01381                         continue;
01382                     }
01383                     if (late)
01384                     {
01385                         // The latest repetition was too long ago, so schedule the next one
01386                         rescheduleAlarm(event, alarm, false);
01387                         updateCalAndDisplay = true;
01388                         continue;
01389                     }
01390                 }
01391                 if (!alarmToExecuteValid)
01392                 {
01393                     kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": execute\n";
01394                     alarmToExecute = alarm;             // note the alarm to be executed
01395                     alarmToExecuteValid = true;         // only trigger one alarm for the event
01396                 }
01397                 else
01398                     kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": skip\n";
01399             }
01400 
01401             // If there is an alarm to execute, do this last after rescheduling/cancelling
01402             // any others. This ensures that the updated event is only saved once to the calendar.
01403             if (alarmToExecute.valid())
01404                 execAlarm(event, alarmToExecute, true, !alarmToExecute.repeatAtLogin());
01405             else
01406             {
01407                 if (function == EVENT_TRIGGER)
01408                 {
01409                     // The alarm is to be executed regardless of whether it's due.
01410                     // Only trigger one alarm from the event - we don't want multiple
01411                     // identical messages, for example.
01412                     KAAlarm alarm = event.firstAlarm();
01413                     if (alarm.valid())
01414                         execAlarm(event, alarm, false);
01415                 }
01416                 if (updateCalAndDisplay)
01417                     KAlarm::updateEvent(event, 0);     // update the window lists and calendar file
01418                 else if (function != EVENT_TRIGGER)
01419                 {
01420                     kdDebug(5950) << "KAlarmApp::handleEvent(): no action\n";
01421                     Daemon::eventHandled(eventID, false);
01422                 }
01423             }
01424             break;
01425         }
01426     }
01427     return true;
01428 }
01429 
01430 /******************************************************************************
01431 * Called when an alarm is currently being displayed, to store a copy of the
01432 * alarm in the displaying calendar, and to reschedule it for its next repetition.
01433 * If no repetitions remain, cancel it.
01434 */
01435 void KAlarmApp::alarmShowing(KAEvent& event, KAAlarm::Type alarmType, const DateTime& alarmTime)
01436 {
01437     kdDebug(5950) << "KAlarmApp::alarmShowing(" << event.id() << ", " << KAAlarm::debugType(alarmType) << ")\n";
01438     KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(event.id());
01439     if (!kcalEvent)
01440         kdError(5950) << "KAlarmApp::alarmShowing(): event ID not found: " << event.id() << endl;
01441     else
01442     {
01443         KAAlarm alarm = event.alarm(alarmType);
01444         if (!alarm.valid())
01445             kdError(5950) << "KAlarmApp::alarmShowing(): alarm type not found: " << event.id() << ":" << alarmType << endl;
01446         else
01447         {
01448             // Copy the alarm to the displaying calendar in case of a crash, etc.
01449             KAEvent dispEvent;
01450             dispEvent.setDisplaying(event, alarmType, alarmTime.dateTime());
01451             AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen();
01452             if (cal)
01453             {
01454                 cal->deleteEvent(dispEvent.id());   // in case it already exists
01455                 cal->addEvent(dispEvent);
01456                 cal->save();
01457             }
01458 
01459             rescheduleAlarm(event, alarm, true);
01460             return;
01461         }
01462     }
01463     Daemon::eventHandled(event.id(), false);
01464 }
01465 
01466 /******************************************************************************
01467 * Called when an alarm action has completed, to perform any post-alarm actions.
01468 */
01469 void KAlarmApp::alarmCompleted(const KAEvent& event)
01470 {
01471     if (!event.postAction().isEmpty()  &&  ShellProcess::authorised())
01472     {
01473         QString command = event.postAction();
01474         kdDebug(5950) << "KAlarmApp::alarmCompleted(" << event.id() << "): " << command << endl;
01475         doShellCommand(command, event, 0, ProcData::POST_ACTION);
01476     }
01477 }
01478 
01479 /******************************************************************************
01480 * Reschedule the alarm for its next recurrence. If none remain, delete it.
01481 * If the alarm is deleted and it is the last alarm for its event, the event is
01482 * removed from the calendar file and from every main window instance.
01483 */
01484 void KAlarmApp::rescheduleAlarm(KAEvent& event, const KAAlarm& alarm, bool updateCalAndDisplay)
01485 {
01486     kdDebug(5950) << "KAlarmApp::rescheduleAlarm()" << endl;
01487     bool update = false;
01488     if (alarm.reminder()  ||  alarm.deferred())
01489     {
01490         // It's an advance warning alarm or an extra deferred alarm, so delete it
01491         event.removeExpiredAlarm(alarm.type());
01492         update = true;
01493     }
01494     else if (alarm.repeatAtLogin())
01495     {
01496         // Leave an alarm which repeats at every login until its main alarm is deleted
01497         if (updateCalAndDisplay  &&  event.updated())
01498             update = true;
01499     }
01500     else
01501     {
01502         // Reschedule the alarm for its next recurrence.
01503         KAEvent::OccurType type = event.setNextOccurrence(QDateTime::currentDateTime());
01504         switch (type)
01505         {
01506             case KAEvent::NO_OCCURRENCE:
01507                 // All repetitions are finished, so cancel the event
01508                 cancelAlarm(event, alarm.type(), updateCalAndDisplay);
01509                 break;
01510             default:
01511                 if (!(type & KAEvent::OCCURRENCE_REPEAT))
01512                     break;
01513                 // Next occurrence is a repeat, so fall through to recurrence handling
01514             case KAEvent::RECURRENCE_DATE:
01515             case KAEvent::RECURRENCE_DATE_TIME:
01516             case KAEvent::LAST_RECURRENCE:
01517                 // The event is due by now and repetitions still remain, so rewrite the event
01518                 if (updateCalAndDisplay)
01519                     update = true;
01520                 else
01521                 {
01522                     event.cancelCancelledDeferral();
01523                     event.setUpdated();    // note that the calendar file needs to be updated
01524                 }
01525                 break;
01526             case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
01527                 // The first occurrence is still due?!?, so don't do anything
01528                 break;
01529         }
01530         if (event.deferred())
01531         {
01532             // Just in case there's also a deferred alarm, ensure it's removed
01533             event.removeExpiredAlarm(KAAlarm::DEFERRED_ALARM);
01534             update = true;
01535         }
01536     }
01537     if (update)
01538     {
01539         event.cancelCancelledDeferral();
01540         KAlarm::updateEvent(event, 0);     // update the window lists and calendar file
01541     }
01542 }
01543 
01544 /******************************************************************************
01545 * Delete the alarm. If it is the last alarm for its event, the event is removed
01546 * from the calendar file and from every main window instance.
01547 */
01548 void KAlarmApp::cancelAlarm(KAEvent& event, KAAlarm::Type alarmType, bool updateCalAndDisplay)
01549 {
01550     kdDebug(5950) << "KAlarmApp::cancelAlarm()" << endl;
01551     event.cancelCancelledDeferral();
01552     if (alarmType == KAAlarm::MAIN_ALARM  &&  !event.displaying()  &&  event.toBeArchived())
01553     {
01554         // The event is being deleted. Save it in the expired calendar file first.
01555         QString id = event.id();    // save event ID since KAlarm::addExpiredEvent() changes it
01556         KAlarm::addExpiredEvent(event);
01557         event.setEventID(id);       // restore event ID
01558     }
01559     event.removeExpiredAlarm(alarmType);
01560     if (!event.alarmCount())
01561         KAlarm::deleteEvent(event, false);
01562     else if (updateCalAndDisplay)
01563         KAlarm::updateEvent(event, 0);    // update the window lists and calendar file
01564 }
01565 
01566 /******************************************************************************
01567 * Execute an alarm by displaying its message or file, or executing its command.
01568 * Reply = ShellProcess instance if a command alarm
01569 *       != 0 if successful
01570 *       = 0 if the alarm is disabled, or if an error message was output.
01571 */
01572 void* KAlarmApp::execAlarm(KAEvent& event, const KAAlarm& alarm, bool reschedule, bool allowDefer, bool noPreAction)
01573 {
01574     if (!event.enabled())
01575     {
01576         // The event is disabled.
01577         if (reschedule)
01578             rescheduleAlarm(event, alarm, true);
01579         return 0;
01580     }
01581 
01582     void* result = (void*)1;
01583     event.setArchive();
01584     switch (alarm.action())
01585     {
01586         case KAAlarm::MESSAGE:
01587         case KAAlarm::FILE:
01588         {
01589             // Display a message or file, provided that the same event isn't already being displayed
01590             MessageWin* win = MessageWin::findEvent(event.id());
01591             // Find if we're changing a reminder message to the real message
01592             bool reminder = (alarm.type() & KAAlarm::REMINDER_ALARM);
01593             bool replaceReminder = !reminder && win && (win->alarmType() & KAAlarm::REMINDER_ALARM);
01594             if (!reminder  &&  !event.deferred()
01595             &&  (replaceReminder || !win)  &&  !noPreAction
01596             &&  !event.preAction().isEmpty()  &&  ShellProcess::authorised())
01597             {
01598                 // It's not a reminder or a deferred alarm, and there is no message window
01599                 // (other than a reminder window) currently displayed for this alarm,
01600                 // and we need to execute a command before displaying the new window.
01601                 // Check whether the command is already being executed for this alarm.
01602                 for (QValueList<ProcData*>::Iterator it = mCommandProcesses.begin();  it != mCommandProcesses.end();  ++it)
01603                 {
01604                     ProcData* pd = *it;
01605                     if (pd->event->id() == event.id()  &&  (pd->flags & ProcData::PRE_ACTION))
01606                     {
01607                         kdDebug(5950) << "KAlarmApp::execAlarm(): already executing pre-DISPLAY command" << endl;
01608                         return pd->process;   // already executing - don't duplicate the action
01609                     }
01610                 }
01611 
01612                 QString command = event.preAction();
01613                 kdDebug(5950) << "KAlarmApp::execAlarm(): pre-DISPLAY command: " << command << endl;
01614                 int flags = (reschedule ? ProcData::RESCHEDULE : 0) | (allowDefer ? ProcData::ALLOW_DEFER : 0);
01615                 if (doShellCommand(command, event, &alarm, (flags | ProcData::PRE_ACTION)))
01616                     return result;     // display the message after the command completes
01617                 // Error executing command - display the message even though it failed
01618             }
01619             if (!event.enabled())
01620                 delete win;        // event is disabled - close its window
01621             else if (!win
01622                  ||  !win->hasDefer() && !alarm.repeatAtLogin()
01623                  ||  replaceReminder)
01624             {
01625                 // Either there isn't already a message for this event,
01626                 // or there is a repeat-at-login message with no Defer
01627                 // button, which needs to be replaced with a new message,
01628                 // or the caption needs to be changed from "Reminder" to "Message".
01629                 if (win)
01630                     win->setRecreating();    // prevent post-alarm actions
01631                 delete win;
01632                 (new MessageWin(event, alarm, reschedule, allowDefer))->show();
01633             }
01634             else
01635             {
01636                 // Raise the existing message window and replay any sound
01637                 win->repeat(alarm);    // N.B. this reschedules the alarm
01638             }
01639             break;
01640         }
01641         case KAAlarm::COMMAND:
01642         {
01643             int flags = event.commandXterm() ? ProcData::EXEC_IN_XTERM : 0;
01644             QString command = event.cleanText();
01645             if (event.commandScript())
01646             {
01647                 // Store the command script in a temporary file for execution
01648                 kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: (script)" << endl;
01649                 QString tmpfile = createTempScriptFile(command, false, event, alarm);
01650                 if (tmpfile.isEmpty())
01651                     result = 0;
01652                 else
01653                     result = doShellCommand(tmpfile, event, &alarm, (flags | ProcData::TEMP_FILE));
01654             }
01655             else
01656             {
01657                 kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: " << command << endl;
01658                 result = doShellCommand(command, event, &alarm, flags);
01659             }
01660             if (reschedule)
01661                 rescheduleAlarm(event, alarm, true);
01662             break;
01663         }
01664         case KAAlarm::EMAIL:
01665         {
01666             kdDebug(5950) << "KAlarmApp::execAlarm(): EMAIL to: " << event.emailAddresses(", ") << endl;
01667             QStringList errmsgs;
01668             if (!KAMail::send(event, errmsgs, (reschedule || allowDefer)))
01669                 result = 0;
01670             if (!errmsgs.isEmpty())
01671             {
01672                 // Some error occurred, although the email may have been sent successfully
01673                 if (result)
01674                     kdDebug(5950) << "KAlarmApp::execAlarm(): copy error: " << errmsgs[1] << endl;
01675                 else
01676                     kdDebug(5950) << "KAlarmApp::execAlarm(): failed: " << errmsgs[1] << endl;
01677                 (new MessageWin(event, alarm.dateTime(), errmsgs))->show();
01678             }
01679             if (reschedule)
01680                 rescheduleAlarm(event, alarm, true);
01681             break;
01682         }
01683         default:
01684             return 0;
01685     }
01686     return result;
01687 }
01688 
01689 /******************************************************************************
01690 * Execute a shell command line specified by an alarm.
01691 * If the PRE_ACTION bit of 'flags' is set, the alarm will be executed via
01692 * execAlarm() once the command completes, the execAlarm() parameters being
01693 * derived from the remaining bits in 'flags'.
01694 */
01695 ShellProcess* KAlarmApp::doShellCommand(const QString& command, const KAEvent& event, const KAAlarm* alarm, int flags)
01696 {
01697     kdDebug(5950) << "KAlarmApp::doShellCommand(" << command << ", " << event.id() << ")" << endl;
01698     KProcess::Communication comms = KProcess::NoCommunication;
01699     QString cmd;
01700     QString tmpXtermFile;
01701     if (flags & ProcData::EXEC_IN_XTERM)
01702     {
01703         // Execute the command in a terminal window.
01704         cmd = Preferences::cmdXTermCommand();
01705         cmd.replace("%t", aboutData()->programName());     // set the terminal window title
01706         if (cmd.find("%C") >= 0)
01707         {
01708             // Execute the command from a temporary script file
01709             if (flags & ProcData::TEMP_FILE)
01710                 cmd.replace("%C", command);    // the command is already calling a temporary file
01711             else
01712             {
01713                 tmpXtermFile = createTempScriptFile(command, true, event, *alarm);
01714                 if (tmpXtermFile.isEmpty())
01715                     return 0;
01716                 cmd.replace("%C", tmpXtermFile);    // %C indicates where to insert the command
01717             }
01718         }
01719         else if (cmd.find("%W") >= 0)
01720         {
01721             // Execute the command from a temporary script file,
01722             // with a sleep after the command is executed
01723             tmpXtermFile = createTempScriptFile(command + QString::fromLatin1("\nsleep 86400\n"), true, event, *alarm);
01724             if (tmpXtermFile.isEmpty())
01725                 return 0;
01726             cmd.replace("%W", tmpXtermFile);    // %w indicates where to insert the command
01727         }
01728         else if (cmd.find("%w") >= 0)
01729         {
01730             // Append a sleep to the command.
01731             // Quote the command in case it contains characters such as [>|;].
01732             QString exec = KShellProcess::quote(command + QString::fromLatin1("; sleep 86400"));
01733             cmd.replace("%w", exec);    // %w indicates where to insert the command string
01734         }
01735         else
01736         {
01737             // Set the command to execute.
01738             // Put it in quotes in case it contains characters such as [>|;].
01739             QString exec = KShellProcess::quote(command);
01740             if (cmd.find("%c") >= 0)
01741                 cmd.replace("%c", exec);    // %c indicates where to insert the command string
01742             else
01743                 cmd.append(exec);           // otherwise, simply append the command string
01744         }
01745     }
01746     else
01747     {
01748         cmd = command;
01749         comms = KProcess::AllOutput;
01750     }
01751     ShellProcess* proc = new ShellProcess(cmd);
01752     connect(proc, SIGNAL(shellExited(ShellProcess*)), SLOT(slotCommandExited(ShellProcess*)));
01753     QGuardedPtr<ShellProcess> logproc = 0;
01754     if (comms == KProcess::AllOutput  &&  !event.logFile().isEmpty())
01755     {
01756         // Output is to be appended to a log file.
01757         // Set up a logging process to write the command's output to.
01758         connect(proc, SIGNAL(receivedStdout(KProcess*,char*,int)), SLOT(slotCommandOutput(KProcess*,char*,int)));
01759         connect(proc, SIGNAL(receivedStderr(KProcess*,char*,int)), SLOT(slotCommandOutput(KProcess*,char*,int)));
01760         logproc = new ShellProcess(QString::fromLatin1("cat >>%1").arg(event.logFile()));
01761         connect(logproc, SIGNAL(shellExited(ShellProcess*)), SLOT(slotLogProcExited(ShellProcess*)));
01762         logproc->start(KProcess::Stdin);
01763         QCString heading;
01764         if (alarm  &&  alarm->dateTime().isValid())
01765         {
01766             QString dateTime = alarm->dateTime().isDateOnly()
01767                              ? KGlobal::locale()->formatDate(alarm->dateTime().date(), true)
01768                              : KGlobal::locale()->formatDateTime(alarm->dateTime().dateTime());
01769             heading.sprintf("\n******* KAlarm %s *******\n", dateTime.latin1());
01770         }
01771         else
01772             heading = "\n******* KAlarm *******\n";
01773         logproc->writeStdin(heading, heading.length()+1);
01774     }
01775     ProcData* pd = new ProcData(proc, logproc, new KAEvent(event), (alarm ? new KAAlarm(*alarm) : 0), flags);
01776     if (flags & ProcData::TEMP_FILE)
01777         pd->tempFiles += command;
01778     if (!tmpXtermFile.isEmpty())
01779         pd->tempFiles += tmpXtermFile;
01780     mCommandProcesses.append(pd);
01781     if (proc->start(comms))
01782         return proc;
01783 
01784     // Error executing command - report it
01785     kdError(5950) << "KAlarmApp::doShellCommand(): command failed to start\n";
01786     commandErrorMsg(proc, event, alarm, flags);
01787     mCommandProcesses.remove(pd);
01788     delete pd;
01789     return 0;
01790 }
01791 
01792 /******************************************************************************
01793 * Create a temporary script file containing the specified command string.
01794 * Reply = path of temporary file, or null string if error.
01795 */
01796 QString KAlarmApp::createTempScriptFile(const QString& command, bool insertShell, const KAEvent& event, const KAAlarm& alarm)
01797 {
01798     KTempFile tmpFile(QString::null, QString::null, 0700);
01799     tmpFile.setAutoDelete(false);     // don't delete file when it is destructed
01800     QTextStream* stream = tmpFile.textStream();
01801     if (!stream)
01802         kdError(5950) << "KAlarmApp::createTempScript(): Unable to create a temporary script file" << endl;
01803     else
01804     {
01805         if (insertShell)
01806             *stream << "#!" << ShellProcess::shellPath() << "\n";
01807         *stream << command;
01808         tmpFile.close();
01809         if (tmpFile.status())
01810             kdError(5950) << "KAlarmApp::createTempScript(): Error " << tmpFile.status() << " writing to temporary script file" << endl;
01811         else
01812             return tmpFile.name();
01813     }
01814 
01815     QStringList errmsgs(i18n("Error creating temporary script file"));
01816     (new MessageWin(event, alarm.dateTime(), errmsgs))->show();
01817     return QString::null;
01818 }
01819 
01820 /******************************************************************************
01821 * Called when an executing command alarm sends output to stdout or stderr.
01822 */
01823 void KAlarmApp::slotCommandOutput(KProcess* proc, char* buffer, int bufflen)
01824 {
01825 //kdDebug(5950) << "KAlarmApp::slotCommandOutput(): '" << QCString(buffer, bufflen+1) << "'\n";
01826     // Find this command in the command list
01827     for (QValueList<ProcData*>::Iterator it = mCommandProcesses.begin();  it != mCommandProcesses.end();  ++it)
01828     {
01829         ProcData* pd = *it;
01830         if (pd->process == proc  &&  pd->logProcess)
01831         {
01832             pd->logProcess->writeStdin(buffer, bufflen);
01833             break;
01834         }
01835     }
01836 }
01837 
01838 /******************************************************************************
01839 * Called when a logging process completes.
01840 */
01841 void KAlarmApp::slotLogProcExited(ShellProcess* proc)
01842 {
01843     // Because it's held as a guarded pointer in the ProcData structure,
01844     // we don't need to set any pointers to zero.
01845     delete proc;
01846 }
01847 
01848 /******************************************************************************
01849 * Called when a command alarm's execution completes.
01850 */
01851 void KAlarmApp::slotCommandExited(ShellProcess* proc)
01852 {
01853     kdDebug(5950) << "KAlarmApp::slotCommandExited()\n";
01854     // Find this command in the command list
01855     for (QValueList<ProcData*>::Iterator it = mCommandProcesses.begin();  it != mCommandProcesses.end();  ++it)
01856     {
01857         ProcData* pd = *it;
01858         if (pd->process == proc)
01859         {
01860             // Found the command
01861             if (pd->logProcess)
01862                 pd->logProcess->stdinExit();   // terminate the logging process
01863 
01864             // Check its exit status
01865             if (!proc->normalExit())
01866             {
01867                 QString errmsg = proc->errorMessage();
01868                 kdWarning(5950) << "KAlarmApp::slotCommandExited(" << pd->event->cleanText() << "): " << errmsg << endl;
01869                 if (pd->messageBoxParent)
01870                 {
01871                     // Close the existing informational KMessageBox for this process
01872                     QObjectList* dialogs = pd->messageBoxParent->queryList("KDialogBase", 0, false, true);
01873                     KDialogBase* dialog = (KDialogBase*)dialogs->getFirst();
01874                     delete dialog;
01875                     delete dialogs;
01876                     if (!pd->tempFile())
01877                     {
01878                         errmsg += '\n';
01879                         errmsg += proc->command();
01880                     }
01881                     KMessageBox::error(pd->messageBoxParent, errmsg);
01882                 }
01883                 else
01884                     commandErrorMsg(proc, *pd->event, pd->alarm, pd->flags);
01885             }
01886             if (pd->preAction())
01887                 execAlarm(*pd->event, *pd->alarm, pd->reschedule(), pd->allowDefer(), true);
01888             mCommandProcesses.remove(it);
01889             delete pd;
01890             break;
01891         }
01892     }
01893 
01894     // If there are now no executing shell commands, quit if a quit was queued
01895     if (mPendingQuit  &&  mCommandProcesses.isEmpty())
01896         quitIf(mPendingQuitCode);
01897 }
01898 
01899 /******************************************************************************
01900 * Output an error message for a shell command.
01901 */
01902 void KAlarmApp::commandErrorMsg(const ShellProcess* proc, const KAEvent& event, const KAAlarm* alarm, int flags)
01903 {
01904     QStringList errmsgs;
01905     if (flags & ProcData::PRE_ACTION)
01906         errmsgs += i18n("Pre-alarm action:");
01907     else if (flags & ProcData::POST_ACTION)
01908         errmsgs += i18n("Post-alarm action:");
01909     errmsgs += proc->errorMessage();
01910     if (!(flags & ProcData::TEMP_FILE))
01911         errmsgs += proc->command();
01912     (new MessageWin(event, (alarm ? alarm->dateTime() : DateTime()), errmsgs))->show();
01913 }
01914 
01915 /******************************************************************************
01916 * Notes that an informational KMessageBox is displayed for this process.
01917 */
01918 void KAlarmApp::commandMessage(ShellProcess* proc, QWidget* parent)
01919 {
01920     // Find this command in the command list
01921     for (QValueList<ProcData*>::Iterator it = mCommandProcesses.begin();  it != mCommandProcesses.end();  ++it)
01922     {
01923         ProcData* pd = *it;
01924         if (pd->process == proc)
01925         {
01926             pd->messageBoxParent = parent;
01927             break;
01928         }
01929     }
01930 }
01931 
01932 /******************************************************************************
01933 * Set up remaining DCOP handlers and start processing DCOP calls.
01934 */
01935 void KAlarmApp::setUpDcop()
01936 {
01937     if (!mInitialised)
01938     {
01939         mInitialised = true;      // we're now ready to handle DCOP calls
01940         Daemon::createDcopHandler();
01941         QTimer::singleShot(0, this, SLOT(processQueue()));    // process anything already queued
01942     }
01943 }
01944 
01945 /******************************************************************************
01946 * If this is the first time through, open the calendar file, optionally start
01947 * the alarm daemon and register with it, and set up the DCOP handler.
01948 */
01949 bool KAlarmApp::initCheck(bool calendarOnly)
01950 {
01951     bool startdaemon;
01952     AlarmCalendar* cal = AlarmCalendar::activeCalendar();
01953     if (!cal->isOpen())
01954     {
01955         kdDebug(5950) << "KAlarmApp::initCheck(): opening active calendar\n";
01956 
01957         // First time through. Open the calendar file.
01958         if (!cal->open())
01959             return false;
01960 
01961         if (!mStartOfDay.isValid())
01962             changeStartOfDay();     // start of day time has changed, so adjust date-only alarms
01963 
01964         /* Need to open the display calendar now, since otherwise if the daemon
01965          * immediately notifies display alarms, they will often be processed while
01966          * redisplayAlarms() is executing open() (but before open() completes),
01967          * which causes problems!!
01968          */
01969         AlarmCalendar::displayCalendar()->open();
01970 
01971         /* Need to open the expired alarm calendar now, since otherwise if the daemon
01972          * immediately notifies multiple alarms, the second alarm is likely to be
01973          * processed while the calendar is executing open() (but before open() completes),
01974          * which causes a hang!!
01975          */
01976         AlarmCalendar::expiredCalendar()->open();
01977         AlarmCalendar::expiredCalendar()->setPurgeDays(theInstance->mPrefsExpiredKeepDays);
01978 
01979         startdaemon = true;
01980     }
01981     else
01982         startdaemon = !Daemon::isRegistered();
01983 
01984     if (!calendarOnly)
01985     {
01986         setUpDcop();      // start processing DCOP calls
01987         if (startdaemon)
01988             Daemon::start();  // make sure the alarm daemon is running
01989     }
01990     return true;
01991 }
01992 
01993 /******************************************************************************
01994 *  Convert the --time parameter string into a date/time or date value.
01995 *  The parameter is in the form [[[yyyy-]mm-]dd-]hh:mm or yyyy-mm-dd.
01996 *  Reply = true if successful.
01997 */
01998 static bool convWakeTime(const QCString& timeParam, QDateTime& dateTime, bool& noTime)
01999 {
02000     if (timeParam.length() > 19)
02001         return false;
02002     char timeStr[20];
02003     strcpy(timeStr, timeParam);
02004     int dt[5] = { -1, -1, -1, -1, -1 };
02005     char* s;
02006     char* end;
02007     // Get the minute value
02008     if ((s = strchr(timeStr, ':')) == 0)
02009         noTime = true;
02010     else
02011     {
02012         noTime = false;
02013         *s++ = 0;
02014         dt[4] = strtoul(s, &end, 10);
02015         if (end == s  ||  *end  ||  dt[4] >= 60)
02016             return false;
02017         // Get the hour value
02018         if ((s = strrchr(timeStr, '-')) == 0)
02019             s = timeStr;
02020         else
02021             *s++ = 0;
02022         dt[3] = strtoul(s, &end, 10);
02023         if (end == s  ||  *end  ||  dt[3] >= 24)
02024             return false;
02025     }
02026     bool dateSet = false;
02027     if (s != timeStr)
02028     {
02029         dateSet = true;
02030         // Get the day value
02031         if ((s = strrchr(timeStr, '-')) == 0)
02032             s = timeStr;
02033         else
02034             *s++ = 0;
02035         dt[2] = strtoul(s, &end, 10);
02036         if (end == s  ||  *end  ||  dt[2] == 0  ||  dt[2] > 31)
02037             return false;
02038         if (s != timeStr)
02039         {
02040             // Get the month value
02041             if ((s = strrchr(timeStr, '-')) == 0)
02042                 s = timeStr;
02043             else
02044                 *s++ = 0;
02045             dt[1] = strtoul(s, &end, 10);
02046             if (end == s  ||  *end  ||  dt[1] == 0  ||  dt[1] > 12)
02047                 return false;
02048             if (s != timeStr)
02049             {
02050                 // Get the year value
02051                 dt[0] = strtoul(timeStr, &end, 10);
02052                 if (end == timeStr  ||  *end)
02053                     return false;
02054             }
02055         }
02056     }
02057 
02058     QDate date(dt[0], dt[1], dt[2]);
02059     QTime time(0, 0, 0);
02060     if (noTime)
02061     {
02062         // No time was specified, so the full date must have been specified
02063         if (dt[0] < 0)
02064             return false;
02065     }
02066     else
02067     {
02068         // Compile the values into a date/time structure
02069         QDateTime now = QDateTime::currentDateTime();
02070         if (dt[0] < 0)
02071             date.setYMD(now.date().year(),
02072                         (dt[1] < 0 ? now.date().month() : dt[1]),
02073                         (dt[2] < 0 ? now.date().day() : dt[2]));
02074         time.setHMS(dt[3], dt[4], 0);
02075         if (!dateSet  &&  time < now.time())
02076             date = date.addDays(1);
02077     }
02078     if (!date.isValid())
02079         return false;
02080     dateTime.setDate(date);
02081     dateTime.setTime(time);
02082     return true;
02083 }
02084 
02085 /******************************************************************************
02086 * Convert a time interval command line parameter.
02087 * 'timeInterval' receives the count for the recurType. If 'allowMonthYear' is
02088 * false, 'timeInterval' is converted to minutes.
02089 * Reply = true if successful.
02090 */
02091 static bool convInterval(const QCString& timeParam, KARecurrence::Type& recurType, int& timeInterval, bool allowMonthYear)
02092 {
02093     QCString timeString = timeParam;
02094     // Get the recurrence interval
02095     bool ok = true;
02096     uint interval = 0;
02097     bool negative = (timeString[0] == '-');
02098     if (negative)
02099         timeString = timeString.right(1);
02100     uint length = timeString.length();
02101     switch (timeString[length - 1])
02102     {
02103         case 'Y':
02104             if (!allowMonthYear)
02105                 ok = false;
02106             recurType = KARecurrence::ANNUAL_DATE;
02107             timeString = timeString.left(length - 1);
02108             break;
02109         case 'W':
02110             recurType = KARecurrence::WEEKLY;
02111             timeString = timeString.left(length - 1);
02112             break;
02113         case 'D':
02114             recurType = KARecurrence::DAILY;
02115             timeString = timeString.left(length - 1);
02116             break;
02117         case 'M':
02118         {
02119             int i = timeString.find('H');
02120             if (i < 0)
02121             {
02122                 if (!allowMonthYear)
02123                     ok = false;
02124                 recurType = KARecurrence::MONTHLY_DAY;
02125                 timeString = timeString.left(length - 1);
02126             }
02127             else
02128             {
02129                 recurType = KARecurrence::MINUTELY;
02130                 interval = timeString.left(i).toUInt(&ok) * 60;
02131                 timeString = timeString.mid(i + 1, length - i - 2);
02132             }
02133             break;
02134         }
02135         default:       // should be a digit
02136             recurType = KARecurrence::MINUTELY;
02137             break;
02138     }
02139     if (ok)
02140         interval += timeString.toUInt(&ok);
02141     if (!allowMonthYear)
02142     {
02143         // Convert time interval to minutes
02144         switch (recurType)
02145         {
02146             case KARecurrence::WEEKLY:
02147                 interval *= 7;
02148                 // fall through to DAILY
02149             case KARecurrence::DAILY:
02150                 interval *= 24*60;
02151                 break;
02152             default:
02153                 break;
02154         }
02155     }
02156     timeInterval = static_cast<int>(interval);
02157     if (negative)
02158         timeInterval = -timeInterval;
02159     return ok;
02160 }
02161 
02162 KAlarmApp::ProcData::ProcData(ShellProcess* p, ShellProcess* logp, KAEvent* e, KAAlarm* a, int f)
02163     : process(p),
02164       logProcess(logp),
02165       event(e),
02166       alarm(a),
02167       messageBoxParent(0),
02168       flags(f)
02169 { }
02170 
02171 KAlarmApp::ProcData::~ProcData()
02172 {
02173     while (!tempFiles.isEmpty())
02174     {
02175         // Delete the temporary file called by the XTerm command
02176         QFile f(tempFiles.first());
02177         f.remove();
02178         tempFiles.remove(tempFiles.begin());
02179     }
02180     delete process;
02181     delete event;
02182     delete alarm;
02183 }
KDE Home | KDE Accessibility Home | Description of Access Keys