kalarm

functions.cpp
1/*
2 * functions.cpp - miscellaneous functions
3 * Program: kalarm
4 * Copyright © 2001-2009 by David Jarvie <djarvie@kde.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21#include "kalarm.h"
22#include "functions.h"
23
24#include "alarmcalendar.h"
25#include "alarmevent.h"
26#include "alarmlistview.h"
27#include "daemon.h"
28#include "kalarmapp.h"
29#include "kamail.h"
30#include "mainwindow.h"
31#include "messagewin.h"
32#include "preferences.h"
33#include "shellprocess.h"
34#include "templatelistview.h"
35#include "templatemenuaction.h"
36
37#include <tqdeepcopy.h>
38#include <tqdir.h>
39#include <tqregexp.h>
40
41#include <tdeconfig.h>
42#include <tdeaction.h>
43#include <tdeglobal.h>
44#include <tdelocale.h>
45#include <kstdguiitem.h>
46#include <tdestdaccel.h>
47#include <tdemessagebox.h>
48#include <tdefiledialog.h>
49#include <dcopclient.h>
50#include <dcopref.h>
51#include <kdcopservicestarter.h>
52#include <kdebug.h>
53
54#include <libkcal/event.h>
55#include <libkcal/icalformat.h>
56#include <libkpimidentities/identitymanager.h>
57#include <libkpimidentities/identity.h>
58#include <libkcal/person.h>
59
60
61namespace
62{
63bool resetDaemonQueued = false;
64TQCString korganizerName = "korganizer";
65TQString korgStartError;
66#define KORG_DCOP_OBJECT "KOrganizerIface"
67//const char* KORG_DCOP_WINDOW = "KOrganizer MainWindow"; // not used
68const char* KMAIL_DCOP_WINDOW = "kmail-mainwindow#1";
69
70bool sendToKOrganizer(const KAEvent&);
71bool deleteFromKOrganizer(const TQString& eventID);
72bool runKOrganizer();
73}
74
75
76namespace KAlarm
77{
78
79/******************************************************************************
80* Display a main window with the specified event selected.
81*/
82MainWindow* displayMainWindowSelected(const TQString& eventID)
83{
84 MainWindow* win = MainWindow::firstWindow();
85 if (!win)
86 {
87 if (theApp()->checkCalendarDaemon()) // ensure calendar is open and daemon started
88 {
89 win = MainWindow::create();
90 win->show();
91 }
92 }
93 else
94 {
95 // There is already a main window, so make it the active window
96 bool visible = win->isVisible();
97 if (visible)
98 win->hide(); // in case it's on a different desktop
99 if (!visible || win->isMinimized())
100 win->showNormal();
101 win->raise();
102 win->setActiveWindow();
103 }
104 if (win && !eventID.isEmpty())
105 win->selectEvent(eventID);
106 return win;
107}
108
109/******************************************************************************
110* Create a New Alarm TDEAction.
111*/
112TDEAction* createNewAlarmAction(const TQString& label, TQObject* receiver, const char* slot, TDEActionCollection* actions, const char* name)
113{
114 return new TDEAction(label, "document-new", TDEStdAccel::openNew(), receiver, slot, actions, name);
115}
116
117/******************************************************************************
118* Create a New From Template TDEAction.
119*/
120TemplateMenuAction* createNewFromTemplateAction(const TQString& label, TQObject* receiver, const char* slot, TDEActionCollection* actions, const char* name)
121{
122 return new TemplateMenuAction(label, "new_from_template", receiver, slot, actions, name);
123}
124
125/******************************************************************************
126* Add a new active (non-expired) alarm.
127* Save it in the calendar file and add it to every main window instance.
128* If 'selectionView' is non-null, the selection highlight is moved to the new
129* event in that listView instance.
130* 'event' is updated with the actual event ID.
131*/
132UpdateStatus addEvent(KAEvent& event, AlarmListView* selectionView, TQWidget* errmsgParent, bool useEventID, bool allowKOrgUpdate)
133{
134 kdDebug(5950) << "KAlarm::addEvent(): " << event.id() << endl;
135 UpdateStatus status = UPDATE_OK;
136 if (!theApp()->checkCalendarDaemon()) // ensure calendar is open and daemon started
137 return UPDATE_FAILED;
138 else
139 {
140 // Save the event details in the calendar file, and get the new event ID
141 AlarmCalendar* cal = AlarmCalendar::activeCalendar();
142 if (!cal->addEvent(event, useEventID))
143 status = UPDATE_FAILED;
144 else if (!cal->save())
145 status = SAVE_FAILED;
146 }
147 if (status == UPDATE_OK)
148 {
149 if (allowKOrgUpdate && event.copyToKOrganizer())
150 {
151 if (!sendToKOrganizer(event)) // tell KOrganizer to show the event
152 status = UPDATE_KORG_ERR;
153 }
154
155 // Update the window lists
156 AlarmListView::addEvent(event, selectionView);
157 return status;
158 }
159
160 if (errmsgParent)
161 displayUpdateError(errmsgParent, status, ERR_ADD, 1);
162 return status;
163}
164
165/******************************************************************************
166* Save the event in the expired calendar file and adjust every main window instance.
167* The event's ID is changed to an expired ID if necessary.
168*/
169bool addExpiredEvent(KAEvent& event)
170{
171 kdDebug(5950) << "KAlarm::addExpiredEvent(" << event.id() << ")\n";
172 AlarmCalendar* cal = AlarmCalendar::expiredCalendarOpen();
173 if (!cal)
174 return false;
175 bool archiving = (KAEvent::uidStatus(event.id()) == KAEvent::ACTIVE);
176 if (archiving)
177 event.setSaveDateTime(TQDateTime::currentDateTime()); // time stamp to control purging
178 KCal::Event* kcalEvent = cal->addEvent(event);
179 cal->save();
180
181 // Update window lists
182 if (!archiving)
183 AlarmListView::addEvent(event, 0);
184 else if (kcalEvent)
185 AlarmListView::modifyEvent(KAEvent(*kcalEvent), 0);
186 return true;
187}
188
189/******************************************************************************
190* Add a new template.
191* Save it in the calendar file and add it to every template list view.
192* If 'selectionView' is non-null, the selection highlight is moved to the new
193* event in that listView instance.
194* 'event' is updated with the actual event ID.
195*/
196UpdateStatus addTemplate(KAEvent& event, TemplateListView* selectionView, TQWidget* errmsgParent)
197{
198 kdDebug(5950) << "KAlarm::addTemplate(): " << event.id() << endl;
199 UpdateStatus status = UPDATE_OK;
200
201 // Add the template to the calendar file
202 AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen();
203 if (!cal || !cal->addEvent(event))
204 status = UPDATE_FAILED;
205 else if (!cal->save())
206 status = SAVE_FAILED;
207 else
208 {
209 cal->emitEmptyStatus();
210
211 // Update the window lists
212 TemplateListView::addEvent(event, selectionView);
213 return UPDATE_OK;
214 }
215
216 if (errmsgParent)
217 displayUpdateError(errmsgParent, status, ERR_TEMPLATE, 1);
218 return status;
219}
220
221/******************************************************************************
222* Modify an active (non-expired) alarm in the calendar file and in every main
223* window instance.
224* The new event will have a different event ID from the old one.
225* If 'selectionView' is non-null, the selection highlight is moved to the
226* modified event in that listView instance.
227*/
228UpdateStatus modifyEvent(KAEvent& oldEvent, const KAEvent& newEvent, AlarmListView* selectionView, TQWidget* errmsgParent)
229{
230 kdDebug(5950) << "KAlarm::modifyEvent(): '" << oldEvent.id() << endl;
231
232 UpdateStatus status = UPDATE_OK;
233 if (!newEvent.valid())
234 {
235 deleteEvent(oldEvent, true);
236 status = UPDATE_FAILED;
237 }
238 else
239 {
240 if (oldEvent.copyToKOrganizer())
241 {
242 // Tell KOrganizer to delete its old event.
243 // But ignore errors, because the user could have manually
244 // deleted it since KAlarm asked KOrganizer to set it up.
245 deleteFromKOrganizer(oldEvent.id());
246 }
247
248 // Update the event in the calendar file, and get the new event ID
249 AlarmCalendar* cal = AlarmCalendar::activeCalendar();
250 if (!cal->deleteEvent(oldEvent.id())
251 || !cal->addEvent(const_cast<KAEvent&>(newEvent), true))
252 status = UPDATE_FAILED;
253 else if (!cal->save())
254 status = SAVE_FAILED;
255 if (status == UPDATE_OK)
256 {
257 if (newEvent.copyToKOrganizer())
258 {
259 if (!sendToKOrganizer(newEvent)) // tell KOrganizer to show the new event
260 status = UPDATE_KORG_ERR;
261 }
262
263 // Update the window lists
264 AlarmListView::modifyEvent(oldEvent.id(), newEvent, selectionView);
265 return status;
266 }
267 }
268
269 if (errmsgParent)
270 displayUpdateError(errmsgParent, status, ERR_ADD, 1);
271 return status;
272}
273
274/******************************************************************************
275* Update an active (non-expired) alarm from the calendar file and from every
276* main window instance.
277* The new event will have the same event ID as the old one.
278* If 'selectionView' is non-null, the selection highlight is moved to the
279* updated event in that listView instance.
280* The event is not updated in KOrganizer, since this function is called when an
281* existing alarm is rescheduled (due to recurrence or deferral).
282*/
283UpdateStatus updateEvent(KAEvent& event, AlarmListView* selectionView, TQWidget* errmsgParent, bool archiveOnDelete, bool incRevision)
284{
285 kdDebug(5950) << "KAlarm::updateEvent(): " << event.id() << endl;
286
287 if (!event.valid())
288 deleteEvent(event, archiveOnDelete);
289 else
290 {
291 // Update the event in the calendar file.
292 if (incRevision)
293 event.incrementRevision(); // ensure alarm daemon sees the event has changed
294 AlarmCalendar* cal = AlarmCalendar::activeCalendar();
295 cal->updateEvent(event);
296 if (!cal->save())
297 {
298 if (errmsgParent)
299 displayUpdateError(errmsgParent, SAVE_FAILED, ERR_ADD, 1);
300 return SAVE_FAILED;
301 }
302
303 // Update the window lists
304 AlarmListView::modifyEvent(event, selectionView);
305 }
306 return UPDATE_OK;
307}
308
309/******************************************************************************
310* Update a template in the calendar file and in every template list view.
311* If 'selectionView' is non-null, the selection highlight is moved to the
312* updated event in that listView instance.
313*/
314UpdateStatus updateTemplate(const KAEvent& event, TemplateListView* selectionView, TQWidget* errmsgParent)
315{
316 UpdateStatus status = UPDATE_OK;
317 AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen();
318 if (!cal)
319 status = UPDATE_FAILED;
320 else
321 {
322 cal->updateEvent(event);
323 if (!cal->save())
324 status = SAVE_FAILED;
325 else
326 {
327 TemplateListView::modifyEvent(event.id(), event, selectionView);
328 return UPDATE_OK;
329 }
330 }
331
332 if (errmsgParent)
333 displayUpdateError(errmsgParent, SAVE_FAILED, ERR_TEMPLATE, 1);
334 return status;
335}
336
337/******************************************************************************
338* Delete an alarm from the calendar file and from every main window instance.
339* If the event is archived, the event's ID is changed to an expired ID if necessary.
340*/
341UpdateStatus deleteEvent(KAEvent& event, bool archive, TQWidget* errmsgParent)
342{
343 TQString id = event.id();
344 kdDebug(5950) << "KAlarm::deleteEvent(): " << id << endl;
345
346 // Update the window lists
347 AlarmListView::deleteEvent(id);
348
349 UpdateStatus status = UPDATE_OK;
350 AlarmCalendar* cal;
351
352 // Delete the event from the calendar file
353 if (KAEvent::uidStatus(id) == KAEvent::EXPIRED)
354 {
355 cal = AlarmCalendar::expiredCalendarOpen();
356 if (!cal)
357 status = UPDATE_FAILED;
358 }
359 else
360 {
361 if (event.copyToKOrganizer())
362 {
363 // The event was shown in KOrganizer, so tell KOrganizer to
364 // delete it. Note that an error could occur if the user
365 // manually deleted it from KOrganizer since it was set up.
366 if (!deleteFromKOrganizer(event.id()))
367 status = UPDATE_KORG_ERR;
368 }
369 if (archive && event.toBeArchived())
370 addExpiredEvent(event); // this changes the event ID to an expired ID
371 cal = AlarmCalendar::activeCalendar();
372 }
373 if (status != UPDATE_FAILED)
374 {
375 if (!cal->deleteEvent(id, true)) // save calendar after deleting
376 status = SAVE_FAILED;
377 }
378 if (status > UPDATE_KORG_ERR && errmsgParent)
379 displayUpdateError(errmsgParent, SAVE_FAILED, ERR_DELETE, 1);
380 return status;
381}
382
383/******************************************************************************
384* Delete a template from the calendar file and from every template list view.
385*/
386UpdateStatus deleteTemplate(const KAEvent& event)
387{
388 TQString id = event.id();
389
390 // Delete the template from the calendar file
391 AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen();
392 if (!cal)
393 return UPDATE_FAILED;
394 if (!cal->deleteEvent(id, true)) // save calendar after deleting
395 return SAVE_FAILED;
396 cal->emitEmptyStatus();
397
398 // Update the window lists
399 TemplateListView::deleteEvent(id);
400 return UPDATE_OK;
401}
402
403/******************************************************************************
404* Delete an alarm from the display calendar.
405*/
406void deleteDisplayEvent(const TQString& eventID)
407{
408 kdDebug(5950) << "KAlarm::deleteDisplayEvent(" << eventID << ")\n";
409
410 if (KAEvent::uidStatus(eventID) == KAEvent::DISPLAYING)
411 {
412 AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen();
413 if (cal)
414 cal->deleteEvent(eventID, true); // save calendar after deleting
415 }
416}
417
418/******************************************************************************
419* Undelete an expired alarm, and update every main window instance.
420* The archive bit is set to ensure that it gets re-archived if it is deleted again.
421* If 'selectionView' is non-null, the selection highlight is moved to the
422* restored event in that listView instance.
423*/
424UpdateStatus reactivateEvent(KAEvent& event, AlarmListView* selectionView, bool useEventID)
425{
426 TQString id = event.id();
427 kdDebug(5950) << "KAlarm::reactivateEvent(): " << id << endl;
428
429 // Delete the event from the expired calendar file
430 if (KAEvent::uidStatus(id) == KAEvent::EXPIRED)
431 {
432 TQDateTime now = TQDateTime::currentDateTime();
433 if (event.occursAfter(now, true))
434 {
435 if (event.recurs() || event.repeatCount())
436 event.setNextOccurrence(now); // skip any recurrences in the past
437 event.setArchive(); // ensure that it gets re-archived if it is deleted
438
439 // Save the event details in the calendar file, and get the new event ID
440 AlarmCalendar* cal = AlarmCalendar::activeCalendar();
441 if (!cal->addEvent(event, useEventID))
442 return UPDATE_FAILED;
443 if (!cal->save())
444 return SAVE_FAILED;
445
446 UpdateStatus status = UPDATE_OK;
447 if (event.copyToKOrganizer())
448 {
449 if (!sendToKOrganizer(event)) // tell KOrganizer to show the event
450 status = UPDATE_KORG_ERR;
451 }
452
453 // Update the window lists
454 AlarmListView::undeleteEvent(id, event, selectionView);
455
456 cal = AlarmCalendar::expiredCalendarOpen();
457 if (cal)
458 cal->deleteEvent(id, true); // save calendar after deleting
459 return status;
460 }
461 }
462 return UPDATE_FAILED;
463}
464
465/******************************************************************************
466* Enable or disable an alarm in the calendar file and in every main window instance.
467* The new event will have the same event ID as the old one.
468* If 'selectionView' is non-null, the selection highlight is moved to the
469* updated event in that listView instance.
470*/
471UpdateStatus enableEvent(KAEvent& event, AlarmListView* selectionView, bool enable)
472{
473 kdDebug(5950) << "KAlarm::enableEvent(" << enable << "): " << event.id() << endl;
474
475 if (enable != event.enabled())
476 {
477 event.setEnabled(enable);
478
479 // Update the event in the calendar file
480 AlarmCalendar* cal = AlarmCalendar::activeCalendar();
481 cal->updateEvent(event);
482 if (!cal->save())
483 return SAVE_FAILED;
484
485 // If we're disabling a display alarm, close any message window
486 if (!enable && event.displayAction())
487 {
488 MessageWin* win = MessageWin::findEvent(event.id());
489 delete win;
490 }
491
492 // Update the window lists
493 AlarmListView::modifyEvent(event, selectionView);
494 }
495 return UPDATE_OK;
496}
497
498/******************************************************************************
499* Display an error message about an error saving an event.
500*/
501void displayUpdateError(TQWidget* parent, UpdateStatus, UpdateError code, int nAlarms)
502{
503 TQString errmsg;
504 switch (code)
505 {
506 case ERR_ADD:
507 errmsg = (nAlarms > 1) ? i18n("Error saving alarms")
508 : i18n("Error saving alarm");
509 break;
510 case ERR_DELETE:
511 errmsg = (nAlarms > 1) ? i18n("Error deleting alarms")
512 : i18n("Error deleting alarm");
513 break;
514 case ERR_REACTIVATE:
515 errmsg = (nAlarms > 1) ? i18n("Error saving reactivated alarms")
516 : i18n("Error saving reactivated alarm");
517 break;
518 case ERR_TEMPLATE:
519 errmsg = i18n("Error saving alarm template");
520 break;
521 }
522 KMessageBox::error(parent, errmsg);
523}
524
525/******************************************************************************
526* Display an error message corresponding to a specified alarm update error code.
527*/
528void displayKOrgUpdateError(TQWidget* parent, KOrgUpdateError code, int nAlarms)
529{
530 TQString errmsg;
531 switch (code)
532 {
533 case KORG_ERR_ADD:
534 errmsg = (nAlarms > 1) ? i18n("Unable to show alarms in KOrganizer")
535 : i18n("Unable to show alarm in KOrganizer");
536 break;
537 case KORG_ERR_MODIFY:
538 errmsg = i18n("Unable to update alarm in KOrganizer");
539 break;
540 case KORG_ERR_DELETE:
541 errmsg = (nAlarms > 1) ? i18n("Unable to delete alarms from KOrganizer")
542 : i18n("Unable to delete alarm from KOrganizer");
543 break;
544 }
545 KMessageBox::error(parent, errmsg);
546}
547
548/******************************************************************************
549* Display the alarm edit dialogue to edit a specified alarm.
550*/
551bool edit(const TQString& eventID)
552{
553 AlarmCalendar* cal;
554 switch (KAEvent::uidStatus(eventID))
555 {
556 case KAEvent::ACTIVE:
557 cal = AlarmCalendar::activeCalendar();
558 break;
559 case KAEvent::TEMPLATE:
560 cal = AlarmCalendar::templateCalendarOpen();
561 break;
562 default:
563 kdError(5950) << "KAlarm::edit(" << eventID << "): event not active or template" << endl;
564 return false;
565 }
566 KCal::Event* kcalEvent = cal->event(eventID);
567 if (!kcalEvent)
568 {
569 kdError(5950) << "KAlarm::edit(): event ID not found: " << eventID << endl;
570 return false;
571 }
572 KAEvent event(*kcalEvent);
573 MainWindow::executeEdit(event);
574 return true;
575}
576
577/******************************************************************************
578* Display the alarm edit dialogue to edit a new alarm, optionally preset with
579* a template.
580*/
581bool editNew(const TQString& templateName)
582{
583 bool result = true;
584 if (!templateName.isEmpty())
585 {
586 AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen();
587 if (cal)
588 {
589 KAEvent templateEvent = KAEvent::findTemplateName(*cal, templateName);
590 if (templateEvent.valid())
591 {
592 MainWindow::executeNew(templateEvent);
593 return true;
594 }
595 kdWarning(5950) << "KAlarm::editNew(" << templateName << "): template not found" << endl;
596 }
597 result = false;
598 }
599 MainWindow::executeNew();
600 return result;
601}
602
603/******************************************************************************
604* Returns a list of all alarm templates.
605* If shell commands are disabled, command alarm templates are omitted.
606*/
607TQValueList<KAEvent> templateList()
608{
609 TQValueList<KAEvent> templates;
610 AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen();
611 if (cal)
612 {
613 bool includeCmdAlarms = ShellProcess::authorised();
614 KCal::Event::List events = cal->events();
615 for (KCal::Event::List::ConstIterator it = events.begin(); it != events.end(); ++it)
616 {
617 KCal::Event* kcalEvent = *it;
618 KAEvent event(*kcalEvent);
619 if (includeCmdAlarms || event.action() != KAEvent::COMMAND)
620 templates.append(event);
621 }
622 }
623 return templates;
624}
625
626/******************************************************************************
627* To be called after an alarm has been edited.
628* Prompt the user to re-enable alarms if they are currently disabled, and if
629* it's an email alarm, warn if no 'From' email address is configured.
630*/
631void outputAlarmWarnings(TQWidget* parent, const KAEvent* event)
632{
633 if (event && event->action() == KAEvent::EMAIL
634 && Preferences::emailAddress().isEmpty())
635 KMessageBox::information(parent, i18n("Please set the 'From' email address...",
636 "%1\nPlease set it in the Preferences dialog.").arg(KAMail::i18n_NeedFromEmailAddress()));
637
638 if (!Daemon::monitoringAlarms())
639 {
640 if (KMessageBox::warningYesNo(parent, i18n("Alarms are currently disabled.\nDo you want to enable alarms now?"),
641 TQString(), i18n("Enable"), i18n("Keep Disabled"),
642 TQString::fromLatin1("EditEnableAlarms"))
643 == KMessageBox::Yes)
644 Daemon::setAlarmsEnabled();
645 }
646}
647
648/******************************************************************************
649* Reset the alarm daemon and reload the calendar.
650* If the daemon is not already running, start it.
651*/
652void resetDaemon()
653{
654 kdDebug(5950) << "KAlarm::resetDaemon()" << endl;
655 if (!resetDaemonQueued)
656 {
657 resetDaemonQueued = true;
658 theApp()->processQueue();
659 }
660}
661
662/******************************************************************************
663* This method must only be called from the main KAlarm queue processing loop,
664* to prevent asynchronous calendar operations interfering with one another.
665*
666* If resetDaemon() has been called, reset the alarm daemon and reload the calendars.
667* If the daemon is not already running, start it.
668*/
669void resetDaemonIfQueued()
670{
671 if (resetDaemonQueued)
672 {
673 kdDebug(5950) << "KAlarm::resetDaemonIfNeeded()" << endl;
674 AlarmCalendar::activeCalendar()->reload();
675 AlarmCalendar::expiredCalendar()->reload();
676
677 // Close any message windows for alarms which are now disabled
678 KAEvent event;
679 KCal::Event::List events = AlarmCalendar::activeCalendar()->events();
680 for (KCal::Event::List::ConstIterator it = events.begin(); it != events.end(); ++it)
681 {
682 KCal::Event* kcalEvent = *it;
683 event.set(*kcalEvent);
684 if (!event.enabled() && event.displayAction())
685 {
686 MessageWin* win = MessageWin::findEvent(event.id());
687 delete win;
688 }
689 }
690
691 MainWindow::refresh();
692 if (!Daemon::reset())
693 Daemon::start();
694 resetDaemonQueued = false;
695 }
696}
697
698/******************************************************************************
699* Start KMail if it isn't already running, and optionally iconise it.
700* Reply = reason for failure to run KMail (which may be the empty string)
701* = null string if success.
702*/
703TQString runKMail(bool minimise)
704{
705 TQCString dcopName;
706 TQString errmsg;
707 if (!runProgram("kmail", (minimise ? KMAIL_DCOP_WINDOW : ""), dcopName, errmsg))
708 return i18n("Unable to start KMail\n(%1)").arg(errmsg);
709 return TQString();
710}
711
712/******************************************************************************
713* Start another program for DCOP access if it isn't already running.
714* If 'windowName' is not empty, the program's window of that name is iconised.
715* On exit, 'dcopName' contains the DCOP name to access the application, and
716* 'errorMessage' contains an error message if failure.
717* Reply = true if the program is now running.
718*/
719bool runProgram(const TQCString& program, const TQCString& windowName, TQCString& dcopName, TQString& errorMessage)
720{
721 if (!tdeApp->dcopClient()->isApplicationRegistered(program))
722 {
723 // KOrganizer is not already running, so start it
724 if (TDEApplication::startServiceByDesktopName(TQString::fromLatin1(program), TQString(), &errorMessage, &dcopName))
725 {
726 kdError(5950) << "runProgram(): couldn't start " << program << " (" << errorMessage << ")\n";
727 return false;
728 }
729 // Minimise its window - don't use hide() since this would remove all
730 // trace of it from the panel if it is not configured to be docked in
731 // the system tray.
732 tdeApp->dcopClient()->send(dcopName, windowName, "minimize()", TQString());
733 }
734 else if (dcopName.isEmpty())
735 dcopName = program;
736 errorMessage = TQString();
737 return true;
738}
739
740/******************************************************************************
741* Read the size for the specified window from the config file, for the
742* current screen resolution.
743* Reply = true if size set in the config file, in which case 'result' is set
744* = false if no size is set, in which case 'result' is unchanged.
745*/
746bool readConfigWindowSize(const char* window, TQSize& result)
747{
748 TDEConfig* config = TDEGlobal::config();
749 config->setGroup(TQString::fromLatin1(window));
750 TQWidget* desktop = TDEApplication::desktop();
751 TQSize s = TQSize(config->readNumEntry(TQString::fromLatin1("Width %1").arg(desktop->width()), 0),
752 config->readNumEntry(TQString::fromLatin1("Height %1").arg(desktop->height()), 0));
753 if (s.isEmpty())
754 return false;
755 result = s;
756 return true;
757}
758
759/******************************************************************************
760* Write the size for the specified window to the config file, for the
761* current screen resolution.
762*/
763void writeConfigWindowSize(const char* window, const TQSize& size)
764{
765 TDEConfig* config = TDEGlobal::config();
766 config->setGroup(TQString::fromLatin1(window));
767 TQWidget* desktop = TDEApplication::desktop();
768 config->writeEntry(TQString::fromLatin1("Width %1").arg(desktop->width()), size.width());
769 config->writeEntry(TQString::fromLatin1("Height %1").arg(desktop->height()), size.height());
770 config->sync();
771}
772
773/******************************************************************************
774* Return the current KAlarm version number.
775*/
776int Version()
777{
778 static int version = 0;
779 if (!version)
780 version = getVersionNumber(KALARM_VERSION);
781 return version;
782}
783
784/******************************************************************************
785* Convert the supplied KAlarm version string to a version number.
786* Reply = version number (double digit for each of major, minor & issue number,
787* e.g. 010203 for 1.2.3
788* = 0 if invalid version string.
789*/
790int getVersionNumber(const TQString& version, TQString* subVersion)
791{
792 // N.B. Remember to change Version(int major, int minor, int rev)
793 // if the representation returned by this method changes.
794 if (subVersion)
795 *subVersion = TQString();
796 int count = version.contains('.') + 1;
797 if (count < 2)
798 return 0;
799 bool ok;
800 unsigned vernum = version.section('.', 0, 0).toUInt(&ok) * 10000; // major version
801 if (!ok)
802 return 0;
803 unsigned v = version.section('.', 1, 1).toUInt(&ok); // minor version
804 if (!ok)
805 return 0;
806 vernum += (v < 99 ? v : 99) * 100;
807 if (count >= 3)
808 {
809 // Issue number: allow other characters to follow the last digit
810 TQString issue = version.section('.', 2);
811 if (!issue.at(0).isDigit())
812 return 0;
813 int n = issue.length();
814 int i;
815 for (i = 0; i < n && issue.at(i).isDigit(); ++i) ;
816 if (subVersion)
817 *subVersion = issue.mid(i);
818 v = issue.left(i).toUInt(); // issue number
819 vernum += (v < 99 ? v : 99);
820 }
821 return vernum;
822}
823
824/******************************************************************************
825* Check from its mime type whether a file appears to be a text or image file.
826* If a text file, its type is distinguished.
827* Reply = file type.
828*/
829FileType fileType(const TQString& mimetype)
830{
831 static const char* applicationTypes[] = {
832 "x-shellscript", "x-nawk", "x-awk", "x-perl", "x-python",
833 "x-desktop", "x-troff", 0 };
834 static const char* formattedTextTypes[] = {
835 "html", "xml", 0 };
836
837 if (mimetype.startsWith(TQString::fromLatin1("image/")))
838 return Image;
839 int slash = mimetype.find('/');
840 if (slash < 0)
841 return Unknown;
842 TQString type = mimetype.mid(slash + 1);
843 const char* typel = type.latin1();
844 if (mimetype.startsWith(TQString::fromLatin1("application")))
845 {
846 for (int i = 0; applicationTypes[i]; ++i)
847 if (!strcmp(typel, applicationTypes[i]))
848 return TextApplication;
849 }
850 else if (mimetype.startsWith(TQString::fromLatin1("text")))
851 {
852 for (int i = 0; formattedTextTypes[i]; ++i)
853 if (!strcmp(typel, formattedTextTypes[i]))
854 return TextFormatted;
855 return TextPlain;
856 }
857 return Unknown;
858}
859
860/******************************************************************************
861* Display a modal dialogue to choose an existing file, initially highlighting
862* any specified file.
863* @param initialFile The file to initially highlight - must be a full path name or URL.
864* @param defaultDir The directory to start in if @p initialFile is empty. If empty,
865* the user's home directory will be used. Updated to the
866* directory containing the selected file, if a file is chosen.
867* @param mode OR of KFile::Mode values, e.g. ExistingOnly, LocalOnly.
868* Reply = URL selected. If none is selected, URL.isEmpty() is true.
869*/
870TQString browseFile(const TQString& caption, TQString& defaultDir, const TQString& initialFile,
871 const TQString& filter, int mode, TQWidget* parent, const char* name)
872{
873 TQString initialDir = !initialFile.isEmpty() ? TQString(initialFile).remove(TQRegExp("/[^/]*$"))
874 : !defaultDir.isEmpty() ? defaultDir
875 : TQDir::homeDirPath();
876 KFileDialog fileDlg(initialDir, filter, parent, name, true);
877 fileDlg.setOperationMode(mode & KFile::ExistingOnly ? KFileDialog::Opening : KFileDialog::Saving);
878 fileDlg.setMode(KFile::File | mode);
879 fileDlg.setCaption(caption);
880 if (!initialFile.isEmpty())
881 fileDlg.setSelection(initialFile);
882 if (fileDlg.exec() != TQDialog::Accepted)
883 return TQString();
884 KURL url = fileDlg.selectedURL();
885 defaultDir = url.path();
886 return (mode & KFile::LocalOnly) ? url.path() : url.prettyURL();
887}
888
889/******************************************************************************
890* Return the first day of the week for the user's locale.
891* Reply = 1 (Mon) .. 7 (Sun).
892*/
893int localeFirstDayOfWeek()
894{
895 static int firstDay = 0;
896 if (!firstDay)
897 firstDay = TDEGlobal::locale()->weekStartDay();
898 return firstDay;
899}
900
901/******************************************************************************
902* Return the supplied string with any accelerator code stripped out.
903*/
904TQString stripAccel(const TQString& text)
905{
906 unsigned len = text.length();
907 TQString out = TQDeepCopy<TQString>(text);
908 TQChar *corig = (TQChar*)out.unicode();
909 TQChar *cout = corig;
910 TQChar *cin = cout;
911 while (len)
912 {
913 if ( *cin == '&' )
914 {
915 ++cin;
916 --len;
917 if ( !len )
918 break;
919 }
920 *cout = *cin;
921 ++cout;
922 ++cin;
923 --len;
924 }
925 unsigned newlen = cout - corig;
926 if (newlen != out.length())
927 out.truncate(newlen);
928 return out;
929}
930
931} // namespace KAlarm
932
933
934namespace {
935
936/******************************************************************************
937* Tell KOrganizer to put an alarm in its calendar.
938* It will be held by KOrganizer as a simple event, without alarms - KAlarm
939* is still responsible for alarming.
940*/
941bool sendToKOrganizer(const KAEvent& event)
942{
943 KCal::Event* kcalEvent = event.event();
944 TQString uid = KAEvent::uid(event.id(), KAEvent::KORGANIZER);
945 kcalEvent->setUid(uid);
946 kcalEvent->clearAlarms();
947 TQString userEmail;
948 switch (event.action())
949 {
950 case KAEvent::MESSAGE:
951 case KAEvent::FILE:
952 case KAEvent::COMMAND:
953 kcalEvent->setSummary(event.cleanText());
954 userEmail = Preferences::emailAddress();
955 break;
956 case KAEvent::EMAIL:
957 {
958 TQString from = event.emailFromId()
959 ? KAMail::identityManager()->identityForUoid(event.emailFromId()).fullEmailAddr()
960 : Preferences::emailAddress();
961 AlarmText atext;
962 atext.setEmail(event.emailAddresses(", "), from, TQString(), TQString(), event.emailSubject(), TQString());
963 kcalEvent->setSummary(atext.displayText());
964 userEmail = from;
965 break;
966 }
967 }
968 kcalEvent->setOrganizer(KCal::Person(TQString(), userEmail));
969
970 // Translate the event into string format
971 KCal::ICalFormat format;
972 format.setTimeZone(TQString(), false);
973 TQString iCal = format.toICalString(kcalEvent);
974kdDebug(5950)<<"Korg->"<<iCal<<endl;
975 delete kcalEvent;
976
977 // Send the event to KOrganizer
978 if (!runKOrganizer()) // start KOrganizer if it isn't already running
979 return false;
980 TQByteArray data, replyData;
981 TQCString replyType;
982 TQDataStream arg(data, IO_WriteOnly);
983 arg << iCal;
984 if (tdeApp->dcopClient()->call(korganizerName, KORG_DCOP_OBJECT, "addIncidence(TQString)", data, replyType, replyData)
985 && replyType == "bool")
986 {
987 bool result;
988 TQDataStream reply(replyData, IO_ReadOnly);
989 reply >> result;
990 if (result)
991 {
992 kdDebug(5950) << "sendToKOrganizer(" << uid << "): success\n";
993 return true;
994 }
995 }
996 kdError(5950) << "sendToKOrganizer(): KOrganizer addEvent(" << uid << ") dcop call failed\n";
997 return false;
998}
999
1000/******************************************************************************
1001* Tell KOrganizer to delete an event from its calendar.
1002*/
1003bool deleteFromKOrganizer(const TQString& eventID)
1004{
1005 if (!runKOrganizer()) // start KOrganizer if it isn't already running
1006 return false;
1007 TQString newID = KAEvent::uid(eventID, KAEvent::KORGANIZER);
1008 TQByteArray data, replyData;
1009 TQCString replyType;
1010 TQDataStream arg(data, IO_WriteOnly);
1011 arg << newID << true;
1012 if (tdeApp->dcopClient()->call(korganizerName, KORG_DCOP_OBJECT, "deleteIncidence(TQString,bool)", data, replyType, replyData)
1013 && replyType == "bool")
1014 {
1015 bool result;
1016 TQDataStream reply(replyData, IO_ReadOnly);
1017 reply >> result;
1018 if (result)
1019 {
1020 kdDebug(5950) << "deleteFromKOrganizer(" << newID << "): success\n";
1021 return true;
1022 }
1023 }
1024 kdError(5950) << "sendToKOrganizer(): KOrganizer deleteEvent(" << newID << ") dcop call failed\n";
1025 return false;
1026}
1027
1028/******************************************************************************
1029* Start KOrganizer if not already running, and create its DCOP interface.
1030*/
1031bool runKOrganizer()
1032{
1033 TQString error;
1034 TQCString dcopService;
1035 int result = KDCOPServiceStarter::self()->findServiceFor("DCOP/Organizer", TQString(), TQString(), &error, &dcopService);
1036 if (result)
1037 {
1038 kdDebug(5950) << "Unable to start DCOP/Organizer: " << dcopService << " " << error << endl;
1039 return false;
1040 }
1041 // If Kontact is running, there is be a load() method which needs to be called
1042 // to load KOrganizer into Kontact. But if KOrganizer is running independently,
1043 // the load() method doesn't exist.
1044 TQCString dummy;
1045 if (!tdeApp->dcopClient()->findObject(dcopService, KORG_DCOP_OBJECT, "", TQByteArray(), dummy, dummy))
1046 {
1047 DCOPRef ref(dcopService, dcopService); // talk to the TDEUniqueApplication or its Kontact wrapper
1048 DCOPReply reply = ref.call("load()");
1049 if (!reply.isValid() || !(bool)reply)
1050 {
1051 kdWarning(5950) << "Error loading " << dcopService << endl;
1052 return false;
1053 }
1054 if (!tdeApp->dcopClient()->findObject(dcopService, KORG_DCOP_OBJECT, "", TQByteArray(), dummy, dummy))
1055 {
1056 kdWarning(5950) << "Unable to access KOrganizer's " KORG_DCOP_OBJECT " DCOP object" << endl;
1057 return false;
1058 }
1059 }
1060 return true;
1061}
1062
1063} // namespace
1064
1065#ifdef HAVE_XTEST
1066#include <X11/keysym.h>
1067#include <X11/extensions/XTest.h>
1068#include <tqwindowdefs.h>
1069
1070/******************************************************************************
1071* Cancel the screen saver, in case it is active.
1072* Only implemented if the X11 XTest extension is installed.
1073*/
1074void x11_cancelScreenSaver()
1075{
1076 kdDebug(5950) << "KAlarm::cancelScreenSaver()" << endl;
1077 Display* display = tqt_xdisplay();
1078 static int XTestKeyCode = 0;
1079 if (!XTestKeyCode)
1080 XTestKeyCode = XKeysymToKeycode(display, XK_Shift_L);
1081 XTestFakeKeyEvent(display, XTestKeyCode, true, CurrentTime);
1082 XTestFakeKeyEvent(display, XTestKeyCode, false, CurrentTime);
1083 XSync(display, false);
1084}
1085#endif // HAVE_XTEST
represents calendar alarms and events
Provides read and write access to calendar files.
Definition: alarmcalendar.h:37
KAEvent corresponds to a KCal::Event instance.
Definition: alarmevent.h:232
void setTimeZone(const TQString &id, bool utc)
TQString toICalString(Incidence *)
void setOrganizer(const Person &o)
void setUid(const TQString &)
void setSummary(const TQString &summary)
MessageWin: A window to display an alarm message.
Definition: messagewin.h:45
miscellaneous functions
UpdateStatus
Return codes from calendar update functions.
Definition: functions.h:51
FileType
Return codes from fileType()
Definition: functions.h:47
the KAlarm application object
main application window
displays an alarm message