kalarm

kalarmapp.cpp

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