kalarm

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