• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdepim
  • Sitemap
  • Contact Us
 

kalarm

kalarmapp.cpp

Go to the documentation of this file.
00001 /*
00002  *  kalarmapp.cpp  -  the KAlarm application object
00003  *  Program:  kalarm
00004  *  Copyright © 2001-2008 by David Jarvie <djarvie@kde.org>
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License along
00017  *  with this program; if not, write to the Free Software Foundation, Inc.,
00018  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019  */
00020 
00021 #include "kalarm.h"
00022 #include "kalarmapp.moc"
00023 
00024 #include "alarmcalendar.h"
00025 #include "eventlistmodel.h"
00026 #include "alarmlistview.h"
00027 #include "birthdaymodel.h"
00028 #include "editdlg.h"
00029 #include "dbushandler.h"
00030 #include "functions.h"
00031 #include "kamail.h"
00032 #include "karecurrence.h"
00033 #include "mainwindow.h"
00034 #include "messagebox.h"
00035 #include "messagewin.h"
00036 #include "preferences.h"
00037 #include "prefdlg.h"
00038 #include "shellprocess.h"
00039 #include "startdaytimer.h"
00040 #include "traywindow.h"
00041 
00042 #include <stdlib.h>
00043 #include <ctype.h>
00044 #include <iostream>
00045 #include <climits>
00046 
00047 #include <QObject>
00048 #include <QTimer>
00049 #include <QRegExp>
00050 #include <QFile>
00051 #include <QByteArray>
00052 #include <QTextStream>
00053 
00054 #include <kcmdlineargs.h>
00055 #include <klocale.h>
00056 #include <kstandarddirs.h>
00057 #include <kconfig.h>
00058 #include <kaboutdata.h>
00059 #include <ktemporaryfile.h>
00060 #include <kfileitem.h>
00061 #include <kglobal.h>
00062 #include <kstandardguiitem.h>
00063 #include <kservicetypetrader.h>
00064 #include <netwm.h>
00065 #include <kdebug.h>
00066 #include <kshell.h>
00067 
00068 static bool convInterval(const QByteArray& timeParam, KARecurrence::Type&, int& timeInterval, bool allowMonthYear = false);
00069 
00070 /******************************************************************************
00071 * Find the maximum number of seconds late which a late-cancel alarm is allowed
00072 * to be. This is calculated as the late cancel interval, plus a few seconds
00073 * leeway to cater for any timing irregularities.
00074 */
00075 static inline int maxLateness(int lateCancel)
00076 {
00077     static const int LATENESS_LEEWAY = 5;
00078     int lc = (lateCancel >= 1) ? (lateCancel - 1)*60 : 0;
00079     return LATENESS_LEEWAY + lc;
00080 }
00081 
00082 
00083 KAlarmApp*  KAlarmApp::theInstance  = 0;
00084 int         KAlarmApp::mActiveCount = 0;
00085 int         KAlarmApp::mFatalError  = 0;
00086 QString     KAlarmApp::mFatalMessage;
00087 
00088 
00089 /******************************************************************************
00090 * Construct the application.
00091 */
00092 KAlarmApp::KAlarmApp()
00093     : KUniqueApplication(),
00094       mInitialised(false),
00095       mQuitting(false),
00096       mLoginAlarmsDone(false),
00097       mDBusHandler(new DBusHandler()),
00098       mTrayWindow(0),
00099       mAlarmTimer(new QTimer(this)),
00100       mArchivedPurgeDays(-1),      // default to not purging
00101       mPurgeDaysQueued(-1),
00102       mPendingQuit(false),
00103       mProcessingQueue(false),
00104       mSessionClosingDown(false),
00105       mAlarmsEnabled(true),
00106       mSpeechEnabled(false)
00107 {
00108     kDebug();
00109     mAlarmTimer->setSingleShot(true);
00110     connect(mAlarmTimer, SIGNAL(timeout()), SLOT(checkNextDueAlarm()));
00111 
00112     setQuitOnLastWindowClosed(false);
00113     Preferences::self()->readConfig();
00114     Preferences::setAutoStart(true);
00115     Preferences::self()->writeConfig();
00116     Preferences::connect(SIGNAL(startOfDayChanged(const QTime&, const QTime&)), this, SLOT(changeStartOfDay()));
00117     Preferences::connect(SIGNAL(feb29TypeChanged(Feb29Type)), this, SLOT(slotFeb29TypeChanged(Feb29Type)));
00118     Preferences::connect(SIGNAL(showInSystemTrayChanged(bool)), this, SLOT(slotShowInSystemTrayChanged()));
00119     Preferences::connect(SIGNAL(archivedKeepDaysChanged(int)), this, SLOT(setArchivePurgeDays()));
00120     KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type());
00121 
00122     if (AlarmCalendar::initialiseCalendars())
00123     {
00124         connect(AlarmCalendar::resources(), SIGNAL(earliestAlarmChanged()), SLOT(checkNextDueAlarm()));
00125 
00126         KConfigGroup config(KGlobal::config(), "General");
00127         mNoSystemTray        = config.readEntry("NoSystemTray", false);
00128         mOldShowInSystemTray = wantShowInSystemTray();
00129         mStartOfDay          = Preferences::startOfDay();
00130         if (Preferences::hasStartOfDayChanged())
00131             mStartOfDay.setHMS(100,0,0);    // start of day time has changed: flag it as invalid
00132         DateTime::setStartOfDay(mStartOfDay);
00133         mPrefsArchivedColour = Preferences::archivedColour();
00134     }
00135 
00136     // Check if the speech synthesis daemon is installed
00137     mSpeechEnabled = (KServiceTypeTrader::self()->query("DBUS/Text-to-Speech", "Name == 'KTTSD'").count() > 0);
00138     if (!mSpeechEnabled)
00139         kDebug() << "Speech synthesis disabled (KTTSD not found)";
00140     // Check if KOrganizer is installed
00141     QString korg = QLatin1String("korganizer");
00142     mKOrganizerEnabled = !KStandardDirs::locate("exe", korg).isNull()  ||  !KStandardDirs::findExe(korg).isNull();
00143     if (!mKOrganizerEnabled)
00144         kDebug() << "KOrganizer options disabled (KOrganizer not found)";
00145 }
00146 
00147 /******************************************************************************
00148 */
00149 KAlarmApp::~KAlarmApp()
00150 {
00151     while (!mCommandProcesses.isEmpty())
00152     {
00153         ProcData* pd = mCommandProcesses[0];
00154         mCommandProcesses.pop_front();
00155         delete pd;
00156     }
00157     AlarmCalendar::terminateCalendars();
00158 }
00159 
00160 /******************************************************************************
00161 * Return the one and only KAlarmApp instance.
00162 * If it doesn't already exist, it is created first.
00163 */
00164 KAlarmApp* KAlarmApp::getInstance()
00165 {
00166     if (!theInstance)
00167     {
00168         theInstance = new KAlarmApp;
00169 
00170         if (mFatalError)
00171             theInstance->quitFatal();
00172     }
00173     return theInstance;
00174 }
00175 
00176 /******************************************************************************
00177 * Restore the saved session if required.
00178 */
00179 bool KAlarmApp::restoreSession()
00180 {
00181     if (!isSessionRestored())
00182         return false;
00183     if (mFatalError)
00184     {
00185         quitFatal();
00186         return false;
00187     }
00188 
00189     // Process is being restored by session management.
00190     kDebug() << "Restoring";
00191     ++mActiveCount;
00192     if (!initCheck(true))     // open the calendar file (needed for main windows), don't process queue yet
00193     {
00194         --mActiveCount;
00195         quitIf(1, true);    // error opening the main calendar - quit
00196         return true;
00197     }
00198     MainWindow* trayParent = 0;
00199     for (int i = 1;  KMainWindow::canBeRestored(i);  ++i)
00200     {
00201         QString type = KMainWindow::classNameOfToplevel(i);
00202         if (type == QLatin1String("MainWindow"))
00203         {
00204             MainWindow* win = MainWindow::create(true);
00205             win->restore(i, false);
00206             if (win->isHiddenTrayParent())
00207                 trayParent = win;
00208             else
00209                 win->show();
00210         }
00211         else if (type == QLatin1String("MessageWin"))
00212         {
00213             MessageWin* win = new MessageWin;
00214             win->restore(i, false);
00215             if (win->isValid())
00216                 win->show();
00217             else
00218                 delete win;
00219         }
00220     }
00221     startProcessQueue();      // start processing the execution queue
00222 
00223     // Try to display the system tray icon if it is configured to be shown
00224     if (MainWindow::count()  &&  wantShowInSystemTray())
00225     {
00226         displayTrayIcon(true, trayParent);
00227         // Occasionally for no obvious reason, the main main window is
00228         // shown when it should be hidden, so hide it just to be sure.
00229         if (trayParent)
00230             trayParent->hide();
00231     }
00232 
00233     --mActiveCount;
00234     quitIf(0);           // quit if no windows are open
00235     return true;
00236 }
00237 
00238 /******************************************************************************
00239 * Called for a KUniqueApplication when a new instance of the application is
00240 * started.
00241 */
00242 int KAlarmApp::newInstance()
00243 {
00244     kDebug();
00245     if (mFatalError)
00246     {
00247         quitFatal();
00248         return 1;
00249     }
00250     ++mActiveCount;
00251     int exitCode = 0;               // default = success
00252     static bool firstInstance = true;
00253     bool dontRedisplay = false;
00254     if (!firstInstance || !isSessionRestored())
00255     {
00256         QString usage;
00257         KCmdLineArgs* args = KCmdLineArgs::parsedArgs();
00258 
00259         // Use a 'do' loop which is executed only once to allow easy error exits.
00260         // Errors use 'break' to skip to the end of the function.
00261         do
00262         {
00263             #define USAGE(message)  { usage = message; break; }
00264             if (args->isSet("tray"))
00265             {
00266                 // Display only the system tray icon
00267                 kDebug() << "--tray";
00268                 args->clear();      // free up memory
00269                 if (!KSystemTrayIcon::isSystemTrayAvailable())
00270                 {
00271                     exitCode = 1;
00272                     break;
00273                 }
00274                 if (!initCheck())   // open the calendar, start processing execution queue
00275                 {
00276                     exitCode = 1;
00277                     break;
00278                 }
00279                 if (!displayTrayIcon(true))
00280                 {
00281                     exitCode = 1;
00282                     break;
00283                 }
00284             }
00285             else
00286             if (args->isSet("triggerEvent")  ||  args->isSet("cancelEvent"))
00287             {
00288                 // Display or delete the event with the specified event ID
00289                 kDebug() << "Handle event";
00290                 EventFunc function = EVENT_HANDLE;
00291                 int count = 0;
00292                 const char* option = 0;
00293                 if (args->isSet("triggerEvent"))  { function = EVENT_TRIGGER;  option = "triggerEvent";  ++count; }
00294                 if (args->isSet("cancelEvent"))   { function = EVENT_CANCEL;   option = "cancelEvent";   ++count; }
00295                 if (count > 1)
00296                     USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--triggerEvent"), QLatin1String("--cancelEvent")));
00297                 if (!initCheck(true))   // open the calendar, don't start processing execution queue yet
00298                 {
00299                     exitCode = 1;
00300                     break;
00301                 }
00302                 QString eventID = args->getOption(option);
00303                 args->clear();      // free up memory
00304                 startProcessQueue();        // start processing the execution queue
00305                 if (!handleEvent(eventID, function))
00306                 {
00307                     exitCode = 1;
00308                     break;
00309                 }
00310             }
00311             else
00312             if (args->isSet("edit"))
00313             {
00314                 QString eventID = args->getOption("edit");
00315                 if (!initCheck())
00316                 {
00317                     exitCode = 1;
00318                     break;
00319                 }
00320                 if (!KAlarm::editAlarm(eventID))
00321                 {
00322                     USAGE(i18nc("@info:shell", "<icode>%1</icode>: Event <resource>%2</resource> not found, or not editable", QString::fromLatin1("--edit"), eventID))
00323                     exitCode = 1;
00324                     break;
00325                 }
00326             }
00327             else
00328             if (args->isSet("edit-new-display")  ||  args->isSet("edit-new-command")  ||  args->isSet("edit-new-email"))
00329             {
00330                 EditAlarmDlg::Type type = args->isSet("edit-new-display") ? EditAlarmDlg::DISPLAY
00331                                         : args->isSet("edit-new-command") ? EditAlarmDlg::COMMAND
00332                                         : EditAlarmDlg::EMAIL;
00333                 if (!initCheck())
00334                 {
00335                     exitCode = 1;
00336                     break;
00337                 }
00338                 KAlarm::editNewAlarm(type);
00339             }
00340             else
00341             if (args->isSet("edit-new-preset"))
00342             {
00343                 QString templ = args->getOption("edit-new-preset");
00344                 if (!initCheck())
00345                 {
00346                     exitCode = 1;
00347                     break;
00348                 }
00349                 KAlarm::editNewAlarm(templ);
00350             }
00351             else
00352             if (args->isSet("file")  ||  args->isSet("exec")  ||  args->isSet("exec-display")  ||  args->isSet("mail")  ||  args->count())
00353             {
00354                 // Display a message or file, execute a command, or send an email
00355                 KAEvent::Action action = KAEvent::MESSAGE;
00356                 QString          alMessage;
00357                 uint             alFromID;
00358                 EmailAddressList alAddresses;
00359                 QStringList      alAttachments;
00360                 QString          alSubject;
00361                 int flags = KAEvent::DEFAULT_FONT;
00362                 if (args->isSet("file"))
00363                 {
00364                     kDebug() << "File";
00365                     if (args->isSet("exec"))
00366                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--exec"), QLatin1String("--file")))
00367                     if (args->isSet("mail"))
00368                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--mail"), QLatin1String("--file")))
00369                     if (args->count())
00370                         USAGE(i18nc("@info:shell", "message incompatible with <icode>%1</icode>", QLatin1String("--file")))
00371                     alMessage = args->getOption("file");
00372                     action = KAEvent::FILE;
00373                 }
00374                 else if (args->isSet("exec-display"))
00375                 {
00376                     kDebug() << "--exec-display";
00377                     if (args->isSet("exec"))
00378                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--exec"), QLatin1String("--exec-display")))
00379                     if (args->isSet("mail"))
00380                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--mail"), QLatin1String("--exec-display")))
00381                     alMessage = args->getOption("exec-display");
00382                     int n = args->count();
00383                     for (int i = 0;  i < n;  ++i)
00384                     {
00385                         alMessage += ' ';
00386                         alMessage += args->arg(i);
00387                     }
00388                     action = KAEvent::COMMAND;
00389                     flags |= KAEvent::DISPLAY_COMMAND;
00390                 }
00391                 else if (args->isSet("exec"))
00392                 {
00393                     kDebug() << "--exec";
00394                     if (args->isSet("mail"))
00395                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--mail"), QLatin1String("--exec")))
00396                     alMessage = args->getOption("exec");
00397                     int n = args->count();
00398                     for (int i = 0;  i < n;  ++i)
00399                     {
00400                         alMessage += ' ';
00401                         alMessage += args->arg(i);
00402                     }
00403                     action = KAEvent::COMMAND;
00404                 }
00405                 else if (args->isSet("mail"))
00406                 {
00407                     kDebug() << "--mail";
00408                     if (args->isSet("subject"))
00409                         alSubject = args->getOption("subject");
00410                     if (args->isSet("from-id"))
00411                         alFromID = KAMail::identityUoid(args->getOption("from-id"));
00412                     QStringList params = args->getOptionList("mail");
00413                     for (QStringList::Iterator i = params.begin();  i != params.end();  ++i)
00414                     {
00415                         QString addr = *i;
00416                         if (!KAMail::checkAddress(addr))
00417                             USAGE(i18nc("@info:shell", "<icode>%1</icode>: invalid email address", QLatin1String("--mail")))
00418                         alAddresses += KCal::Person(QString(), addr);
00419                     }
00420                     params = args->getOptionList("attach");
00421                     for (QStringList::Iterator i = params.begin();  i != params.end();  ++i)
00422                         alAttachments += *i;
00423                     alMessage = args->arg(0);
00424                     action = KAEvent::EMAIL;
00425                 }
00426                 else
00427                 {
00428                     kDebug() << "Message";
00429                     alMessage = args->arg(0);
00430                 }
00431 
00432                 if (action != KAEvent::EMAIL)
00433                 {
00434                     if (args->isSet("subject"))
00435                         USAGE(i18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode>", QLatin1String("--subject"), QLatin1String("--mail")))
00436                     if (args->isSet("from-id"))
00437                         USAGE(i18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode>", QLatin1String("--from-id"), QLatin1String("--mail")))
00438                     if (args->isSet("attach"))
00439                         USAGE(i18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode>", QLatin1String("--attach"), QLatin1String("--mail")))
00440                     if (args->isSet("bcc"))
00441                         USAGE(i18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode>", QLatin1String("--bcc"), QLatin1String("--mail")))
00442                 }
00443 
00444                 KDateTime alarmTime, endTime;
00445                 QColor    bgColour = Preferences::defaultBgColour();
00446                 QColor    fgColour = Preferences::defaultFgColour();
00447                 KARecurrence recurrence;
00448                 int       repeatCount    = 0;
00449                 int       repeatInterval = 0;
00450                 if (args->isSet("color"))
00451                 {
00452                     // Background colour is specified
00453                     QString colourText = args->getOption("color");
00454                     if (colourText[0] == '0' && colourText[1].toLower() == 'x')
00455                         colourText.replace(0, 2, "#");
00456                     bgColour.setNamedColor(colourText);
00457                     if (!bgColour.isValid())
00458                         USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> parameter", QLatin1String("--color")))
00459                 }
00460                 if (args->isSet("colorfg"))
00461                 {
00462                     // Foreground colour is specified
00463                     QString colourText = args->getOption("colorfg");
00464                     if (colourText[0] == '0' && colourText[1].toLower() == 'x')
00465                         colourText.replace(0, 2, "#");
00466                     fgColour.setNamedColor(colourText);
00467                     if (!fgColour.isValid())
00468                         USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> parameter", QLatin1String("--colorfg")))
00469                 }
00470 
00471                 if (args->isSet("time"))
00472                 {
00473                     QByteArray dateTime = args->getOption("time").toLocal8Bit();
00474                     if (!KAlarm::convTimeString(dateTime, alarmTime))
00475                         USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> parameter", QLatin1String("--time")))
00476                 }
00477                 else
00478                     alarmTime = KDateTime::currentLocalDateTime();
00479 
00480                 bool haveRecurrence = args->isSet("recurrence");
00481                 if (haveRecurrence)
00482                 {
00483                     if (args->isSet("login"))
00484                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--login"), QLatin1String("--recurrence")))
00485                     if (args->isSet("until"))
00486                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--until"), QLatin1String("--recurrence")))
00487                     QString rule = args->getOption("recurrence");
00488                     recurrence.set(rule);
00489                 }
00490                 if (args->isSet("interval"))
00491                 {
00492                     // Repeat count is specified
00493                     int count;
00494                     if (args->isSet("login"))
00495                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--login"), QLatin1String("--interval")))
00496                     bool ok;
00497                     if (args->isSet("repeat"))
00498                     {
00499                         count = args->getOption("repeat").toInt(&ok);
00500                         if (!ok || !count || count < -1 || (count < 0 && haveRecurrence))
00501                             USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> parameter", QLatin1String("--repeat")))
00502                     }
00503                     else if (haveRecurrence)
00504                         USAGE(i18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode>", QLatin1String("--interval"), QLatin1String("--repeat")))
00505                     else if (args->isSet("until"))
00506                     {
00507                         count = 0;
00508                         QByteArray dateTime = args->getOption("until").toLocal8Bit();
00509                         bool ok;
00510                         if (args->isSet("time"))
00511                             ok = KAlarm::convTimeString(dateTime, endTime, alarmTime);
00512                         else
00513                             ok = KAlarm::convTimeString(dateTime, endTime);
00514                         if (!ok)
00515                             USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> parameter", QLatin1String("--until")))
00516                         if (alarmTime.isDateOnly()  &&  !endTime.isDateOnly())
00517                             USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> parameter for date-only alarm", QLatin1String("--until")))
00518                         if (!alarmTime.isDateOnly()  &&  endTime.isDateOnly())
00519                             endTime.setTime(QTime(23,59,59));
00520                         if (endTime < alarmTime)
00521                             USAGE(i18nc("@info:shell", "<icode>%1</icode> earlier than <icode>%2</icode>", QLatin1String("--until"), QLatin1String("--time")))
00522                     }
00523                     else
00524                         count = -1;
00525 
00526                     // Get the recurrence interval
00527                     int interval;
00528                     KARecurrence::Type recurType;
00529                     if (!convInterval(args->getOption("interval").toLocal8Bit(), recurType, interval, !haveRecurrence)
00530                     ||  interval < 0)
00531                         USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> parameter", QLatin1String("--interval")))
00532                     if (alarmTime.isDateOnly()  &&  recurType == KARecurrence::MINUTELY)
00533                         USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> parameter for date-only alarm", QLatin1String("--interval")))
00534 
00535                     if (haveRecurrence)
00536                     {
00537                         // There is a also a recurrence specified, so set up a sub-repetition
00538                         int longestInterval = recurrence.longestInterval();
00539                         if (count * interval > longestInterval)
00540                             USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> and <icode>%2</icode> parameters: repetition is longer than <icode>%3</icode> interval", QLatin1String("--interval"), QLatin1String("--repeat"), QLatin1String("--recurrence")));
00541                         repeatCount    = count;
00542                         repeatInterval = interval;
00543                     }
00544                     else
00545                     {
00546                         // There is no other recurrence specified, so convert the repetition
00547                         // parameters into a KCal::Recurrence
00548                         recurrence.set(recurType, interval, count, alarmTime, endTime);
00549                     }
00550                 }
00551                 else
00552                 {
00553                     if (args->isSet("repeat"))
00554                         USAGE(i18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode>", QLatin1String("--repeat"), QLatin1String("--interval")))
00555                     if (args->isSet("until"))
00556                         USAGE(i18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode>", QLatin1String("--until"), QLatin1String("--interval")))
00557                 }
00558 
00559                 QString    audioFile;
00560                 float      audioVolume = -1;
00561                 bool       audioRepeat = args->isSet("play-repeat");
00562                 if (audioRepeat  ||  args->isSet("play"))
00563                 {
00564                     // Play a sound with the alarm
00565                     if (audioRepeat  &&  args->isSet("play"))
00566                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--play"), QLatin1String("--play-repeat")))
00567                     if (args->isSet("beep"))
00568                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--beep"), QLatin1String(audioRepeat ? "--play-repeat" : "--play")))
00569                     if (args->isSet("speak"))
00570                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--speak"), QLatin1String(audioRepeat ? "--play-repeat" : "--play")))
00571                     audioFile = args->getOption(audioRepeat ? "play-repeat" : "play");
00572                     if (args->isSet("volume"))
00573                     {
00574                         bool ok;
00575                         int volumepc = args->getOption("volume").toInt(&ok);
00576                         if (!ok  ||  volumepc < 0  ||  volumepc > 100)
00577                             USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> parameter", QLatin1String("--volume")))
00578                         audioVolume = static_cast<float>(volumepc) / 100;
00579                     }
00580                 }
00581                 else if (args->isSet("volume"))
00582                     USAGE(i18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode> or <icode>%3</icode>", QLatin1String("--volume"), QLatin1String("--play"), QLatin1String("--play-repeat")))
00583                 if (args->isSet("speak"))
00584                 {
00585                     if (args->isSet("beep"))
00586                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--beep"), QLatin1String("--speak")))
00587                     if (!mSpeechEnabled)
00588                         USAGE(i18nc("@info:shell", "<icode>%1</icode> requires speech synthesis to be configured using KTTSD", QLatin1String("--speak")))
00589                 }
00590                 int reminderMinutes = 0;
00591                 bool onceOnly = args->isSet("reminder-once");
00592                 if (args->isSet("reminder")  ||  onceOnly)
00593                 {
00594                     // Issue a reminder alarm in advance of the main alarm
00595                     if (onceOnly  &&  args->isSet("reminder"))
00596                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", QLatin1String("--reminder"), QLatin1String("--reminder-once")))
00597                     QString opt = onceOnly ? QLatin1String("--reminder-once") : QLatin1String("--reminder");
00598                     if (args->isSet("exec"))
00599                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", opt, QLatin1String("--exec")))
00600                     if (args->isSet("mail"))
00601                         USAGE(i18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", opt, QLatin1String("--mail")))
00602                     KARecurrence::Type recurType;
00603                     QString optval = args->getOption(onceOnly ? "reminder-once" : "reminder");
00604                     if (!convInterval(args->getOption(onceOnly ? "reminder-once" : "reminder").toLocal8Bit(), recurType, reminderMinutes))
00605                         USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> parameter", opt))
00606                     if (recurType == KARecurrence::MINUTELY  &&  alarmTime.isDateOnly())
00607                         USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> parameter for date-only alarm", opt))
00608                 }
00609 
00610                 int lateCancel = 0;
00611                 if (args->isSet("late-cancel"))
00612                 {
00613                     KARecurrence::Type recurType;
00614                     bool ok = convInterval(args->getOption("late-cancel").toLocal8Bit(), recurType, lateCancel);
00615                     if (!ok  ||  lateCancel <= 0)
00616                         USAGE(i18nc("@info:shell", "Invalid <icode>%1</icode> parameter", QLatin1String("late-cancel")))
00617                 }
00618                 else if (args->isSet("auto-close"))
00619                     USAGE(i18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode>", QLatin1String("--auto-close"), QLatin1String("--late-cancel")))
00620 
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 (alarmTime.isDateOnly())
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                 kDebug() << "Interactive";
00661                 if (args->isSet("ack-confirm"))
00662                     usage += QLatin1String("--ack-confirm ");
00663                 if (args->isSet("attach"))
00664                     usage += QLatin1String("--attach ");
00665                 if (args->isSet("auto-close"))
00666                     usage += QLatin1String("--auto-close ");
00667                 if (args->isSet("bcc"))
00668                     usage += QLatin1String("--bcc ");
00669                 if (args->isSet("beep"))
00670                     usage += QLatin1String("--beep ");
00671                 if (args->isSet("color"))
00672                     usage += QLatin1String("--color ");
00673                 if (args->isSet("colorfg"))
00674                     usage += QLatin1String("--colorfg ");
00675                 if (args->isSet("disable"))
00676                     usage += QLatin1String("--disable ");
00677                 if (args->isSet("from-id"))
00678                     usage += QLatin1String("--from-id ");
00679                 if (args->isSet("korganizer"))
00680                     usage += QLatin1String("--korganizer ");
00681                 if (args->isSet("late-cancel"))
00682                     usage += QLatin1String("--late-cancel ");
00683                 if (args->isSet("login"))
00684                     usage += QLatin1String("--login ");
00685                 if (args->isSet("play"))
00686                     usage += QLatin1String("--play ");
00687                 if (args->isSet("play-repeat"))
00688                     usage += QLatin1String("--play-repeat ");
00689                 if (args->isSet("reminder"))
00690                     usage += QLatin1String("--reminder ");
00691                 if (args->isSet("reminder-once"))
00692                     usage += QLatin1String("--reminder-once ");
00693                 if (args->isSet("speak"))
00694                     usage += QLatin1String("--speak ");
00695                 if (args->isSet("subject"))
00696                     usage += QLatin1String("--subject ");
00697                 if (args->isSet("time"))
00698                     usage += QLatin1String("--time ");
00699                 if (args->isSet("volume"))
00700                     usage += QLatin1String("--volume ");
00701                 if (!usage.isEmpty())
00702                 {
00703                     usage += i18nc("@info:shell", ": option(s) only valid with a message/<icode>%1</icode>/<icode>%2</icode>", QLatin1String("--file"), QLatin1String("--exec"));
00704                     break;
00705                 }
00706 
00707                 args->clear();      // free up memory
00708                 if (!initCheck())
00709                 {
00710                     exitCode = 1;
00711                     break;
00712                 }
00713 
00714                 (MainWindow::create())->show();
00715             }
00716         } while (0);    // only execute once
00717 
00718         if (!usage.isEmpty())
00719         {
00720             // Note: we can't use args->usage() since that also quits any other
00721             // running 'instances' of the program.
00722             std::cerr << usage.toLocal8Bit().data()
00723                       << i18nc("@info:shell", "\nUse --help to get a list of available command line options.\n").toLocal8Bit().data();
00724             exitCode = 1;
00725         }
00726     }
00727     if (firstInstance  &&  !dontRedisplay  &&  !exitCode)
00728         MessageWin::redisplayAlarms();
00729 
00730     --mActiveCount;
00731     firstInstance = false;
00732 
00733     // Quit the application if this was the last/only running "instance" of the program.
00734     // Executing 'return' doesn't work very well since the program continues to
00735     // run if no windows were created.
00736     quitIf(exitCode);
00737     return exitCode;
00738 }
00739 
00740 /******************************************************************************
00741 * Quit the program, optionally only if there are no more "instances" running.
00742 */
00743 void KAlarmApp::quitIf(int exitCode, bool force)
00744 {
00745     if (force)
00746     {
00747         // Quit regardless, except for message windows
00748         mQuitting = true;
00749         MainWindow::closeAll();
00750         displayTrayIcon(false);
00751         if (MessageWin::instanceCount())
00752             return;
00753     }
00754     else if (mQuitting)
00755         return;   // MainWindow::closeAll() causes quitIf() to be called again
00756     else
00757     {
00758         // Quit only if there are no more "instances" running
00759         mPendingQuit = false;
00760         if (mActiveCount > 0  ||  MessageWin::instanceCount())
00761             return;
00762         int mwcount = MainWindow::count();
00763         MainWindow* mw = mwcount ? MainWindow::firstWindow() : 0;
00764         if (mwcount > 1  ||  (mwcount && (!mw->isHidden() || !mw->isTrayParent())))
00765             return;
00766         // There are no windows left except perhaps a main window which is a hidden tray icon parent
00767         if (mTrayWindow)
00768         {
00769             // There is a system tray icon.
00770             // Don't exit unless the system tray doesn't seem to exist.
00771             if (checkSystemTray())
00772                 return;
00773         }
00774         if (!mDcopQueue.isEmpty()  ||  !mCommandProcesses.isEmpty())
00775         {
00776             // Don't quit yet if there are outstanding actions on the execution queue
00777             mPendingQuit = true;
00778             mPendingQuitCode = exitCode;
00779             return;
00780         }
00781     }
00782 
00783     // This was the last/only running "instance" of the program, so exit completely.
00784     kDebug() << exitCode << ": quitting";
00785     AlarmCalendar::terminateCalendars();
00786     BirthdayModel::close();
00787     exit(exitCode);
00788 }
00789 
00790 /******************************************************************************
00791 * Called when the Quit menu item is selected.
00792 * Closes the system tray window and all main windows, but does not exit the
00793 * program if other windows are still open.
00794 */
00795 void KAlarmApp::doQuit(QWidget* parent)
00796 {
00797     kDebug();
00798     if (MessageBox::warningContinueCancel(parent, KMessageBox::Cancel,
00799                                           i18nc("@info", "Quitting will disable alarms (once any alarm message windows are closed)."),
00800                                           QString(), KStandardGuiItem::quit(), Preferences::QUIT_WARN
00801                                          ) != KMessageBox::Yes)
00802         return;
00803     quitIf(0, true);
00804 }
00805 
00806 /******************************************************************************
00807 * Called when the session manager is about to close down the application.
00808 */
00809 void KAlarmApp::commitData(QSessionManager& sm)
00810 {
00811     mSessionClosingDown = true;
00812     KUniqueApplication::commitData(sm);
00813     mSessionClosingDown = false;         // reset in case shutdown is cancelled
00814 }
00815 
00816 /******************************************************************************
00817 * Display an error message for a fatal error. Prevent further actions since
00818 * the program state is unsafe.
00819 */
00820 void KAlarmApp::displayFatalError(const QString& message)
00821 {
00822     if (!mFatalError)
00823     {
00824         mFatalError = 1;
00825         mFatalMessage = message;
00826         if (theInstance)
00827             QTimer::singleShot(0, theInstance, SLOT(quitFatal()));
00828     }
00829 }
00830 
00831 /******************************************************************************
00832 * Quit the program, once the fatal error message has been acknowledged.
00833 */
00834 void KAlarmApp::quitFatal()
00835 {
00836     switch (mFatalError)
00837     {
00838         case 0:
00839         case 2:
00840             return;
00841         case 1:
00842             mFatalError = 2;
00843             KMessageBox::error(0, mFatalMessage);
00844             mFatalError = 3;
00845             // fall through to '3'
00846         case 3:
00847             if (theInstance)
00848                 theInstance->quitIf(1, true);
00849             break;
00850     }
00851     QTimer::singleShot(1000, this, SLOT(quitFatal()));
00852 }
00853 
00854 /******************************************************************************
00855 * Called by the alarm timer when the next alarm is due.
00856 * Also called when the execution queue has finished processing to check for the
00857 * next alarm.
00858 */
00859 void KAlarmApp::checkNextDueAlarm()
00860 {
00861     if (!mAlarmsEnabled)
00862         return;
00863     // Find the first alarm due
00864     KAEvent* nextEvent = AlarmCalendar::resources()->earliestAlarm();
00865     if (!nextEvent)
00866         return;   // there are no alarms pending
00867     KDateTime nextDt = nextEvent->nextTrigger(KAEvent::ALL_TRIGGER).effectiveKDateTime();
00868     qint64 interval = KDateTime::currentDateTime(Preferences::timeZone()).secsTo_long(nextDt);
00869     if (interval <= 0)
00870     {
00871         // Queue the alarm
00872         queueAlarmId(nextEvent->id());
00873         kDebug() << nextEvent->id() << ": due now";
00874         QTimer::singleShot(0, this, SLOT(processQueue()));
00875     }
00876     else
00877     {
00878         // No alarm is due yet, so set timer to wake us when it's due.
00879         // Check for integer overflow before setting timer.
00880         interval *= 1000;
00881         if (interval > INT_MAX)
00882             interval = INT_MAX;
00883         kDebug() << nextEvent->id() << "wait" << interval/1000 << "seconds";
00884         mAlarmTimer->start(static_cast<int>(interval));
00885     }
00886 }
00887 
00888 /******************************************************************************
00889 * Called by the alarm timer when the next alarm is due.
00890 * Also called when the execution queue has finished processing to check for the
00891 * next alarm.
00892 */
00893 void KAlarmApp::queueAlarmId(const QString& id)
00894 {
00895     for (int i = 0, end = mDcopQueue.count();  i < end;  ++i)
00896     {
00897         if (mDcopQueue[i].function == EVENT_HANDLE  &&  mDcopQueue[i].eventId