kalarm

functions.cpp
00001 /*
00002  *  functions.cpp  -  miscellaneous functions
00003  *  Program:  kalarm
00004  *  Copyright © 2001-2009 by David Jarvie <djarvie@kde.org>
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License along
00017  *  with this program; if not, write to the Free Software Foundation, Inc.,
00018  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019  */
00020 
00021 #include "kalarm.h"
00022 #include "functions.h"
00023 
00024 #include "alarmcalendar.h"
00025 #include "alarmevent.h"
00026 #include "alarmlistview.h"
00027 #include "daemon.h"
00028 #include "kalarmapp.h"
00029 #include "kamail.h"
00030 #include "mainwindow.h"
00031 #include "messagewin.h"
00032 #include "preferences.h"
00033 #include "shellprocess.h"
00034 #include "templatelistview.h"
00035 #include "templatemenuaction.h"
00036 
00037 #include <tqdeepcopy.h>
00038 #include <tqdir.h>
00039 #include <tqregexp.h>
00040 
00041 #include <kconfig.h>
00042 #include <kaction.h>
00043 #include <kglobal.h>
00044 #include <klocale.h>
00045 #include <kstdguiitem.h>
00046 #include <kstdaccel.h>
00047 #include <kmessagebox.h>
00048 #include <kfiledialog.h>
00049 #include <dcopclient.h>
00050 #include <dcopref.h>
00051 #include <kdcopservicestarter.h>
00052 #include <kdebug.h>
00053 
00054 #include <libkcal/event.h>
00055 #include <libkcal/icalformat.h>
00056 #include <libkpimidentities/identitymanager.h>
00057 #include <libkpimidentities/identity.h>
00058 #include <libkcal/person.h>
00059 
00060 
00061 namespace
00062 {
00063 bool        resetDaemonQueued = false;
00064 TQCString    korganizerName = "korganizer";
00065 TQString     korgStartError;
00066 #define     KORG_DCOP_OBJECT    "KOrganizerIface"
00067 const char* KORG_DCOP_WINDOW  = "KOrganizer MainWindow";
00068 const char* KMAIL_DCOP_WINDOW = "kmail-mainwindow#1";
00069 
00070 bool sendToKOrganizer(const KAEvent&);
00071 bool deleteFromKOrganizer(const TQString& eventID);
00072 bool runKOrganizer();
00073 }
00074 
00075 
00076 namespace KAlarm
00077 {
00078 
00079 /******************************************************************************
00080 *  Display a main window with the specified event selected.
00081 */
00082 MainWindow* displayMainWindowSelected(const TQString& eventID)
00083 {
00084     MainWindow* win = MainWindow::firstWindow();
00085     if (!win)
00086     {
00087         if (theApp()->checkCalendarDaemon())    // ensure calendar is open and daemon started
00088         {
00089             win = MainWindow::create();
00090             win->show();
00091         }
00092     }
00093     else
00094     {
00095         // There is already a main window, so make it the active window
00096         bool visible = win->isVisible();
00097         if (visible)
00098             win->hide();        // in case it's on a different desktop
00099         if (!visible  ||  win->isMinimized())
00100             win->showNormal();
00101         win->raise();
00102         win->setActiveWindow();
00103     }
00104     if (win  &&  !eventID.isEmpty())
00105         win->selectEvent(eventID);
00106     return win;
00107 }
00108 
00109 /******************************************************************************
00110 * Create a New Alarm KAction.
00111 */
00112 KAction* createNewAlarmAction(const TQString& label, TQObject* receiver, const char* slot, KActionCollection* actions, const char* name)
00113 {
00114     return new KAction(label, "filenew", KStdAccel::openNew(), receiver, slot, actions, name);
00115 }
00116 
00117 /******************************************************************************
00118 * Create a New From Template KAction.
00119 */
00120 TemplateMenuAction* createNewFromTemplateAction(const TQString& label, TQObject* receiver, const char* slot, KActionCollection* actions, const char* name)
00121 {
00122     return new TemplateMenuAction(label, "new_from_template", receiver, slot, actions, name);
00123 }
00124 
00125 /******************************************************************************
00126 * Add a new active (non-expired) alarm.
00127 * Save it in the calendar file and add it to every main window instance.
00128 * If 'selectionView' is non-null, the selection highlight is moved to the new
00129 * event in that listView instance.
00130 * 'event' is updated with the actual event ID.
00131 */
00132 UpdateStatus addEvent(KAEvent& event, AlarmListView* selectionView, TQWidget* errmsgParent, bool useEventID, bool allowKOrgUpdate)
00133 {
00134     kdDebug(5950) << "KAlarm::addEvent(): " << event.id() << endl;
00135     UpdateStatus status = UPDATE_OK;
00136     if (!theApp()->checkCalendarDaemon())    // ensure calendar is open and daemon started
00137         return UPDATE_FAILED;
00138     else
00139     {
00140         // Save the event details in the calendar file, and get the new event ID
00141         AlarmCalendar* cal = AlarmCalendar::activeCalendar();
00142         if (!cal->addEvent(event, useEventID))
00143             status = UPDATE_FAILED;
00144         else if (!cal->save())
00145             status = SAVE_FAILED;
00146     }
00147     if (status == UPDATE_OK)
00148     {
00149         if (allowKOrgUpdate  &&  event.copyToKOrganizer())
00150         {
00151             if (!sendToKOrganizer(event))    // tell KOrganizer to show the event
00152                 status = UPDATE_KORG_ERR;
00153         }
00154 
00155         // Update the window lists
00156         AlarmListView::addEvent(event, selectionView);
00157         return status;
00158     }
00159 
00160     if (errmsgParent)
00161         displayUpdateError(errmsgParent, status, ERR_ADD, 1);
00162     return status;
00163 }
00164 
00165 /******************************************************************************
00166 * Save the event in the expired calendar file and adjust every main window instance.
00167 * The event's ID is changed to an expired ID if necessary.
00168 */
00169 bool addExpiredEvent(KAEvent& event)
00170 {
00171     kdDebug(5950) << "KAlarm::addExpiredEvent(" << event.id() << ")\n";
00172     AlarmCalendar* cal = AlarmCalendar::expiredCalendarOpen();
00173     if (!cal)
00174         return false;
00175     bool archiving = (KAEvent::uidStatus(event.id()) == KAEvent::ACTIVE);
00176     if (archiving)
00177         event.setSaveDateTime(TQDateTime::currentDateTime());   // time stamp to control purging
00178     KCal::Event* kcalEvent = cal->addEvent(event);
00179     cal->save();
00180 
00181     // Update window lists
00182     if (!archiving)
00183         AlarmListView::addEvent(event, 0);
00184     else if (kcalEvent)
00185         AlarmListView::modifyEvent(KAEvent(*kcalEvent), 0);
00186     return true;
00187 }
00188 
00189 /******************************************************************************
00190 * Add a new template.
00191 * Save it in the calendar file and add it to every template list view.
00192 * If 'selectionView' is non-null, the selection highlight is moved to the new
00193 * event in that listView instance.
00194 * 'event' is updated with the actual event ID.
00195 */
00196 UpdateStatus addTemplate(KAEvent& event, TemplateListView* selectionView, TQWidget* errmsgParent)
00197 {
00198     kdDebug(5950) << "KAlarm::addTemplate(): " << event.id() << endl;
00199     UpdateStatus status = UPDATE_OK;
00200 
00201     // Add the template to the calendar file
00202     AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen();
00203     if (!cal  ||  !cal->addEvent(event))
00204         status = UPDATE_FAILED;
00205     else if (!cal->save())
00206         status = SAVE_FAILED;
00207     else
00208     {
00209         cal->emitEmptyStatus();
00210 
00211         // Update the window lists
00212         TemplateListView::addEvent(event, selectionView);
00213         return UPDATE_OK;
00214     }
00215 
00216     if (errmsgParent)
00217         displayUpdateError(errmsgParent, status, ERR_TEMPLATE, 1);
00218     return status;
00219 }
00220 
00221 /******************************************************************************
00222 * Modify an active (non-expired) alarm in the calendar file and in every main
00223 * window instance.
00224 * The new event will have a different event ID from the old one.
00225 * If 'selectionView' is non-null, the selection highlight is moved to the
00226 * modified event in that listView instance.
00227 */
00228 UpdateStatus modifyEvent(KAEvent& oldEvent, const KAEvent& newEvent, AlarmListView* selectionView, TQWidget* errmsgParent)
00229 {
00230     kdDebug(5950) << "KAlarm::modifyEvent(): '" << oldEvent.id() << endl;
00231 
00232     UpdateStatus status = UPDATE_OK;
00233     if (!newEvent.valid())
00234     {
00235         deleteEvent(oldEvent, true);
00236         status = UPDATE_FAILED;
00237     }
00238     else
00239     {
00240         if (oldEvent.copyToKOrganizer())
00241         {
00242             // Tell KOrganizer to delete its old event.
00243             // But ignore errors, because the user could have manually
00244             // deleted it since KAlarm asked KOrganizer to set it up.
00245             deleteFromKOrganizer(oldEvent.id());
00246         }
00247 
00248         // Update the event in the calendar file, and get the new event ID
00249         AlarmCalendar* cal = AlarmCalendar::activeCalendar();
00250         if (!cal->deleteEvent(oldEvent.id())
00251         ||  !cal->addEvent(const_cast<KAEvent&>(newEvent), true))
00252             status = UPDATE_FAILED;
00253         else if (!cal->save())
00254             status = SAVE_FAILED;
00255         if (status == UPDATE_OK)
00256         {
00257             if (newEvent.copyToKOrganizer())
00258             {
00259                 if (!sendToKOrganizer(newEvent))    // tell KOrganizer to show the new event
00260                     status = UPDATE_KORG_ERR;
00261             }
00262 
00263             // Update the window lists
00264             AlarmListView::modifyEvent(oldEvent.id(), newEvent, selectionView);
00265             return status;
00266         }
00267     }
00268 
00269     if (errmsgParent)
00270         displayUpdateError(errmsgParent, status, ERR_ADD, 1);
00271     return status;
00272 }
00273 
00274 /******************************************************************************
00275 * Update an active (non-expired) alarm from the calendar file and from every
00276 * main window instance.
00277 * The new event will have the same event ID as the old one.
00278 * If 'selectionView' is non-null, the selection highlight is moved to the
00279 * updated event in that listView instance.
00280 * The event is not updated in KOrganizer, since this function is called when an
00281 * existing alarm is rescheduled (due to recurrence or deferral).
00282 */
00283 UpdateStatus updateEvent(KAEvent& event, AlarmListView* selectionView, TQWidget* errmsgParent, bool archiveOnDelete, bool incRevision)
00284 {
00285     kdDebug(5950) << "KAlarm::updateEvent(): " << event.id() << endl;
00286 
00287     if (!event.valid())
00288         deleteEvent(event, archiveOnDelete);
00289     else
00290     {
00291         // Update the event in the calendar file.
00292         if (incRevision)
00293             event.incrementRevision();    // ensure alarm daemon sees the event has changed
00294         AlarmCalendar* cal = AlarmCalendar::activeCalendar();
00295         cal->updateEvent(event);
00296         if (!cal->save())
00297         {
00298             if (errmsgParent)
00299                 displayUpdateError(errmsgParent, SAVE_FAILED, ERR_ADD, 1);
00300             return SAVE_FAILED;
00301         }
00302 
00303         // Update the window lists
00304         AlarmListView::modifyEvent(event, selectionView);
00305     }
00306     return UPDATE_OK;
00307 }
00308 
00309 /******************************************************************************
00310 * Update a template in the calendar file and in every template list view.
00311 * If 'selectionView' is non-null, the selection highlight is moved to the
00312 * updated event in that listView instance.
00313 */
00314 UpdateStatus updateTemplate(const KAEvent& event, TemplateListView* selectionView, TQWidget* errmsgParent)
00315 {
00316     UpdateStatus status = UPDATE_OK;
00317     AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen();
00318     if (!cal)
00319         status = UPDATE_FAILED;
00320     else
00321     {
00322         cal->updateEvent(event);
00323         if (!cal->save())
00324             status = SAVE_FAILED;
00325         else
00326         {
00327             TemplateListView::modifyEvent(event.id(), event, selectionView);
00328             return UPDATE_OK;
00329         }
00330     }
00331 
00332     if (errmsgParent)
00333         displayUpdateError(errmsgParent, SAVE_FAILED, ERR_TEMPLATE, 1);
00334     return status;
00335 }
00336 
00337 /******************************************************************************
00338 * Delete an alarm from the calendar file and from every main window instance.
00339 * If the event is archived, the event's ID is changed to an expired ID if necessary.
00340 */
00341 UpdateStatus deleteEvent(KAEvent& event, bool archive, TQWidget* errmsgParent)
00342 {
00343     TQString id = event.id();
00344     kdDebug(5950) << "KAlarm::deleteEvent(): " << id << endl;
00345 
00346     // Update the window lists
00347     AlarmListView::deleteEvent(id);
00348 
00349     UpdateStatus status = UPDATE_OK;
00350     AlarmCalendar* cal;
00351 
00352     // Delete the event from the calendar file
00353     if (KAEvent::uidStatus(id) == KAEvent::EXPIRED)
00354     {
00355         cal = AlarmCalendar::expiredCalendarOpen();
00356         if (!cal)
00357             status = UPDATE_FAILED;
00358     }
00359     else
00360     {
00361         if (event.copyToKOrganizer())
00362         {
00363             // The event was shown in KOrganizer, so tell KOrganizer to
00364             // delete it. Note that an error could occur if the user
00365             // manually deleted it from KOrganizer since it was set up.
00366             if (!deleteFromKOrganizer(event.id()))
00367                 status = UPDATE_KORG_ERR;
00368         }
00369         if (archive  &&  event.toBeArchived())
00370             addExpiredEvent(event);     // this changes the event ID to an expired ID
00371         cal = AlarmCalendar::activeCalendar();
00372     }
00373     if (status != UPDATE_FAILED)
00374     {
00375         if (!cal->deleteEvent(id, true))   // save calendar after deleting
00376             status = SAVE_FAILED;
00377     }
00378     if (status > UPDATE_KORG_ERR  &&  errmsgParent)
00379         displayUpdateError(errmsgParent, SAVE_FAILED, ERR_DELETE, 1);
00380     return status;
00381 }
00382 
00383 /******************************************************************************
00384 * Delete a template from the calendar file and from every template list view.
00385 */
00386 UpdateStatus deleteTemplate(const KAEvent& event)
00387 {
00388     TQString id = event.id();
00389 
00390     // Delete the template from the calendar file
00391     AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen();
00392     if (!cal)
00393         return UPDATE_FAILED;
00394     if (!cal->deleteEvent(id, true))    // save calendar after deleting
00395         return SAVE_FAILED;
00396     cal->emitEmptyStatus();
00397 
00398     // Update the window lists
00399     TemplateListView::deleteEvent(id);
00400     return UPDATE_OK;
00401 }
00402 
00403 /******************************************************************************
00404 * Delete an alarm from the display calendar.
00405 */
00406 void deleteDisplayEvent(const TQString& eventID)
00407 {
00408     kdDebug(5950) << "KAlarm::deleteDisplayEvent(" << eventID << ")\n";
00409 
00410     if (KAEvent::uidStatus(eventID) == KAEvent::DISPLAYING)
00411     {
00412         AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen();
00413         if (cal)
00414             cal->deleteEvent(eventID, true);   // save calendar after deleting
00415     }
00416 }
00417 
00418 /******************************************************************************
00419 * Undelete an expired alarm, and update every main window instance.
00420 * The archive bit is set to ensure that it gets re-archived if it is deleted again.
00421 * If 'selectionView' is non-null, the selection highlight is moved to the
00422 * restored event in that listView instance.
00423 */
00424 UpdateStatus reactivateEvent(KAEvent& event, AlarmListView* selectionView, bool useEventID)
00425 {
00426     TQString id = event.id();
00427     kdDebug(5950) << "KAlarm::reactivateEvent(): " << id << endl;
00428 
00429     // Delete the event from the expired calendar file
00430     if (KAEvent::uidStatus(id) == KAEvent::EXPIRED)
00431     {
00432         TQDateTime now = TQDateTime::currentDateTime();
00433         if (event.occursAfter(now, true))
00434         {
00435             if (event.recurs()  ||  event.repeatCount())
00436                 event.setNextOccurrence(now);   // skip any recurrences in the past
00437             event.setArchive();    // ensure that it gets re-archived if it is deleted
00438 
00439             // Save the event details in the calendar file, and get the new event ID
00440             AlarmCalendar* cal = AlarmCalendar::activeCalendar();
00441             if (!cal->addEvent(event, useEventID))
00442                 return UPDATE_FAILED;
00443             if (!cal->save())
00444                 return SAVE_FAILED;
00445 
00446             UpdateStatus status = UPDATE_OK;
00447             if (event.copyToKOrganizer())
00448             {
00449                 if (!sendToKOrganizer(event))    // tell KOrganizer to show the event
00450                     status = UPDATE_KORG_ERR;
00451             }
00452 
00453             // Update the window lists
00454             AlarmListView::undeleteEvent(id, event, selectionView);
00455 
00456             cal = AlarmCalendar::expiredCalendarOpen();
00457             if (cal)
00458                 cal->deleteEvent(id, true);   // save calendar after deleting
00459             return status;
00460         }
00461     }
00462     return UPDATE_FAILED;
00463 }
00464 
00465 /******************************************************************************
00466 * Enable or disable an alarm in the calendar file and in every main window instance.
00467 * The new event will have the same event ID as the old one.
00468 * If 'selectionView' is non-null, the selection highlight is moved to the
00469 * updated event in that listView instance.
00470 */
00471 UpdateStatus enableEvent(KAEvent& event, AlarmListView* selectionView, bool enable)
00472 {
00473     kdDebug(5950) << "KAlarm::enableEvent(" << enable << "): " << event.id() << endl;
00474 
00475     if (enable != event.enabled())
00476     {
00477         event.setEnabled(enable);
00478 
00479         // Update the event in the calendar file
00480         AlarmCalendar* cal = AlarmCalendar::activeCalendar();
00481         cal->updateEvent(event);
00482         if (!cal->save())
00483             return SAVE_FAILED;
00484 
00485         // If we're disabling a display alarm, close any message window
00486         if (!enable  &&  event.displayAction())
00487         {
00488             MessageWin* win = MessageWin::findEvent(event.id());
00489             delete win;
00490         }
00491 
00492         // Update the window lists
00493         AlarmListView::modifyEvent(event, selectionView);
00494     }
00495     return UPDATE_OK;
00496 }
00497 
00498 /******************************************************************************
00499 * Display an error message about an error saving an event.
00500 */
00501 void displayUpdateError(TQWidget* parent, UpdateStatus, UpdateError code, int nAlarms)
00502 {
00503     TQString errmsg;
00504     switch (code)
00505     {
00506         case ERR_ADD:
00507             errmsg = (nAlarms > 1) ? i18n("Error saving alarms")
00508                                    : i18n("Error saving alarm");
00509             break;
00510         case ERR_DELETE:
00511             errmsg = (nAlarms > 1) ? i18n("Error deleting alarms")
00512                                    : i18n("Error deleting alarm");
00513             break;
00514         case ERR_REACTIVATE:
00515             errmsg = (nAlarms > 1) ? i18n("Error saving reactivated alarms")
00516                                    : i18n("Error saving reactivated alarm");
00517             break;
00518         case ERR_TEMPLATE:
00519             errmsg = i18n("Error saving alarm template");
00520             break;
00521     }
00522     KMessageBox::error(parent, errmsg);
00523 }
00524 
00525 /******************************************************************************
00526 * Display an error message corresponding to a specified alarm update error code.
00527 */
00528 void displayKOrgUpdateError(TQWidget* parent, KOrgUpdateError code, int nAlarms)
00529 {
00530     TQString errmsg;
00531     switch (code)
00532     {
00533         case KORG_ERR_ADD:
00534             errmsg = (nAlarms > 1) ? i18n("Unable to show alarms in KOrganizer")
00535                                    : i18n("Unable to show alarm in KOrganizer");
00536             break;
00537         case KORG_ERR_MODIFY:
00538             errmsg = i18n("Unable to update alarm in KOrganizer");
00539             break;
00540         case KORG_ERR_DELETE:
00541             errmsg = (nAlarms > 1) ? i18n("Unable to delete alarms from KOrganizer")
00542                                    : i18n("Unable to delete alarm from KOrganizer");
00543             break;
00544     }
00545     KMessageBox::error(parent, errmsg);
00546 }
00547 
00548 /******************************************************************************
00549 * Display the alarm edit dialogue to edit a specified alarm.
00550 */
00551 bool edit(const TQString& eventID)
00552 {
00553     AlarmCalendar* cal;
00554     switch (KAEvent::uidStatus(eventID))
00555     {
00556         case KAEvent::ACTIVE:
00557             cal = AlarmCalendar::activeCalendar();
00558             break;
00559         case KAEvent::TEMPLATE:
00560             cal = AlarmCalendar::templateCalendarOpen();
00561             break;
00562         default:
00563             kdError(5950) << "KAlarm::edit(" << eventID << "): event not active or template" << endl;
00564             return false;
00565     }
00566     KCal::Event* kcalEvent = cal->event(eventID);
00567     if (!kcalEvent)
00568     {
00569         kdError(5950) << "KAlarm::edit(): event ID not found: " << eventID << endl;
00570         return false;
00571     }
00572     KAEvent event(*kcalEvent);
00573     MainWindow::executeEdit(event);
00574     return true;
00575 }
00576 
00577 /******************************************************************************
00578 * Display the alarm edit dialogue to edit a new alarm, optionally preset with
00579 * a template.
00580 */
00581 bool editNew(const TQString& templateName)
00582 {
00583     bool result = true;
00584     if (!templateName.isEmpty())
00585     {
00586         AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen();
00587         if (cal)
00588         {
00589             KAEvent templateEvent = KAEvent::findTemplateName(*cal, templateName);
00590             if (templateEvent.valid())
00591             {
00592                 MainWindow::executeNew(templateEvent);
00593                 return true;
00594             }
00595             kdWarning(5950) << "KAlarm::editNew(" << templateName << "): template not found" << endl;
00596         }
00597         result = false;
00598     }
00599     MainWindow::executeNew();
00600     return result;
00601 }
00602 
00603 /******************************************************************************
00604 *  Returns a list of all alarm templates.
00605 *  If shell commands are disabled, command alarm templates are omitted.
00606 */
00607 TQValueList<KAEvent> templateList()
00608 {
00609     TQValueList<KAEvent> templates;
00610     AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen();
00611     if (cal)
00612     {
00613         bool includeCmdAlarms = ShellProcess::authorised();
00614         KCal::Event::List events = cal->events();
00615         for (KCal::Event::List::ConstIterator it = events.begin();  it != events.end();  ++it)
00616         {
00617             KCal::Event* kcalEvent = *it;
00618             KAEvent event(*kcalEvent);
00619             if (includeCmdAlarms  ||  event.action() != KAEvent::COMMAND)
00620                 templates.append(event);
00621         }
00622     }
00623     return templates;
00624 }
00625 
00626 /******************************************************************************
00627 * To be called after an alarm has been edited.
00628 * Prompt the user to re-enable alarms if they are currently disabled, and if
00629 * it's an email alarm, warn if no 'From' email address is configured.
00630 */
00631 void outputAlarmWarnings(TQWidget* parent, const KAEvent* event)
00632 {
00633     if (event  &&  event->action() == KAEvent::EMAIL
00634     &&  Preferences::emailAddress().isEmpty())
00635         KMessageBox::information(parent, i18n("Please set the 'From' email address...",
00636                                               "%1\nPlease set it in the Preferences dialog.").arg(KAMail::i18n_NeedFromEmailAddress()));
00637 
00638     if (!Daemon::monitoringAlarms())
00639     {
00640         if (KMessageBox::warningYesNo(parent, i18n("Alarms are currently disabled.\nDo you want to enable alarms now?"),
00641                                       TQString(), i18n("Enable"), i18n("Keep Disabled"),
00642                                       TQString::fromLatin1("EditEnableAlarms"))
00643                         == KMessageBox::Yes)
00644             Daemon::setAlarmsEnabled();
00645     }
00646 }
00647 
00648 /******************************************************************************
00649 * Reset the alarm daemon and reload the calendar.
00650 * If the daemon is not already running, start it.
00651 */
00652 void resetDaemon()
00653 {
00654     kdDebug(5950) << "KAlarm::resetDaemon()" << endl;
00655     if (!resetDaemonQueued)
00656     {
00657         resetDaemonQueued = true;
00658         theApp()->processQueue();
00659     }
00660 }
00661 
00662 /******************************************************************************
00663 * This method must only be called from the main KAlarm queue processing loop,
00664 * to prevent asynchronous calendar operations interfering with one another.
00665 *
00666 * If resetDaemon() has been called, reset the alarm daemon and reload the calendars.
00667 * If the daemon is not already running, start it.
00668 */
00669 void resetDaemonIfQueued()
00670 {
00671     if (resetDaemonQueued)
00672     {
00673         kdDebug(5950) << "KAlarm::resetDaemonIfNeeded()" << endl;
00674         AlarmCalendar::activeCalendar()->reload();
00675         AlarmCalendar::expiredCalendar()->reload();
00676 
00677         // Close any message windows for alarms which are now disabled
00678         KAEvent event;
00679         KCal::Event::List events = AlarmCalendar::activeCalendar()->events();
00680         for (KCal::Event::List::ConstIterator it = events.begin();  it != events.end();  ++it)
00681         {
00682             KCal::Event* kcalEvent = *it;
00683             event.set(*kcalEvent);
00684             if (!event.enabled()  &&  event.displayAction())
00685             {
00686                 MessageWin* win = MessageWin::findEvent(event.id());
00687                 delete win;
00688             }
00689         }
00690 
00691         MainWindow::refresh();
00692         if (!Daemon::reset())
00693             Daemon::start();
00694         resetDaemonQueued = false;
00695     }
00696 }
00697 
00698 /******************************************************************************
00699 *  Start KMail if it isn't already running, and optionally iconise it.
00700 *  Reply = reason for failure to run KMail (which may be the empty string)
00701 *        = null string if success.
00702 */
00703 TQString runKMail(bool minimise)
00704 {
00705     TQCString dcopName;
00706     TQString errmsg;
00707     if (!runProgram("kmail", (minimise ? KMAIL_DCOP_WINDOW : ""), dcopName, errmsg))
00708         return i18n("Unable to start KMail\n(%1)").arg(errmsg);
00709     return TQString();
00710 }
00711 
00712 /******************************************************************************
00713 *  Start another program for DCOP access if it isn't already running.
00714 *  If 'windowName' is not empty, the program's window of that name is iconised.
00715 *  On exit, 'dcopName' contains the DCOP name to access the application, and
00716 *  'errorMessage' contains an error message if failure.
00717 *  Reply = true if the program is now running.
00718 */
00719 bool runProgram(const TQCString& program, const TQCString& windowName, TQCString& dcopName, TQString& errorMessage)
00720 {
00721     if (!kapp->dcopClient()->isApplicationRegistered(program))
00722     {
00723         // KOrganizer is not already running, so start it
00724         if (KApplication::startServiceByDesktopName(TQString::fromLatin1(program), TQString(), &errorMessage, &dcopName))
00725         {
00726             kdError(5950) << "runProgram(): couldn't start " << program << " (" << errorMessage << ")\n";
00727             return false;
00728         }
00729         // Minimise its window - don't use hide() since this would remove all
00730         // trace of it from the panel if it is not configured to be docked in
00731         // the system tray.
00732         kapp->dcopClient()->send(dcopName, windowName, "minimize()", TQString());
00733     }
00734     else if (dcopName.isEmpty())
00735         dcopName = program;
00736     errorMessage = TQString();
00737     return true;
00738 }
00739 
00740 /******************************************************************************
00741 *  Read the size for the specified window from the config file, for the
00742 *  current screen resolution.
00743 *  Reply = true if size set in the config file, in which case 'result' is set
00744 *        = false if no size is set, in which case 'result' is unchanged.
00745 */
00746 bool readConfigWindowSize(const char* window, TQSize& result)
00747 {
00748     KConfig* config = KGlobal::config();
00749     config->setGroup(TQString::fromLatin1(window));
00750     TQWidget* desktop = TQT_TQWIDGET(KApplication::desktop());
00751     TQSize s = TQSize(config->readNumEntry(TQString::fromLatin1("Width %1").arg(desktop->width()), 0),
00752                     config->readNumEntry(TQString::fromLatin1("Height %1").arg(desktop->height()), 0));
00753     if (s.isEmpty())
00754         return false;
00755     result = s;
00756     return true;
00757 }
00758 
00759 /******************************************************************************
00760 *  Write the size for the specified window to the config file, for the
00761 *  current screen resolution.
00762 */
00763 void writeConfigWindowSize(const char* window, const TQSize& size)
00764 {
00765     KConfig* config = KGlobal::config();
00766     config->setGroup(TQString::fromLatin1(window));
00767     TQWidget* desktop = TQT_TQWIDGET(KApplication::desktop());
00768     config->writeEntry(TQString::fromLatin1("Width %1").arg(desktop->width()), size.width());
00769     config->writeEntry(TQString::fromLatin1("Height %1").arg(desktop->height()), size.height());
00770     config->sync();
00771 }
00772 
00773 /******************************************************************************
00774 * Return the current KAlarm version number.
00775 */
00776 int Version()
00777 {
00778     static int version = 0;
00779     if (!version)
00780         version = getVersionNumber(KALARM_VERSION);
00781     return version;
00782 }
00783 
00784 /******************************************************************************
00785 * Convert the supplied KAlarm version string to a version number.
00786 * Reply = version number (double digit for each of major, minor & issue number,
00787 *         e.g. 010203 for 1.2.3
00788 *       = 0 if invalid version string.
00789 */
00790 int getVersionNumber(const TQString& version, TQString* subVersion)
00791 {
00792     // N.B. Remember to change  Version(int major, int minor, int rev)
00793     //      if the representation returned by this method changes.
00794     if (subVersion)
00795         *subVersion = TQString();
00796     int count = version.contains('.') + 1;
00797     if (count < 2)
00798         return 0;
00799     bool ok;
00800     unsigned vernum = version.section('.', 0, 0).toUInt(&ok) * 10000;  // major version
00801     if (!ok)
00802         return 0;
00803     unsigned v = version.section('.', 1, 1).toUInt(&ok);               // minor version
00804     if (!ok)
00805         return 0;
00806     vernum += (v < 99 ? v : 99) * 100;
00807     if (count >= 3)
00808     {
00809         // Issue number: allow other characters to follow the last digit
00810         TQString issue = version.section('.', 2);
00811         if (!issue.at(0).isDigit())
00812             return 0;
00813         int n = issue.length();
00814         int i;
00815         for (i = 0;  i < n && issue.at(i).isDigit();  ++i) ;
00816         if (subVersion)
00817             *subVersion = issue.mid(i);
00818         v = issue.left(i).toUInt();   // issue number
00819         vernum += (v < 99 ? v : 99);
00820     }
00821     return vernum;
00822 }
00823 
00824 /******************************************************************************
00825 * Check from its mime type whether a file appears to be a text or image file.
00826 * If a text file, its type is distinguished.
00827 * Reply = file type.
00828 */
00829 FileType fileType(const TQString& mimetype)
00830 {
00831     static const char* applicationTypes[] = {
00832         "x-shellscript", "x-nawk", "x-awk", "x-perl", "x-python",
00833         "x-desktop", "x-troff", 0 };
00834     static const char* formattedTextTypes[] = {
00835         "html", "xml", 0 };
00836 
00837     if (mimetype.startsWith(TQString::fromLatin1("image/")))
00838         return Image;
00839     int slash = mimetype.find('/');
00840     if (slash < 0)
00841         return Unknown;
00842     TQString type = mimetype.mid(slash + 1);
00843     const char* typel = type.latin1();
00844     if (mimetype.startsWith(TQString::fromLatin1("application")))
00845     {
00846         for (int i = 0;  applicationTypes[i];  ++i)
00847             if (!strcmp(typel, applicationTypes[i]))
00848                 return TextApplication;
00849     }
00850     else if (mimetype.startsWith(TQString::fromLatin1("text")))
00851     {
00852         for (int i = 0;  formattedTextTypes[i];  ++i)
00853             if (!strcmp(typel, formattedTextTypes[i]))
00854                 return TextFormatted;
00855         return TextPlain;
00856     }
00857     return Unknown;
00858 }
00859 
00860 /******************************************************************************
00861 * Display a modal dialogue to choose an existing file, initially highlighting
00862 * any specified file.
00863 * @param initialFile The file to initially highlight - must be a full path name or URL.
00864 * @param defaultDir The directory to start in if @p initialFile is empty. If empty,
00865 *                   the user's home directory will be used. Updated to the
00866 *                   directory containing the selected file, if a file is chosen.
00867 * @param mode OR of KFile::Mode values, e.g. ExistingOnly, LocalOnly.
00868 * Reply = URL selected. If none is selected, URL.isEmpty() is true.
00869 */
00870 TQString browseFile(const TQString& caption, TQString& defaultDir, const TQString& initialFile,
00871                    const TQString& filter, int mode, TQWidget* parent, const char* name)
00872 {
00873     TQString initialDir = !initialFile.isEmpty() ? TQString(initialFile).remove(TQRegExp("/[^/]*$"))
00874                        : !defaultDir.isEmpty()  ? defaultDir
00875                        :                          TQDir::homeDirPath();
00876     KFileDialog fileDlg(initialDir, filter, parent, name, true);
00877     fileDlg.setOperationMode(mode & KFile::ExistingOnly ? KFileDialog::Opening : KFileDialog::Saving);
00878     fileDlg.setMode(KFile::File | mode);
00879     fileDlg.setCaption(caption);
00880     if (!initialFile.isEmpty())
00881         fileDlg.setSelection(initialFile);
00882     if (fileDlg.exec() != TQDialog::Accepted)
00883         return TQString();
00884     KURL url = fileDlg.selectedURL();
00885     defaultDir = url.path();
00886     return (mode & KFile::LocalOnly) ? url.path() : url.prettyURL();
00887 }
00888 
00889 /******************************************************************************
00890 *  Return the first day of the week for the user's locale.
00891 *  Reply = 1 (Mon) .. 7 (Sun).
00892 */
00893 int localeFirstDayOfWeek()
00894 {
00895     static int firstDay = 0;
00896     if (!firstDay)
00897         firstDay = KGlobal::locale()->weekStartDay();
00898     return firstDay;
00899 }
00900 
00901 /******************************************************************************
00902 *  Return the supplied string with any accelerator code stripped out.
00903 */
00904 TQString stripAccel(const TQString& text)
00905 {
00906     unsigned len = text.length();
00907     TQString out = TQDeepCopy<TQString>(text);
00908     TQChar *corig = (TQChar*)out.unicode();
00909     TQChar *cout  = corig;
00910     TQChar *cin   = cout;
00911     while (len)
00912     {
00913         if ( *cin == '&' )
00914         {
00915             ++cin;
00916             --len;
00917             if ( !len )
00918                 break;
00919         }
00920         *cout = *cin;
00921         ++cout;
00922         ++cin;
00923         --len;
00924     }
00925     unsigned newlen = cout - corig;
00926     if (newlen != out.length())
00927         out.truncate(newlen);
00928     return out;
00929 }
00930 
00931 } // namespace KAlarm
00932 
00933 
00934 namespace {
00935 
00936 /******************************************************************************
00937 *  Tell KOrganizer to put an alarm in its calendar.
00938 *  It will be held by KOrganizer as a simple event, without alarms - KAlarm
00939 *  is still responsible for alarming.
00940 */
00941 bool sendToKOrganizer(const KAEvent& event)
00942 {
00943     KCal::Event* kcalEvent = event.event();
00944     TQString uid = KAEvent::uid(event.id(), KAEvent::KORGANIZER);
00945     kcalEvent->setUid(uid);
00946     kcalEvent->clearAlarms();
00947     TQString userEmail;
00948     switch (event.action())
00949     {
00950         case KAEvent::MESSAGE:
00951         case KAEvent::FILE:
00952         case KAEvent::COMMAND:
00953             kcalEvent->setSummary(event.cleanText());
00954             userEmail = Preferences::emailAddress();
00955             break;
00956         case KAEvent::EMAIL:
00957         {
00958             TQString from = event.emailFromId()
00959                          ? KAMail::identityManager()->identityForUoid(event.emailFromId()).fullEmailAddr()
00960                          : Preferences::emailAddress();
00961             AlarmText atext;
00962             atext.setEmail(event.emailAddresses(", "), from, TQString(), TQString(), event.emailSubject(), TQString());
00963             kcalEvent->setSummary(atext.displayText());
00964             userEmail = from;
00965             break;
00966         }
00967     }
00968     kcalEvent->setOrganizer(KCal::Person(TQString(), userEmail));
00969 
00970     // Translate the event into string format
00971     KCal::ICalFormat format;
00972     format.setTimeZone(TQString(), false);
00973     TQString iCal = format.toICalString(kcalEvent);
00974 kdDebug(5950)<<"Korg->"<<iCal<<endl;
00975     delete kcalEvent;
00976 
00977     // Send the event to KOrganizer
00978     if (!runKOrganizer())     // start KOrganizer if it isn't already running
00979         return false;
00980     TQByteArray  data, replyData;
00981     TQCString    replyType;
00982     TQDataStream arg(data, IO_WriteOnly);
00983     arg << iCal;
00984     if (kapp->dcopClient()->call(korganizerName, KORG_DCOP_OBJECT, "addIncidence(TQString)", data, replyType, replyData)
00985     &&  replyType == "bool")
00986     {
00987         bool result;
00988         TQDataStream reply(replyData, IO_ReadOnly);
00989         reply >> result;
00990         if (result)
00991         {
00992             kdDebug(5950) << "sendToKOrganizer(" << uid << "): success\n";
00993             return true;
00994         }
00995     }
00996     kdError(5950) << "sendToKOrganizer(): KOrganizer addEvent(" << uid << ") dcop call failed\n";
00997     return false;
00998 }
00999 
01000 /******************************************************************************
01001 *  Tell KOrganizer to delete an event from its calendar.
01002 */
01003 bool deleteFromKOrganizer(const TQString& eventID)
01004 {
01005     if (!runKOrganizer())     // start KOrganizer if it isn't already running
01006         return false;
01007     TQString newID = KAEvent::uid(eventID, KAEvent::KORGANIZER);
01008     TQByteArray  data, replyData;
01009     TQCString    replyType;
01010     TQDataStream arg(data, IO_WriteOnly);
01011     arg << newID << true;
01012     if (kapp->dcopClient()->call(korganizerName, KORG_DCOP_OBJECT, "deleteIncidence(TQString,bool)", data, replyType, replyData)
01013     &&  replyType == "bool")
01014     {
01015         bool result;
01016         TQDataStream reply(replyData, IO_ReadOnly);
01017         reply >> result;
01018         if (result)
01019         {
01020             kdDebug(5950) << "deleteFromKOrganizer(" << newID << "): success\n";
01021             return true;
01022         }
01023     }
01024     kdError(5950) << "sendToKOrganizer(): KOrganizer deleteEvent(" << newID << ") dcop call failed\n";
01025     return false;
01026 }
01027 
01028 /******************************************************************************
01029 * Start KOrganizer if not already running, and create its DCOP interface.
01030 */
01031 bool runKOrganizer()
01032 {
01033     TQString error;
01034     TQCString dcopService;
01035     int result = KDCOPServiceStarter::self()->findServiceFor("DCOP/Organizer", TQString(), TQString(), &error, &dcopService);
01036     if (result)
01037     {
01038         kdDebug(5950) << "Unable to start DCOP/Organizer: " << dcopService << " " << error << endl;
01039         return false;
01040     }
01041     // If Kontact is running, there is be a load() method which needs to be called
01042     // to load KOrganizer into Kontact. But if KOrganizer is running independently,
01043     // the load() method doesn't exist.
01044     TQCString dummy;
01045     if (!kapp->dcopClient()->findObject(dcopService, KORG_DCOP_OBJECT, "", TQByteArray(), dummy, dummy))
01046     {
01047         DCOPRef ref(dcopService, dcopService); // talk to the KUniqueApplication or its Kontact wrapper
01048         DCOPReply reply = ref.call("load()");
01049         if (!reply.isValid() || !(bool)reply)
01050         {
01051             kdWarning(5950) << "Error loading " << dcopService << endl;
01052             return false;
01053         }
01054         if (!kapp->dcopClient()->findObject(dcopService, KORG_DCOP_OBJECT, "", TQByteArray(), dummy, dummy))
01055         {
01056             kdWarning(5950) << "Unable to access KOrganizer's "KORG_DCOP_OBJECT" DCOP object" << endl;
01057             return false;
01058         }
01059     }
01060     return true;
01061 }
01062 
01063 } // namespace
01064 
01065 #ifdef HAVE_XTEST
01066 #include <X11/keysym.h>
01067 #include <X11/extensions/XTest.h>
01068 #include <tqwindowdefs.h>
01069 
01070 /******************************************************************************
01071 * Cancel the screen saver, in case it is active.
01072 * Only implemented if the X11 XTest extension is installed.
01073 */
01074 void x11_cancelScreenSaver()
01075 {
01076     kdDebug(5950) << "KAlarm::cancelScreenSaver()" << endl;
01077     Display* display = qt_xdisplay();
01078     static int XTestKeyCode = 0;
01079     if (!XTestKeyCode)
01080         XTestKeyCode = XKeysymToKeycode(display, XK_Shift_L);
01081     XTestFakeKeyEvent(display, XTestKeyCode, true, CurrentTime);
01082     XTestFakeKeyEvent(display, XTestKeyCode, false, CurrentTime);
01083     XSync(display, false);
01084 }
01085 #endif // HAVE_XTEST