Build with clang.
[kdepim.git] / kalarm / functions.cpp
bloba363ceec0438f78c73811cfe31dd83cc3d96a563
1 /*
2 * functions.cpp - miscellaneous functions
3 * Program: kalarm
4 * Copyright © 2001-2011 by David Jarvie <djarvie@kde.org>
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.
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.
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.
21 #include "kalarm.h" //krazy:exclude=includes (kalarm.h must be first)
22 #include "functions.h"
23 #include "functions_p.h"
25 #ifdef USE_AKONADI
26 #include "collectionmodel.h"
27 #else
28 #include "alarmresources.h"
29 #include "eventlistmodel.h"
30 #endif
31 #include "alarmcalendar.h"
32 #include "autoqpointer.h"
33 #include "alarmlistview.h"
34 #include "editdlg.h"
35 #include "identities.h"
36 #include "kaevent.h"
37 #include "kalarmapp.h"
38 #include "kamail.h"
39 #include "mainwindow.h"
40 #include "messagebox.h"
41 #include "messagewin.h"
42 #include "preferences.h"
43 #include "shellprocess.h"
44 #include "templatelistview.h"
45 #include "templatemenuaction.h"
47 #ifdef USE_AKONADI
48 #include <kcalcore/event.h>
49 #include <kcalcore/icalformat.h>
50 #include <kcalcore/person.h>
51 #include <kcalcore/duration.h>
52 using namespace KCalCore;
53 #else
54 #include <kcal/event.h>
55 #include <kcal/icalformat.h>
56 #include <kcal/person.h>
57 #include <kcal/duration.h>
58 using namespace KCal;
59 #endif
60 #include <kpimidentities/identitymanager.h>
61 #include <kpimidentities/identity.h>
62 #include <kholidays/holidays.h>
64 #include <kconfiggroup.h>
65 #include <kaction.h>
66 #include <ktoggleaction.h>
67 #include <kactioncollection.h>
68 #include <kdbusservicestarter.h>
69 #include <kglobal.h>
70 #include <klocale.h>
71 #include <kstandarddirs.h>
72 #include <kauth.h>
73 #include <ksystemtimezone.h>
74 #include <kstandardguiitem.h>
75 #include <kstandardshortcut.h>
76 #include <kfiledialog.h>
77 #include <kicon.h>
78 #include <kio/netaccess.h>
79 #include <kfileitem.h>
80 #include <kdebug.h>
81 #include <ktoolinvocation.h>
83 #ifdef Q_WS_X11
84 #include <kwindowsystem.h>
85 #include <kxmessages.h>
86 #include <kstartupinfo.h>
87 #include <netwm.h>
88 #include <QX11Info>
89 #endif
91 #include <QDir>
92 #include <QRegExp>
93 #include <QDesktopWidget>
94 #include <QtDBus/QtDBus>
95 #include <QTimer>
96 #include <qglobal.h>
98 #ifdef USE_AKONADI
99 using namespace Akonadi;
100 #endif
103 namespace
105 bool refreshAlarmsQueued = false;
106 QString korganizerName = "korganizer";
107 QString korgStartError;
108 QDBusInterface* korgInterface = 0;
110 const char* KMAIL_DBUS_SERVICE = "org.kde.kmail";
111 //const char* KMAIL_DBUS_IFACE = "org.kde.kmail.kmail";
112 //const char* KMAIL_DBUS_WINDOW_PATH = "/kmail/kmail_mainwindow_1";
113 const char* KORG_DBUS_SERVICE = "org.kde.korganizer";
114 const char* KORG_DBUS_IFACE = "org.kde.korganizer.Korganizer";
115 // D-Bus object path of KOrganizer's notification interface
116 #define KORG_DBUS_PATH "/Korganizer"
117 #define KORG_DBUS_LOAD_PATH "/korganizer_PimApplication"
118 //const char* KORG_DBUS_WINDOW_PATH = "/korganizer/MainWindow_1";
119 const QString KORGANIZER_UID = QString::fromLatin1("-korg");
121 const char* ALARM_OPTS_FILE = "alarmopts";
122 const char* DONT_SHOW_ERRORS_GROUP = "DontShowErrors";
124 void editNewTemplate(EditAlarmDlg::Type, const KAEvent* preset, QWidget* parent);
125 KAlarm::UpdateStatus sendToKOrganizer(const KAEvent*);
126 KAlarm::UpdateStatus deleteFromKOrganizer(const QString& eventID);
127 KAlarm::UpdateStatus runKOrganizer();
128 QString uidKOrganizer(const QString& eventID);
132 namespace KAlarm
135 Private* Private::mInstance = 0;
137 /******************************************************************************
138 * Display a main window with the specified event selected.
140 #ifdef USE_AKONADI
141 MainWindow* displayMainWindowSelected(Akonadi::Item::Id eventId)
142 #else
143 MainWindow* displayMainWindowSelected(const QString& eventId)
144 #endif
146 MainWindow* win = MainWindow::firstWindow();
147 if (!win)
149 if (theApp()->checkCalendar()) // ensure calendar is open
151 win = MainWindow::create();
152 win->show();
155 else
157 // There is already a main window, so make it the active window
158 win->hide(); // in case it's on a different desktop
159 win->setWindowState(win->windowState() & ~Qt::WindowMinimized);
160 win->show();
161 win->raise();
162 win->activateWindow();
164 #ifdef USE_AKONADI
165 if (win && eventId >= 0)
166 win->selectEvent(eventId);
167 #else
168 if (win && !eventId.isEmpty())
169 win->selectEvent(eventId);
170 #endif
171 return win;
174 /******************************************************************************
175 * Create an "Alarms Enabled/Enable Alarms" action.
177 KToggleAction* createAlarmEnableAction(QObject* parent)
179 KToggleAction* action = new KToggleAction(i18nc("@action", "Enable &Alarms"), parent);
180 action->setChecked(theApp()->alarmsEnabled());
181 QObject::connect(action, SIGNAL(toggled(bool)), theApp(), SLOT(setAlarmsEnabled(bool)));
182 // The following line ensures that all instances are kept in the same state
183 QObject::connect(theApp(), SIGNAL(alarmEnabledToggled(bool)), action, SLOT(setChecked(bool)));
184 return action;
187 /******************************************************************************
188 * Create a "Stop Play" action.
190 KAction* createStopPlayAction(QObject* parent)
192 KAction* action = new KAction(KIcon("media-playback-stop"), i18nc("@action", "Stop Play"), parent);
193 action->setEnabled(MessageWin::isAudioPlaying());
194 QObject::connect(action, SIGNAL(triggered(bool)), theApp(), SLOT(stopAudio()));
195 // The following line ensures that all instances are kept in the same state
196 QObject::connect(theApp(), SIGNAL(audioPlaying(bool)), action, SLOT(setEnabled(bool)));
197 return action;
200 /******************************************************************************
201 * Create a "Spread Windows" action.
203 KToggleAction* createSpreadWindowsAction(QObject* parent)
205 KToggleAction* action = new KToggleAction(i18nc("@action", "Spread Windows"), parent);
206 QObject::connect(action, SIGNAL(triggered(bool)), theApp(), SLOT(spreadWindows(bool)));
207 // The following line ensures that all instances are kept in the same state
208 QObject::connect(theApp(), SIGNAL(spreadWindowsToggled(bool)), action, SLOT(setChecked(bool)));
209 return action;
212 /******************************************************************************
213 * Add a new active (non-archived) alarm.
214 * Save it in the calendar file and add it to every main window instance.
215 * Parameters: msgParent = parent widget for any calendar selection prompt or
216 * error message.
217 * event - is updated with the actual event ID.
219 #ifdef USE_AKONADI
220 UpdateStatus addEvent(KAEvent& event, Collection* calendar, QWidget* msgParent, int options, bool showKOrgErr)
221 #else
222 UpdateStatus addEvent(KAEvent& event, AlarmResource* calendar, QWidget* msgParent, int options, bool showKOrgErr)
223 #endif
225 kDebug() << event.id();
226 bool cancelled = false;
227 UpdateStatus status = UPDATE_OK;
228 if (!theApp()->checkCalendar()) // ensure calendar is open
229 status = UPDATE_FAILED;
230 else
232 // Save the event details in the calendar file, and get the new event ID
233 AlarmCalendar* cal = AlarmCalendar::resources();
234 KAEvent* newev = new KAEvent(event);
235 #ifdef USE_AKONADI
236 if (!cal->addEvent(*newev, msgParent, (options & USE_EVENT_ID), calendar, (options & NO_RESOURCE_PROMPT), &cancelled))
237 #else
238 if (!cal->addEvent(newev, msgParent, (options & USE_EVENT_ID), calendar, (options & NO_RESOURCE_PROMPT), &cancelled))
239 #endif
241 delete newev;
242 status = UPDATE_FAILED;
244 else
246 event = *newev; // update event ID etc.
247 if (!cal->save())
248 status = SAVE_FAILED;
250 if (status == UPDATE_OK)
252 if ((options & ALLOW_KORG_UPDATE) && event.copyToKOrganizer())
254 UpdateStatus st = sendToKOrganizer(newev); // tell KOrganizer to show the event
255 if (st > status)
256 status = st;
259 #ifndef USE_AKONADI
260 // Update the window lists
261 EventListModel::alarms()->addEvent(newev);
262 #endif
266 if (status != UPDATE_OK && !cancelled && msgParent)
267 displayUpdateError(msgParent, status, ERR_ADD, 1, 1, showKOrgErr);
268 return status;
271 /******************************************************************************
272 * Add a list of new active (non-archived) alarms.
273 * Save them in the calendar file and add them to every main window instance.
274 * The events are updated with their actual event IDs.
276 UpdateStatus addEvents(QVector<KAEvent>& events, QWidget* msgParent, bool allowKOrgUpdate, bool showKOrgErr)
278 kDebug() << events.count();
279 if (events.isEmpty())
280 return UPDATE_OK;
281 int warnErr = 0;
282 int warnKOrg = 0;
283 UpdateStatus status = UPDATE_OK;
284 #ifdef USE_AKONADI
285 Collection collection;
286 #else
287 AlarmResource* resource;
288 #endif
289 if (!theApp()->checkCalendar()) // ensure calendar is open
290 status = UPDATE_FAILED;
291 else
293 #ifdef USE_AKONADI
294 collection = CollectionControlModel::instance()->destination(KAlarm::CalEvent::ACTIVE, msgParent);
295 if (!collection.isValid())
296 #else
297 resource = AlarmResources::instance()->destination(KAlarm::CalEvent::ACTIVE, msgParent);
298 if (!resource)
299 #endif
301 kDebug() << "No calendar";
302 status = UPDATE_FAILED;
305 if (status == UPDATE_OK)
307 QString selectID;
308 AlarmCalendar* cal = AlarmCalendar::resources();
309 for (int i = 0, end = events.count(); i < end; ++i)
311 // Save the event details in the calendar file, and get the new event ID
312 #ifdef USE_AKONADI
313 KAEvent* const newev = &events[i];
314 if (!cal->addEvent(events[i], msgParent, false, &collection))
315 #else
316 KAEvent* newev = new KAEvent(events[i]);
317 if (!cal->addEvent(newev, msgParent, false, resource))
318 #endif
320 #ifndef USE_AKONADI
321 delete newev;
322 #endif
323 status = UPDATE_ERROR;
324 ++warnErr;
325 continue;
327 #ifndef USE_AKONADI
328 events[i] = *newev; // update event ID etc.
329 #endif
330 if (allowKOrgUpdate && newev->copyToKOrganizer())
332 UpdateStatus st = sendToKOrganizer(newev); // tell KOrganizer to show the event
333 if (st != UPDATE_OK)
335 ++warnKOrg;
336 if (st > status)
337 status = st;
341 #ifndef USE_AKONADI
342 // Update the window lists, but not yet which item is selected
343 EventListModel::alarms()->addEvent(newev);
344 // selectID = newev->id();
345 #endif
347 if (warnErr == events.count())
348 status = UPDATE_FAILED;
349 else if (!cal->save())
351 status = SAVE_FAILED;
352 warnErr = 0; // everything failed
356 if (status != UPDATE_OK && msgParent)
357 displayUpdateError(msgParent, status, ERR_ADD, (warnErr ? warnErr : events.count()), warnKOrg, showKOrgErr);
358 return status;
361 /******************************************************************************
362 * Save the event in the archived calendar and adjust every main window instance.
363 * The event's ID is changed to an archived ID if necessary.
365 #ifdef USE_AKONADI
366 bool addArchivedEvent(KAEvent& event, Collection* collection)
367 #else
368 bool addArchivedEvent(KAEvent& event, AlarmResource* resource)
369 #endif
371 kDebug() << event.id();
372 QString oldid = event.id();
373 bool archiving = (event.category() == KAlarm::CalEvent::ACTIVE);
374 if (archiving && !Preferences::archivedKeepDays())
375 return false; // expired alarms aren't being kept
376 AlarmCalendar* cal = AlarmCalendar::resources();
377 #ifdef USE_AKONADI
378 KAEvent newevent(event);
379 newevent.setItemId(-1); // invalidate the Akonadi item ID since it's a new item
380 KAEvent* const newev = &newevent;
381 #else
382 KAEvent* newev = new KAEvent(event);
383 #endif
384 if (archiving)
386 newev->setCategory(KAlarm::CalEvent::ARCHIVED); // this changes the event ID
387 newev->setCreatedDateTime(KDateTime::currentUtcDateTime()); // time stamp to control purging
389 // Note that archived resources are automatically saved after changes are made
390 #ifdef USE_AKONADI
391 if (!cal->addEvent(newevent, 0, false, collection))
392 return false;
393 #else
394 if (!cal->addEvent(newev, 0, false, resource))
396 delete newev; // failed to add to calendar - leave event in its original state
397 return false;
399 #endif
400 event = *newev; // update event ID etc.
402 #ifndef USE_AKONADI
403 // Update window lists.
404 // Note: updateEvent() is not used here since that doesn't trigger refiltering
405 // of the alarm list, resulting in the archived event still remaining visible
406 // even if archived events are supposed to be hidden.
407 if (archiving)
408 EventListModel::alarms()->removeEvent(oldid);
409 EventListModel::alarms()->addEvent(newev);
410 #endif
411 return true;
414 /******************************************************************************
415 * Add a new template.
416 * Save it in the calendar file and add it to every template list view.
417 * 'event' is updated with the actual event ID.
418 * Parameters: promptParent = parent widget for any calendar selection prompt.
420 #ifdef USE_AKONADI
421 UpdateStatus addTemplate(KAEvent& event, Collection* collection, QWidget* msgParent)
422 #else
423 UpdateStatus addTemplate(KAEvent& event, AlarmResource* resource, QWidget* msgParent)
424 #endif
426 kDebug() << event.id();
427 UpdateStatus status = UPDATE_OK;
429 // Add the template to the calendar file
430 AlarmCalendar* cal = AlarmCalendar::resources();
431 #ifdef USE_AKONADI
432 KAEvent newev(event);
433 if (!cal->addEvent(newev, msgParent, false, collection))
434 status = UPDATE_FAILED;
435 #else
436 KAEvent* newev = new KAEvent(event);
437 if (!cal->addEvent(newev, msgParent, false, resource))
439 delete newev;
440 status = UPDATE_FAILED;
442 #endif
443 else
445 #ifdef USE_AKONADI
446 event = newev; // update event ID etc.
447 #else
448 event = *newev; // update event ID etc.
449 #endif
450 if (!cal->save())
451 status = SAVE_FAILED;
452 else
454 #ifndef USE_AKONADI
455 // Update the window lists
456 EventListModel::templates()->addEvent(newev);
457 #endif
458 return UPDATE_OK;
462 if (msgParent)
463 displayUpdateError(msgParent, status, ERR_TEMPLATE, 1);
464 return status;
467 /******************************************************************************
468 * Modify an active (non-archived) alarm in the calendar file and in every main
469 * window instance.
470 * The new event must have a different event ID from the old one.
472 UpdateStatus modifyEvent(KAEvent& oldEvent, KAEvent& newEvent, QWidget* msgParent, bool showKOrgErr)
474 kDebug() << oldEvent.id();
476 UpdateStatus status = UPDATE_OK;
477 if (!newEvent.isValid())
479 deleteEvent(oldEvent, true);
480 status = UPDATE_FAILED;
482 else
484 QString oldId = oldEvent.id();
485 if (oldEvent.copyToKOrganizer())
487 // Tell KOrganizer to delete its old event.
488 // But ignore errors, because the user could have manually
489 // deleted it since KAlarm asked KOrganizer to set it up.
490 deleteFromKOrganizer(oldId);
492 #ifdef USE_AKONADI
493 // Update the event in the calendar file, and get the new event ID
494 AlarmCalendar* cal = AlarmCalendar::resources();
495 if (!cal->modifyEvent(oldId, newEvent))
496 status = UPDATE_FAILED;
497 #else
498 // Delete from the window lists to prevent the event's invalid
499 // pointer being accessed.
500 EventListModel::alarms()->removeEvent(oldId);
502 // Update the event in the calendar file, and get the new event ID
503 KAEvent* newev = new KAEvent(newEvent);
504 AlarmCalendar* cal = AlarmCalendar::resources();
505 if (!cal->modifyEvent(oldId, newev))
507 delete newev;
508 status = UPDATE_FAILED;
510 #endif
511 else
513 #ifndef USE_AKONADI
514 newEvent = *newev;
515 #endif
516 if (!cal->save())
517 status = SAVE_FAILED;
518 if (status == UPDATE_OK)
520 if (newEvent.copyToKOrganizer())
522 UpdateStatus st = sendToKOrganizer(&newEvent); // tell KOrganizer to show the new event
523 if (st > status)
524 status = st;
527 // Remove "Don't show error messages again" for the old alarm
528 setDontShowErrors(oldId);
530 #ifndef USE_AKONADI
531 // Update the window lists
532 EventListModel::alarms()->addEvent(newev);
533 #endif
538 if (status != UPDATE_OK && msgParent)
539 displayUpdateError(msgParent, status, ERR_MODIFY, 1, 1, showKOrgErr);
540 return status;
543 /******************************************************************************
544 * Update an active (non-archived) alarm from the calendar file and from every
545 * main window instance.
546 * The new event will have the same event ID as the old one.
547 * The event is not updated in KOrganizer, since this function is called when an
548 * existing alarm is rescheduled (due to recurrence or deferral).
550 UpdateStatus updateEvent(KAEvent& event, QWidget* msgParent, bool archiveOnDelete)
552 kDebug() << event.id();
554 if (!event.isValid())
555 deleteEvent(event, archiveOnDelete);
556 else
558 // Update the event in the calendar file.
559 AlarmCalendar* cal = AlarmCalendar::resources();
560 #ifdef USE_AKONADI
561 cal->updateEvent(event);
562 #else
563 KAEvent* newEvent = cal->updateEvent(event);
564 #endif
565 if (!cal->save())
567 if (msgParent)
568 displayUpdateError(msgParent, SAVE_FAILED, ERR_ADD, 1);
569 return SAVE_FAILED;
572 #ifndef USE_AKONADI
573 // Update the window lists
574 EventListModel::alarms()->updateEvent(newEvent);
575 #endif
577 return UPDATE_OK;
580 /******************************************************************************
581 * Update a template in the calendar file and in every template list view.
582 * If 'selectionView' is non-null, the selection highlight is moved to the
583 * updated event in that listView instance.
585 UpdateStatus updateTemplate(KAEvent& event, QWidget* msgParent)
587 AlarmCalendar* cal = AlarmCalendar::resources();
588 KAEvent* newEvent = cal->updateEvent(event);
589 UpdateStatus status = UPDATE_OK;
590 if (!newEvent)
591 status = UPDATE_FAILED;
592 else if (!cal->save())
593 status = SAVE_FAILED;
594 if (status != UPDATE_OK)
596 if (msgParent)
597 displayUpdateError(msgParent, SAVE_FAILED, ERR_TEMPLATE, 1);
598 return status;
601 #ifndef USE_AKONADI
602 EventListModel::templates()->updateEvent(newEvent);
603 #endif
604 return UPDATE_OK;
607 /******************************************************************************
608 * Delete alarms from the calendar file and from every main window instance.
609 * If the events are archived, the events' IDs are changed to archived IDs if necessary.
611 UpdateStatus deleteEvent(KAEvent& event, bool archive, QWidget* msgParent, bool showKOrgErr)
613 #ifdef USE_AKONADI
614 QVector<KAEvent> events(1, event);
615 #else
616 KAEvent::List events;
617 events += &event;
618 #endif
619 return deleteEvents(events, archive, msgParent, showKOrgErr);
622 #ifdef USE_AKONADI
623 UpdateStatus deleteEvents(QVector<KAEvent>& events, bool archive, QWidget* msgParent, bool showKOrgErr)
624 #else
625 UpdateStatus deleteEvents(KAEvent::List& events, bool archive, QWidget* msgParent, bool showKOrgErr)
626 #endif
628 kDebug() << events.count();
629 if (events.isEmpty())
630 return UPDATE_OK;
631 int warnErr = 0;
632 int warnKOrg = 0;
633 UpdateStatus status = UPDATE_OK;
634 AlarmCalendar* cal = AlarmCalendar::resources();
635 bool deleteWakeFromSuspendAlarm = false;
636 QString wakeFromSuspendId = checkRtcWakeConfig().value(0);
637 for (int i = 0, end = events.count(); i < end; ++i)
639 // Save the event details in the calendar file, and get the new event ID
640 #ifdef USE_AKONADI
641 KAEvent* event = &events[i];
642 #else
643 KAEvent* event = events[i];
644 #endif
645 QString id = event->id();
647 #ifndef USE_AKONADI
648 // Update the window lists and clear stored command errors
649 EventListModel::alarms()->removeEvent(id);
650 event->setCommandError(KAEvent::CMD_NO_ERROR);
651 #endif
653 // Delete the event from the calendar file
654 if (event->category() != KAlarm::CalEvent::ARCHIVED)
656 if (event->copyToKOrganizer())
658 // The event was shown in KOrganizer, so tell KOrganizer to
659 // delete it. But ignore errors, because the user could have
660 // manually deleted it from KOrganizer since it was set up.
661 UpdateStatus st = deleteFromKOrganizer(id);
662 if (st != UPDATE_OK)
664 ++warnKOrg;
665 if (st > status)
666 status = st;
669 if (archive && event->toBeArchived())
671 KAEvent ev(*event);
672 addArchivedEvent(ev); // this changes the event ID to an archived ID
675 #ifdef USE_AKONADI
676 if (!cal->deleteEvent(*event, false)) // don't save calendar after deleting
677 #else
678 if (!cal->deleteEvent(id, false)) // don't save calendar after deleting
679 #endif
681 status = UPDATE_ERROR;
682 ++warnErr;
685 if (id == wakeFromSuspendId)
686 deleteWakeFromSuspendAlarm = true;
688 // Remove "Don't show error messages again" for this alarm
689 setDontShowErrors(id);
692 if (warnErr == events.count())
693 status = UPDATE_FAILED;
694 else if (!cal->save()) // save the calendars now
696 status = SAVE_FAILED;
697 warnErr = events.count();
699 if (status != UPDATE_OK && msgParent)
700 displayUpdateError(msgParent, status, ERR_DELETE, warnErr, warnKOrg, showKOrgErr);
702 // Remove any wake-from-suspend scheduled for a deleted alarm
703 if (deleteWakeFromSuspendAlarm && !wakeFromSuspendId.isEmpty())
704 cancelRtcWake(msgParent, wakeFromSuspendId);
706 return status;
709 /******************************************************************************
710 * Delete templates from the calendar file and from every template list view.
712 #ifdef USE_AKONADI
713 UpdateStatus deleteTemplates(const KAEvent::List& events, QWidget* msgParent)
714 #else
715 UpdateStatus deleteTemplates(const QStringList& eventIDs, QWidget* msgParent)
716 #endif
718 #ifdef USE_AKONADI
719 int count = events.count();
720 #else
721 int count = eventIDs.count();
722 #endif
723 kDebug() << count;
724 if (!count)
725 return UPDATE_OK;
726 int warnErr = 0;
727 UpdateStatus status = UPDATE_OK;
728 AlarmCalendar* cal = AlarmCalendar::resources();
729 for (int i = 0, end = count; i < end; ++i)
731 // Update the window lists
732 #ifndef USE_AKONADI
733 QString id = eventIDs[i];
734 EventListModel::templates()->removeEvent(id);
735 #endif
737 // Delete the template from the calendar file
738 AlarmCalendar* cal = AlarmCalendar::resources();
739 #ifdef USE_AKONADI
740 if (!cal->deleteEvent(*events[i], false)) // don't save calendar after deleting
741 #else
742 if (!cal->deleteEvent(id, false)) // don't save calendar after deleting
743 #endif
745 status = UPDATE_ERROR;
746 ++warnErr;
750 if (warnErr == count)
751 status = UPDATE_FAILED;
752 else if (!cal->save()) // save the calendars now
754 status = SAVE_FAILED;
755 warnErr = count;
757 if (status != UPDATE_OK && msgParent)
758 displayUpdateError(msgParent, status, ERR_TEMPLATE, warnErr);
759 return status;
762 /******************************************************************************
763 * Delete an alarm from the display calendar.
765 void deleteDisplayEvent(const QString& eventID)
767 kDebug() << eventID;
768 AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen();
769 if (cal)
770 #ifdef USE_AKONADI
771 cal->deleteDisplayEvent(eventID, true); // save calendar after deleting
772 #else
773 cal->deleteEvent(eventID, true); // save calendar after deleting
774 #endif
777 /******************************************************************************
778 * Undelete archived alarms, and update every main window instance.
779 * The archive bit is set to ensure that they get re-archived if deleted again.
780 * 'ineligibleIDs' is filled in with the IDs of any ineligible events.
782 #ifdef USE_AKONADI
783 UpdateStatus reactivateEvent(KAEvent& event, Collection* calendar, QWidget* msgParent, bool showKOrgErr)
784 #else
785 UpdateStatus reactivateEvent(KAEvent& event, AlarmResource* calendar, QWidget* msgParent, bool showKOrgErr)
786 #endif
788 QStringList ids;
789 #ifdef USE_AKONADI
790 QVector<KAEvent> events(1, event);
791 #else
792 KAEvent::List events;
793 events += &event;
794 #endif
795 return reactivateEvents(events, ids, calendar, msgParent, showKOrgErr);
798 #ifdef USE_AKONADI
799 UpdateStatus reactivateEvents(QVector<KAEvent>& events, QStringList& ineligibleIDs, Collection* col, QWidget* msgParent, bool showKOrgErr)
800 #else
801 UpdateStatus reactivateEvents(KAEvent::List& events, QStringList& ineligibleIDs, AlarmResource* resource, QWidget* msgParent, bool showKOrgErr)
802 #endif
804 kDebug() << events.count();
805 ineligibleIDs.clear();
806 if (events.isEmpty())
807 return UPDATE_OK;
808 int warnErr = 0;
809 int warnKOrg = 0;
810 UpdateStatus status = UPDATE_OK;
811 #ifdef USE_AKONADI
812 Collection collection;
813 if (col)
814 collection = *col;
815 if (!collection.isValid())
816 collection = CollectionControlModel::instance()->destination(KAlarm::CalEvent::ACTIVE, msgParent);
817 if (!collection.isValid())
818 #else
819 if (!resource)
820 resource = AlarmResources::instance()->destination(KAlarm::CalEvent::ACTIVE, msgParent);
821 if (!resource)
822 #endif
824 kDebug() << "No calendar";
825 status = UPDATE_FAILED;
826 warnErr = events.count();
828 else
830 QString selectID;
831 int count = 0;
832 AlarmCalendar* cal = AlarmCalendar::resources();
833 KDateTime now = KDateTime::currentUtcDateTime();
834 for (int i = 0, end = events.count(); i < end; ++i)
836 // Delete the event from the archived resource
837 #ifdef USE_AKONADI
838 KAEvent* event = &events[i];
839 #else
840 KAEvent* event = events[i];
841 #endif
842 if (event->category() != KAlarm::CalEvent::ARCHIVED
843 || !event->occursAfter(now, true))
845 ineligibleIDs += event->id();
846 continue;
848 ++count;
850 #ifdef USE_AKONADI
851 KAEvent newevent(*event);
852 KAEvent* const newev = &newevent;
853 #else
854 KAEvent* newev = new KAEvent(*event);
855 QString oldid = event->id();
856 #endif
857 newev->setCategory(KAlarm::CalEvent::ACTIVE); // this changes the event ID
858 if (newev->recurs() || newev->repetition())
859 newev->setNextOccurrence(now); // skip any recurrences in the past
860 newev->setArchive(); // ensure that it gets re-archived if it is deleted
862 // Save the event details in the calendar file.
863 // This converts the event ID.
864 #ifdef USE_AKONADI
865 if (!cal->addEvent(newevent, msgParent, true, &collection))
866 #else
867 if (!cal->addEvent(newev, msgParent, true, resource))
868 #endif
870 #ifndef USE_AKONADI
871 delete newev;
872 #endif
873 status = UPDATE_ERROR;
874 ++warnErr;
875 continue;
877 if (newev->copyToKOrganizer())
879 UpdateStatus st = sendToKOrganizer(newev); // tell KOrganizer to show the event
880 if (st != UPDATE_OK)
882 ++warnKOrg;
883 if (st > status)
884 status = st;
888 #ifndef USE_AKONADI
889 // Update the window lists
890 EventListModel::alarms()->updateEvent(oldid, newev);
891 // selectID = newev->id();
892 #endif
894 #ifdef USE_AKONADI
895 if (cal->event(event->id()) // no error if event doesn't exist in archived resource
896 && !cal->deleteEvent(*event, false)) // don't save calendar after deleting
897 #else
898 if (cal->event(oldid) // no error if event doesn't exist in archived resource
899 && !cal->deleteEvent(oldid, false)) // don't save calendar after deleting
900 #endif
902 status = UPDATE_ERROR;
903 ++warnErr;
905 #ifdef USE_AKONADI
906 events[i] = newevent;
907 #else
908 events[i] = newev;
909 #endif
912 if (warnErr == count)
913 status = UPDATE_FAILED;
914 // Save the calendars, even if all events failed, since more than one calendar was updated
915 if (!cal->save() && status != UPDATE_FAILED)
917 status = SAVE_FAILED;
918 warnErr = count;
921 if (status != UPDATE_OK && msgParent)
922 displayUpdateError(msgParent, status, ERR_REACTIVATE, warnErr, warnKOrg, showKOrgErr);
923 return status;
926 /******************************************************************************
927 * Enable or disable alarms in the calendar file and in every main window instance.
928 * The new events will have the same event IDs as the old ones.
930 #ifdef USE_AKONADI
931 UpdateStatus enableEvents(QVector<KAEvent>& events, bool enable, QWidget* msgParent)
932 #else
933 UpdateStatus enableEvents(KAEvent::List& events, bool enable, QWidget* msgParent)
934 #endif
936 kDebug() << events.count();
937 if (events.isEmpty())
938 return UPDATE_OK;
939 UpdateStatus status = UPDATE_OK;
940 AlarmCalendar* cal = AlarmCalendar::resources();
941 bool deleteWakeFromSuspendAlarm = false;
942 QString wakeFromSuspendId = checkRtcWakeConfig().value(0);
943 for (int i = 0, end = events.count(); i < end; ++i)
945 #ifdef USE_AKONADI
946 KAEvent* event = &events[i];
947 #else
948 KAEvent* event = events[i];
949 #endif
950 if (event->category() == KAlarm::CalEvent::ACTIVE
951 && enable != event->enabled())
953 event->setEnabled(enable);
955 if (!enable && event->id() == wakeFromSuspendId)
956 deleteWakeFromSuspendAlarm = true;
958 // Update the event in the calendar file
959 KAEvent* newev = cal->updateEvent(event);
960 if (!newev)
961 kError() << "Error updating event in calendar:" << event->id();
962 else
964 cal->disabledChanged(newev);
966 // If we're disabling a display alarm, close any message window
967 if (!enable && (event->actionTypes() & KAEvent::ACT_DISPLAY))
969 MessageWin* win = MessageWin::findEvent(event->id());
970 delete win;
973 #ifndef USE_AKONADI
974 // Update the window lists
975 EventListModel::alarms()->updateEvent(newev);
976 #endif
981 if (!cal->save())
982 status = SAVE_FAILED;
983 if (status != UPDATE_OK && msgParent)
984 displayUpdateError(msgParent, status, ERR_ADD, events.count(), 0);
986 // Remove any wake-from-suspend scheduled for a disabled alarm
987 if (deleteWakeFromSuspendAlarm && !wakeFromSuspendId.isEmpty())
988 cancelRtcWake(msgParent, wakeFromSuspendId);
990 return status;
993 /******************************************************************************
994 * This method must only be called from the main KAlarm queue processing loop,
995 * to prevent asynchronous calendar operations interfering with one another.
997 * Purge all archived events from the default archived alarm resource whose end
998 * time is longer ago than 'purgeDays'. All events are deleted if 'purgeDays' is
999 * zero.
1001 void purgeArchive(int purgeDays)
1003 if (purgeDays < 0)
1004 return;
1005 kDebug() << purgeDays;
1006 QDate cutoff = KDateTime::currentLocalDate().addDays(-purgeDays);
1007 #ifdef USE_AKONADI
1008 Collection collection = CollectionControlModel::getStandard(KAlarm::CalEvent::ARCHIVED);
1009 if (!collection.isValid())
1010 return;
1011 KAEvent::List events = AlarmCalendar::resources()->events(collection);
1012 for (int i = 0; i < events.count(); )
1014 if (purgeDays && events[i]->createdDateTime().date() >= cutoff)
1015 events.remove(i);
1016 else
1017 ++i;
1019 #else
1020 AlarmResource* resource = AlarmResources::instance()->getStandardResource(KAlarm::CalEvent::ARCHIVED);
1021 if (!resource)
1022 return;
1023 KAEvent::List events = AlarmCalendar::resources()->events(resource);
1024 for (int i = 0; i < events.count(); )
1026 KAEvent* event = events[i];
1027 Incidence* kcalIncidence = resource->incidence(event->id());
1028 if (purgeDays && kcalIncidence && kcalIncidence->created().date() >= cutoff)
1029 events.remove(i);
1030 else
1031 EventListModel::alarms()->removeEvent(events[i++]); // update the window lists
1033 #endif
1034 if (!events.isEmpty())
1035 AlarmCalendar::resources()->purgeEvents(events); // delete the events and save the calendar
1038 /******************************************************************************
1039 * Display an error message about an error when saving an event.
1041 void displayUpdateError(QWidget* parent, UpdateStatus status, UpdateError code, int nAlarms, int nKOrgAlarms, bool showKOrgError)
1043 QString errmsg;
1044 if (status > UPDATE_KORG_ERR)
1046 switch (code)
1048 case ERR_ADD:
1049 case ERR_MODIFY:
1050 errmsg = (nAlarms > 1) ? i18nc("@info", "Error saving alarms")
1051 : i18nc("@info", "Error saving alarm");
1052 break;
1053 case ERR_DELETE:
1054 errmsg = (nAlarms > 1) ? i18nc("@info", "Error deleting alarms")
1055 : i18nc("@info", "Error deleting alarm");
1056 break;
1057 case ERR_REACTIVATE:
1058 errmsg = (nAlarms > 1) ? i18nc("@info", "Error saving reactivated alarms")
1059 : i18nc("@info", "Error saving reactivated alarm");
1060 break;
1061 case ERR_TEMPLATE:
1062 errmsg = (nAlarms > 1) ? i18nc("@info", "Error saving alarm templates")
1063 : i18nc("@info", "Error saving alarm template");
1064 break;
1066 KAMessageBox::error(parent, errmsg);
1068 else if (showKOrgError)
1069 displayKOrgUpdateError(parent, code, status, nKOrgAlarms);
1072 /******************************************************************************
1073 * Display an error message corresponding to a specified alarm update error code.
1075 void displayKOrgUpdateError(QWidget* parent, UpdateError code, UpdateStatus korgError, int nAlarms)
1077 QString errmsg;
1078 switch (code)
1080 case ERR_ADD:
1081 case ERR_REACTIVATE:
1082 errmsg = (nAlarms > 1) ? i18nc("@info", "Unable to show alarms in KOrganizer")
1083 : i18nc("@info", "Unable to show alarm in KOrganizer");
1084 break;
1085 case ERR_MODIFY:
1086 errmsg = i18nc("@info", "Unable to update alarm in KOrganizer");
1087 break;
1088 case ERR_DELETE:
1089 errmsg = (nAlarms > 1) ? i18nc("@info", "Unable to delete alarms from KOrganizer")
1090 : i18nc("@info", "Unable to delete alarm from KOrganizer");
1091 break;
1092 case ERR_TEMPLATE:
1093 return;
1095 QString msg;
1096 if (korgError == UPDATE_KORG_ERRSTART)
1097 msg = i18nc("@info", "<para>%1</para><para>(KOrganizer not fully started)</para>", errmsg);
1098 else if (korgError == UPDATE_KORG_ERR)
1099 msg = i18nc("@info", "<para>%1</para><para>(Error communicating with KOrganizer)</para>", errmsg);
1100 else
1101 msg = errmsg;
1102 KAMessageBox::error(parent, msg);
1105 /******************************************************************************
1106 * Execute a New Alarm dialog for the specified alarm type.
1108 void editNewAlarm(EditAlarmDlg::Type type, QWidget* parent)
1110 execNewAlarmDlg(EditAlarmDlg::create(false, type, parent));
1113 /******************************************************************************
1114 * Execute a New Alarm dialog for the specified alarm type.
1116 void editNewAlarm(KAEvent::SubAction action, QWidget* parent, const AlarmText* text)
1118 bool setAction = false;
1119 EditAlarmDlg::Type type;
1120 switch (action)
1122 case KAEvent::MESSAGE:
1123 case KAEvent::FILE:
1124 type = EditAlarmDlg::DISPLAY;
1125 setAction = true;
1126 break;
1127 case KAEvent::COMMAND:
1128 type = EditAlarmDlg::COMMAND;
1129 break;
1130 case KAEvent::EMAIL:
1131 type = EditAlarmDlg::EMAIL;
1132 break;
1133 case KAEvent::AUDIO:
1134 type = EditAlarmDlg::AUDIO;
1135 break;
1136 default:
1137 return;
1139 EditAlarmDlg* editDlg = EditAlarmDlg::create(false, type, parent);
1140 if (setAction || text)
1141 editDlg->setAction(action, *text);
1142 execNewAlarmDlg(editDlg);
1145 /******************************************************************************
1146 * Execute a New Alarm dialog, optionally either presetting it to the supplied
1147 * event, or setting the action and text.
1149 void editNewAlarm(const KAEvent* preset, QWidget* parent)
1151 execNewAlarmDlg(EditAlarmDlg::create(false, preset, true, parent));
1154 /******************************************************************************
1155 * Common code for editNewAlarm() variants.
1157 void execNewAlarmDlg(EditAlarmDlg* editDlg)
1159 // Create a PrivateNewAlarmDlg parented by editDlg.
1160 // It will be deleted when editDlg is closed.
1161 new PrivateNewAlarmDlg(editDlg);
1162 editDlg->show();
1163 editDlg->raise();
1164 editDlg->activateWindow();
1167 PrivateNewAlarmDlg::PrivateNewAlarmDlg(EditAlarmDlg* dlg)
1168 : QObject(dlg)
1170 connect(dlg, SIGNAL(accepted()), SLOT(okClicked()));
1173 /******************************************************************************
1174 * Called when the dialogue is accepted (e.g. by clicking the OK button).
1175 * Creates the event specified in the instance's dialogue.
1177 void PrivateNewAlarmDlg::okClicked()
1179 accept(static_cast<EditAlarmDlg*>(parent()));
1182 /******************************************************************************
1183 * Creates the event specified in a given dialogue.
1185 void PrivateNewAlarmDlg::accept(EditAlarmDlg* editDlg)
1187 KAEvent event;
1188 #ifdef USE_AKONADI
1189 Collection calendar;
1190 #else
1191 AlarmResource* calendar;
1192 #endif
1193 editDlg->getEvent(event, calendar);
1195 // Add the alarm to the displayed lists and to the calendar file
1196 #ifdef USE_AKONADI
1197 UpdateStatus status = addEvent(event, &calendar, editDlg);
1198 #else
1199 UpdateStatus status = addEvent(event, calendar, editDlg);
1200 #endif
1201 switch (status)
1203 case UPDATE_FAILED:
1204 return;
1205 case UPDATE_KORG_ERR:
1206 case UPDATE_KORG_ERRSTART:
1207 case UPDATE_KORG_FUNCERR:
1208 displayKOrgUpdateError(editDlg, ERR_ADD, status, 1);
1209 break;
1210 default:
1211 break;
1213 Undo::saveAdd(event, calendar);
1215 outputAlarmWarnings(editDlg, &event);
1218 /******************************************************************************
1219 * Display the alarm edit dialog to edit a new alarm, preset with a template.
1221 bool editNewAlarm(const QString& templateName, QWidget* parent)
1223 if (!templateName.isEmpty())
1225 KAEvent* templateEvent = AlarmCalendar::resources()->templateEvent(templateName);
1226 if (templateEvent->isValid())
1228 editNewAlarm(templateEvent, parent);
1229 return true;
1231 kWarning() << templateName << ": template not found";
1233 return false;
1236 /******************************************************************************
1237 * Create a new template.
1239 void editNewTemplate(EditAlarmDlg::Type type, QWidget* parent)
1241 ::editNewTemplate(type, 0, parent);
1244 /******************************************************************************
1245 * Create a new template, based on an existing event or template.
1247 void editNewTemplate(const KAEvent* preset, QWidget* parent)
1249 ::editNewTemplate(EditAlarmDlg::Type(0), preset, parent);
1252 /******************************************************************************
1253 * Check the config as to whether there is a wake-on-suspend alarm pending, and
1254 * if so, delete it from the config if it has expired.
1255 * If 'checkExists' is true, the config entry will only be returned if the
1256 * event exists.
1257 * Reply = config entry: [0] = event ID, [1] = trigger time (time_t).
1258 * = empty list if none or expired.
1260 QStringList checkRtcWakeConfig(bool checkEventExists)
1262 KConfigGroup config(KGlobal::config(), "General");
1263 QStringList params = config.readEntry("RtcWake", QStringList());
1264 if (params.count() == 2 && params[1].toUInt() > KDateTime::currentUtcDateTime().toTime_t())
1266 if (checkEventExists && !AlarmCalendar::getEvent(params[0]))
1267 return QStringList();
1268 return params; // config entry is valid
1270 if (!params.isEmpty())
1272 config.deleteEntry("RtcWake"); // delete the expired config entry
1273 config.sync();
1275 return QStringList();
1278 /******************************************************************************
1279 * Delete any wake-on-suspend alarm from the config.
1281 void deleteRtcWakeConfig()
1283 KConfigGroup config(KGlobal::config(), "General");
1284 config.deleteEntry("RtcWake");
1285 config.sync();
1288 /******************************************************************************
1289 * Delete any wake-on-suspend alarm, optionally only for a specified event.
1291 void cancelRtcWake(QWidget* msgParent, const QString& eventId)
1293 QStringList wakeup = checkRtcWakeConfig();
1294 if (!wakeup.isEmpty() && (eventId.isEmpty() || wakeup[0] == eventId))
1296 Private::instance()->mMsgParent = msgParent ? msgParent : MainWindow::mainMainWindow();
1297 QTimer::singleShot(0, Private::instance(), SLOT(cancelRtcWake()));
1301 /******************************************************************************
1302 * Delete any wake-on-suspend alarm.
1304 void Private::cancelRtcWake()
1306 // setRtcWakeTime will only work with a parent window specified
1307 setRtcWakeTime(0, mMsgParent);
1308 deleteRtcWakeConfig();
1309 KAMessageBox::information(mMsgParent, i18nc("info", "The scheduled Wake from Suspend has been cancelled."));
1312 /******************************************************************************
1313 * Set the wakeup time for the system.
1314 * Set 'triggerTime' to zero to cancel the wakeup.
1315 * Reply = true if successful.
1317 bool setRtcWakeTime(unsigned triggerTime, QWidget* parent)
1319 QVariantMap args;
1320 args["time"] = triggerTime;
1321 KAuth::Action action("org.kde.kalarmrtcwake.settimer");
1322 action.setHelperID("org.kde.kalarmrtcwake");
1323 action.setParentWidget(parent);
1324 action.setArguments(args);
1325 KAuth::ActionReply reply = action.execute();
1326 if (reply.failed())
1328 QString errmsg = reply.errorDescription();
1329 kDebug() << "Error code=" << reply.errorCode() << errmsg;
1330 if (errmsg.isEmpty())
1332 int errcode = reply.errorCode();
1333 switch (reply.type())
1335 case KAuth::ActionReply::KAuthError:
1336 kDebug() << "Authorisation error:" << errcode;
1337 switch (errcode)
1339 case KAuth::ActionReply::AuthorizationDenied:
1340 case KAuth::ActionReply::UserCancelled:
1341 return false; // the user should already know about this
1342 default:
1343 break;
1345 break;
1346 case KAuth::ActionReply::HelperError:
1347 kDebug() << "Helper error:" << errcode;
1348 errcode += 100; // make code distinguishable from KAuthError type
1349 break;
1350 default:
1351 break;
1353 errmsg = i18nc("@info", "Error obtaining authorization (%1)", errcode);
1355 KAMessageBox::information(parent, errmsg);
1356 return false;
1358 return true;
1361 } // namespace KAlarm
1362 namespace
1365 /******************************************************************************
1366 * Create a new template.
1367 * 'preset' is non-null to base it on an existing event or template; otherwise,
1368 * the alarm type is set to 'type'.
1370 void editNewTemplate(EditAlarmDlg::Type type, const KAEvent* preset, QWidget* parent)
1372 #ifdef USE_AKONADI
1373 if (CollectionControlModel::enabledCollections(KAlarm::CalEvent::TEMPLATE, true).isEmpty())
1374 #else
1375 if (!AlarmResources::instance()->activeCount(KAlarm::CalEvent::TEMPLATE, true))
1376 #endif
1378 KAMessageBox::sorry(parent, i18nc("@info", "You must enable a template calendar to save the template in"));
1379 return;
1381 // Use AutoQPointer to guard against crash on application exit while
1382 // the dialogue is still open. It prevents double deletion (both on
1383 // deletion of parent, and on return from this function).
1384 AutoQPointer<EditAlarmDlg> editDlg;
1385 if (preset)
1386 editDlg = EditAlarmDlg::create(true, preset, true, parent);
1387 else
1388 editDlg = EditAlarmDlg::create(true, type, parent);
1389 if (editDlg->exec() == QDialog::Accepted)
1391 KAEvent event;
1392 #ifdef USE_AKONADI
1393 Akonadi::Collection calendar;
1394 #else
1395 AlarmResource* calendar;
1396 #endif
1397 editDlg->getEvent(event, calendar);
1399 // Add the template to the displayed lists and to the calendar file
1400 #ifdef USE_AKONADI
1401 KAlarm::addTemplate(event, &calendar, editDlg);
1402 #else
1403 KAlarm::addTemplate(event, calendar, editDlg);
1404 #endif
1405 Undo::saveAdd(event, calendar);
1409 } // namespace
1410 namespace KAlarm
1413 /******************************************************************************
1414 * Open the Edit Alarm dialog to edit the specified alarm.
1415 * If the alarm is read-only or archived, the dialog is opened read-only.
1417 void editAlarm(KAEvent* event, QWidget* parent)
1419 #ifdef USE_AKONADI
1420 if (event->expired() || AlarmCalendar::resources()->eventReadOnly(event->itemId()))
1421 #else
1422 if (event->expired() || AlarmCalendar::resources()->eventReadOnly(event->id()))
1423 #endif
1425 viewAlarm(event, parent);
1426 return;
1428 QString id = event->id();
1429 // Use AutoQPointer to guard against crash on application exit while
1430 // the dialogue is still open. It prevents double deletion (both on
1431 // deletion of parent, and on return from this function).
1432 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(false, event, false, parent, EditAlarmDlg::RES_USE_EVENT_ID);
1433 if (editDlg->exec() == QDialog::Accepted)
1435 if (!AlarmCalendar::resources()->event(id))
1437 // Event has been deleted while the user was editing the alarm,
1438 // so treat it as a new alarm.
1439 PrivateNewAlarmDlg().accept(editDlg);
1440 return;
1442 KAEvent newEvent;
1443 #ifdef USE_AKONADI
1444 Collection calendar;
1445 #else
1446 AlarmResource* calendar;
1447 #endif
1448 bool changeDeferral = !editDlg->getEvent(newEvent, calendar);
1450 // Update the event in the displays and in the calendar file
1451 Undo::Event undo(*event, calendar);
1452 if (changeDeferral)
1454 // The only change has been to an existing deferral
1455 if (updateEvent(newEvent, editDlg, true) != UPDATE_OK) // keep the same event ID
1456 return; // failed to save event
1458 else
1460 UpdateStatus status = modifyEvent(*event, newEvent, editDlg);
1461 if (status != UPDATE_OK && status <= UPDATE_KORG_ERR)
1462 displayKOrgUpdateError(editDlg, ERR_MODIFY, status, 1);
1464 Undo::saveEdit(undo, newEvent);
1466 outputAlarmWarnings(editDlg, &newEvent);
1470 /******************************************************************************
1471 * Display the alarm edit dialog to edit a specified alarm.
1472 * An error occurs if the alarm is read-only or expired.
1474 bool editAlarm(const QString& eventID, QWidget* parent)
1476 KAEvent* event = AlarmCalendar::resources()->event(eventID);
1477 if (!event)
1479 kError() << eventID << ": event ID not found";
1480 return false;
1482 #ifdef USE_AKONADI
1483 if (AlarmCalendar::resources()->eventReadOnly(event->itemId()))
1484 #else
1485 if (AlarmCalendar::resources()->eventReadOnly(eventID))
1486 #endif
1488 kError() << eventID << ": read-only";
1489 return false;
1491 switch (event->category())
1493 case KAlarm::CalEvent::ACTIVE:
1494 case KAlarm::CalEvent::TEMPLATE:
1495 break;
1496 default:
1497 kError() << eventID << ": event not active or template";
1498 return false;
1500 editAlarm(event, parent);
1501 return true;
1504 /******************************************************************************
1505 * Open the Edit Alarm dialog to edit the specified template.
1506 * If the template is read-only, the dialog is opened read-only.
1508 void editTemplate(KAEvent* event, QWidget* parent)
1510 #ifdef USE_AKONADI
1511 if (AlarmCalendar::resources()->eventReadOnly(event->itemId()))
1512 #else
1513 if (AlarmCalendar::resources()->eventReadOnly(event->id()))
1514 #endif
1516 // The template is read-only, so make the dialogue read-only.
1517 // Use AutoQPointer to guard against crash on application exit while
1518 // the dialogue is still open. It prevents double deletion (both on
1519 // deletion of parent, and on return from this function).
1520 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(true, event, false, parent, EditAlarmDlg::RES_PROMPT, true);
1521 editDlg->exec();
1522 return;
1524 // Use AutoQPointer to guard against crash on application exit while
1525 // the dialogue is still open. It prevents double deletion (both on
1526 // deletion of parent, and on return from this function).
1527 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(true, event, false, parent, EditAlarmDlg::RES_USE_EVENT_ID);
1528 if (editDlg->exec() == QDialog::Accepted)
1530 KAEvent newEvent;
1531 #ifdef USE_AKONADI
1532 Akonadi::Collection calendar;
1533 #else
1534 AlarmResource* calendar;
1535 #endif
1536 editDlg->getEvent(newEvent, calendar);
1537 QString id = event->id();
1538 newEvent.setEventId(id);
1540 // Update the event in the displays and in the calendar file
1541 Undo::Event undo(*event, calendar);
1542 updateTemplate(newEvent, editDlg);
1543 Undo::saveEdit(undo, newEvent);
1547 /******************************************************************************
1548 * Open the Edit Alarm dialog to view the specified alarm (read-only).
1550 void viewAlarm(const KAEvent* event, QWidget* parent)
1552 // Use AutoQPointer to guard against crash on application exit while
1553 // the dialogue is still open. It prevents double deletion (both on
1554 // deletion of parent, and on return from this function).
1555 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(false, event, false, parent, EditAlarmDlg::RES_PROMPT, true);
1556 editDlg->exec();
1559 /******************************************************************************
1560 * Called when OK is clicked in the alarm edit dialog invoked by the Edit button
1561 * in an alarm message window.
1562 * Updates the alarm calendar and closes the dialog.
1564 #ifdef USE_AKONADI
1565 void updateEditedAlarm(EditAlarmDlg* editDlg, KAEvent& event, Collection& calendar)
1566 #else
1567 void updateEditedAlarm(EditAlarmDlg* editDlg, KAEvent& event, AlarmResource* calendar)
1568 #endif
1570 kDebug();
1571 KAEvent newEvent;
1572 #ifdef USE_AKONADI
1573 Akonadi::Collection cal;
1574 #else
1575 AlarmResource* cal;
1576 #endif
1577 editDlg->getEvent(newEvent, cal);
1579 // Update the displayed lists and the calendar file
1580 UpdateStatus status;
1581 if (AlarmCalendar::resources()->event(event.id()))
1583 // The old alarm hasn't expired yet, so replace it
1584 Undo::Event undo(event, calendar);
1585 status = modifyEvent(event, newEvent, editDlg);
1586 Undo::saveEdit(undo, newEvent);
1588 else
1590 // The old event has expired, so simply create a new one
1591 #ifdef USE_AKONADI
1592 status = addEvent(newEvent, &calendar, editDlg);
1593 #else
1594 status = addEvent(newEvent, calendar, editDlg);
1595 #endif
1596 Undo::saveAdd(newEvent, calendar);
1599 if (status != UPDATE_OK && status <= UPDATE_KORG_ERR)
1600 displayKOrgUpdateError(editDlg, ERR_MODIFY, status, 1);
1601 outputAlarmWarnings(editDlg, &newEvent);
1603 editDlg->close();
1606 /******************************************************************************
1607 * Returns a list of all alarm templates.
1608 * If shell commands are disabled, command alarm templates are omitted.
1610 KAEvent::List templateList()
1612 KAEvent::List templates;
1613 bool includeCmdAlarms = ShellProcess::authorised();
1614 KAEvent::List events = AlarmCalendar::resources()->events(KAlarm::CalEvent::TEMPLATE);
1615 for (int i = 0, end = events.count(); i < end; ++i)
1617 KAEvent* event = events[i];
1618 if (includeCmdAlarms || !(event->actionTypes() & KAEvent::ACT_COMMAND))
1619 templates.append(event);
1621 return templates;
1624 /******************************************************************************
1625 * To be called after an alarm has been edited.
1626 * Prompt the user to re-enable alarms if they are currently disabled, and if
1627 * it's an email alarm, warn if no 'From' email address is configured.
1629 void outputAlarmWarnings(QWidget* parent, const KAEvent* event)
1631 if (event && event->actionTypes() == KAEvent::ACT_EMAIL
1632 && Preferences::emailAddress().isEmpty())
1633 KAMessageBox::information(parent, i18nc("@info Please set the 'From' email address...",
1634 "<para>%1</para><para>Please set it in the Configuration dialog.</para>", KAMail::i18n_NeedFromEmailAddress()));
1636 if (!theApp()->alarmsEnabled())
1638 if (KAMessageBox::warningYesNo(parent, i18nc("@info", "<para>Alarms are currently disabled.</para><para>Do you want to enable alarms now?</para>"),
1639 QString(), KGuiItem(i18nc("@action:button", "Enable")), KGuiItem(i18nc("@action:button", "Keep Disabled")),
1640 QLatin1String("EditEnableAlarms"))
1641 == KMessageBox::Yes)
1642 theApp()->setAlarmsEnabled(true);
1646 /******************************************************************************
1647 * Reload the calendar.
1649 void refreshAlarms()
1651 kDebug();
1652 if (!refreshAlarmsQueued)
1654 refreshAlarmsQueued = true;
1655 theApp()->processQueue();
1659 /******************************************************************************
1660 * This method must only be called from the main KAlarm queue processing loop,
1661 * to prevent asynchronous calendar operations interfering with one another.
1663 * If refreshAlarms() has been called, reload the calendars.
1665 void refreshAlarmsIfQueued()
1667 if (refreshAlarmsQueued)
1669 kDebug();
1670 AlarmCalendar::resources()->reload();
1672 // Close any message windows for alarms which are now disabled
1673 KAEvent::List events = AlarmCalendar::resources()->events(KAlarm::CalEvent::ACTIVE);
1674 for (int i = 0, end = events.count(); i < end; ++i)
1676 KAEvent* event = events[i];
1677 if (!event->enabled() && (event->actionTypes() & KAEvent::ACT_DISPLAY))
1679 MessageWin* win = MessageWin::findEvent(event->id());
1680 delete win;
1684 MainWindow::refresh();
1685 refreshAlarmsQueued = false;
1689 /******************************************************************************
1690 * Start KMail if it isn't already running, optionally minimised.
1691 * Reply = reason for failure to run KMail (which may be the empty string)
1692 * = null string if success.
1694 QString runKMail(bool minimise)
1696 QDBusReply<bool> reply = QDBusConnection::sessionBus().interface()->isServiceRegistered(KMAIL_DBUS_SERVICE);
1697 if (!reply.isValid() || !reply.value())
1699 // Program is not already running, so start it
1700 QString errmsg;
1701 if (minimise && Private::startKMailMinimised())
1702 return QString();
1703 if (KToolInvocation::startServiceByDesktopName(QLatin1String("kmail"), QString(), &errmsg))
1705 kError() << "Couldn't start KMail (" << errmsg << ")";
1706 return i18nc("@info", "Unable to start <application>KMail</application><nl/>(<message>%1</message>)", errmsg);
1709 return QString();
1712 /******************************************************************************
1713 * Start KMail, minimised.
1714 * This code is taken from kstart in kdebase.
1716 bool Private::startKMailMinimised()
1718 #ifdef Q_WS_X11
1719 NETRootInfo i(QX11Info::display(), NET::Supported);
1720 if (i.isSupported(NET::WM2KDETemporaryRules))
1722 kDebug() << "using rules";
1723 KXMessages msg;
1724 QString message = "wmclass=kmail\nwmclassmatch=1\n" // 1 = exact match
1725 "wmclasscomplete=false\n"
1726 "minimize=true\nminimizerule=3\n"
1727 "type=" + QString().setNum(NET::Normal) + "\ntyperule=2";
1728 msg.broadcastMessage("_KDE_NET_WM_TEMPORARY_RULES", message, -1, false);
1729 qApp->flush();
1731 else
1733 // Connect to window add to get the NEW windows
1734 kDebug() << "connecting to window add";
1735 connect(KWindowSystem::self(), SIGNAL(windowAdded(WId)), instance(), SLOT(windowAdded(WId)));
1737 // Propagate the app startup notification info to the started app.
1738 // We are not using KApplication, so the env remained set.
1739 KStartupInfoId id = KStartupInfo::currentStartupIdEnv();
1740 KProcess* proc = new KProcess;
1741 (*proc) << "kmail";
1742 int pid = proc->startDetached();
1743 if (!pid)
1745 KStartupInfo::sendFinish(id); // failed to start
1746 return false;
1748 KStartupInfoData data;
1749 data.addPid(pid);
1750 data.setName("kmail");
1751 data.setBin("kmail");
1752 KStartupInfo::sendChange(id, data);
1753 return true;
1754 #else
1755 return false;
1756 #endif
1759 /******************************************************************************
1760 * Called when a window is created, to minimise it.
1761 * This code is taken from kstart in kdebase.
1763 void Private::windowAdded(WId w)
1765 #ifdef Q_WS_X11
1766 static const int SUPPORTED_TYPES = NET::NormalMask | NET::DesktopMask | NET::DockMask
1767 | NET::ToolbarMask | NET::MenuMask | NET::DialogMask
1768 | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask;
1769 KWindowInfo kwinfo = KWindowSystem::windowInfo(w, NET::WMWindowType | NET::WMName);
1770 if (kwinfo.windowType(SUPPORTED_TYPES) == NET::TopMenu
1771 || kwinfo.windowType(SUPPORTED_TYPES) == NET::Toolbar
1772 || kwinfo.windowType(SUPPORTED_TYPES) == NET::Desktop)
1773 return; // always ignore these window types
1775 QX11Info qxinfo;
1776 XWithdrawWindow(QX11Info::display(), w, qxinfo.screen());
1777 QApplication::flush();
1779 NETWinInfo info(QX11Info::display(), w, QX11Info::appRootWindow(), NET::WMState);
1780 XWMHints* hints = XGetWMHints(QX11Info::display(), w);
1781 if (hints)
1783 hints->flags |= StateHint;
1784 hints->initial_state = IconicState;
1785 XSetWMHints(QX11Info::display(), w, hints);
1786 XFree(hints);
1788 info.setWindowType(NET::Normal);
1790 XSync(QX11Info::display(), False);
1791 XMapWindow(QX11Info::display(), w);
1792 XSync(QX11Info::display(), False);
1793 QApplication::flush();
1794 #endif
1797 /******************************************************************************
1798 * The "Don't show again" option for error messages is personal to the user on a
1799 * particular computer. For example, he may want to inhibit error messages only
1800 * on his laptop. So the status is not stored in the alarm calendar, but in the
1801 * user's local KAlarm data directory.
1802 ******************************************************************************/
1804 /******************************************************************************
1805 * Return the Don't-show-again error message tags set for a specified alarm ID.
1807 QStringList dontShowErrors(const QString& eventId)
1809 if (eventId.isEmpty())
1810 return QStringList();
1811 KConfig config(KStandardDirs::locateLocal("appdata", ALARM_OPTS_FILE));
1812 KConfigGroup group(&config, DONT_SHOW_ERRORS_GROUP);
1813 return group.readEntry(eventId, QStringList());
1816 /******************************************************************************
1817 * Check whether the specified Don't-show-again error message tag is set for an
1818 * alarm ID.
1820 bool dontShowErrors(const QString& eventId, const QString& tag)
1822 if (tag.isEmpty() || eventId.isEmpty())
1823 return false;
1824 QStringList tags = dontShowErrors(eventId);
1825 return tags.indexOf(tag) >= 0;
1828 /******************************************************************************
1829 * Reset the Don't-show-again error message tags for an alarm ID.
1830 * If 'tags' is empty, the config entry is deleted.
1832 void setDontShowErrors(const QString& eventId, const QStringList& tags)
1834 if (eventId.isEmpty())
1835 return;
1836 KConfig config(KStandardDirs::locateLocal("appdata", ALARM_OPTS_FILE));
1837 KConfigGroup group(&config, DONT_SHOW_ERRORS_GROUP);
1838 if (tags.isEmpty())
1839 group.deleteEntry(eventId);
1840 else
1841 group.writeEntry(eventId, tags);
1842 group.sync();
1845 /******************************************************************************
1846 * Set the specified Don't-show-again error message tag for an alarm ID.
1847 * Existing tags are unaffected.
1849 void setDontShowErrors(const QString& eventId, const QString& tag)
1851 if (tag.isEmpty() || eventId.isEmpty())
1852 return;
1853 KConfig config(KStandardDirs::locateLocal("appdata", ALARM_OPTS_FILE));
1854 KConfigGroup group(&config, DONT_SHOW_ERRORS_GROUP);
1855 QStringList tags = group.readEntry(eventId, QStringList());
1856 if (tags.indexOf(tag) < 0)
1858 tags += tag;
1859 group.writeEntry(eventId, tags);
1860 group.sync();
1864 /******************************************************************************
1865 * Read the size for the specified window from the config file, for the
1866 * current screen resolution.
1867 * Reply = true if size set in the config file, in which case 'result' is set
1868 * = false if no size is set, in which case 'result' is unchanged.
1870 bool readConfigWindowSize(const char* window, QSize& result, int* splitterWidth)
1872 KConfigGroup config(KGlobal::config(), window);
1873 QWidget* desktop = KApplication::desktop();
1874 QSize s = QSize(config.readEntry(QString::fromLatin1("Width %1").arg(desktop->width()), (int)0),
1875 config.readEntry(QString::fromLatin1("Height %1").arg(desktop->height()), (int)0));
1876 if (s.isEmpty())
1877 return false;
1878 result = s;
1879 if (splitterWidth)
1880 *splitterWidth = config.readEntry(QString::fromLatin1("Splitter %1").arg(desktop->width()), -1);
1881 return true;
1884 /******************************************************************************
1885 * Write the size for the specified window to the config file, for the
1886 * current screen resolution.
1888 void writeConfigWindowSize(const char* window, const QSize& size, int splitterWidth)
1890 KConfigGroup config(KGlobal::config(), window);
1891 QWidget* desktop = KApplication::desktop();
1892 config.writeEntry(QString::fromLatin1("Width %1").arg(desktop->width()), size.width());
1893 config.writeEntry(QString::fromLatin1("Height %1").arg(desktop->height()), size.height());
1894 if (splitterWidth >= 0)
1895 config.writeEntry(QString::fromLatin1("Splitter %1").arg(desktop->width()), splitterWidth);
1896 config.sync();
1899 /******************************************************************************
1900 * Check from its mime type whether a file appears to be a text or image file.
1901 * If a text file, its type is distinguished.
1902 * Reply = file type.
1904 FileType fileType(const KMimeType::Ptr& mimetype)
1906 if (mimetype->is("text/html"))
1907 return TextFormatted;
1908 if (mimetype->is("application/x-executable"))
1909 return TextApplication;
1910 if (mimetype->is("text/plain"))
1911 return TextPlain;
1912 if (mimetype->name().startsWith(QLatin1String("image/")))
1913 return Image;
1914 return Unknown;
1917 /******************************************************************************
1918 * Check that a file exists and is a plain readable file.
1919 * Updates 'filename' and 'url' even if an error occurs, since 'filename' may
1920 * be needed subsequently by showFileErrMessage().
1922 FileErr checkFileExists(QString& filename, KUrl& url)
1924 url = KUrl();
1925 FileErr err = FileErr_None;
1926 QString file = filename;
1927 QRegExp f("^file:/+");
1928 if (f.indexIn(file) >= 0)
1929 file = file.mid(f.matchedLength() - 1);
1930 // Convert any relative file path to absolute
1931 // (using home directory as the default)
1932 int i = file.indexOf(QLatin1Char('/'));
1933 if (i > 0 && file[i - 1] == QLatin1Char(':'))
1935 url = file;
1936 url.cleanPath();
1937 filename = url.prettyUrl();
1938 KIO::UDSEntry uds;
1939 if (!KIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow()))
1940 err = FileErr_Nonexistent;
1941 else
1943 KFileItem fi(uds, url);
1944 if (fi.isDir()) err = FileErr_Directory;
1945 else if (!fi.isReadable()) err = FileErr_Unreadable;
1948 else if (file.isEmpty())
1949 err = FileErr_Blank; // blank file name
1950 else
1952 // It's a local file - convert to absolute path & check validity
1953 QFileInfo info(file);
1954 QDir::setCurrent(QDir::homePath());
1955 filename = info.absoluteFilePath();
1956 url.setPath(filename);
1957 if (info.isDir()) err = FileErr_Directory;
1958 else if (!info.exists()) err = FileErr_Nonexistent;
1959 else if (!info.isReadable()) err = FileErr_Unreadable;
1961 return err;
1964 /******************************************************************************
1965 * Display an error message appropriate to 'err'.
1966 * Display a Continue/Cancel error message if 'errmsgParent' non-null.
1967 * Reply = true to continue, false to cancel.
1969 bool showFileErrMessage(const QString& filename, FileErr err, FileErr blankError, QWidget* errmsgParent)
1971 if (err != FileErr_None)
1973 // If file is a local file, remove "file://" from name
1974 QString file = filename;
1975 QRegExp f("^file:/+");
1976 if (f.indexIn(file) >= 0)
1977 file = file.mid(f.matchedLength() - 1);
1979 QString errmsg;
1980 switch (err)
1982 case FileErr_Blank:
1983 if (blankError == FileErr_BlankDisplay)
1984 errmsg = i18nc("@info", "Please select a file to display");
1985 else if (blankError == FileErr_BlankPlay)
1986 errmsg = i18nc("@info", "Please select a file to play");
1987 else
1988 kFatal() << "Program error";
1989 KAMessageBox::sorry(errmsgParent, errmsg);
1990 return false;
1991 case FileErr_Directory:
1992 KAMessageBox::sorry(errmsgParent, i18nc("@info", "<filename>%1</filename> is a folder", file));
1993 return false;
1994 case FileErr_Nonexistent: errmsg = i18nc("@info", "<filename>%1</filename> not found", file); break;
1995 case FileErr_Unreadable: errmsg = i18nc("@info", "<filename>%1</filename> is not readable", file); break;
1996 case FileErr_NotTextImage: errmsg = i18nc("@info", "<filename>%1</filename> appears not to be a text or image file", file); break;
1997 case FileErr_None:
1998 default:
1999 break;
2001 if (KAMessageBox::warningContinueCancel(errmsgParent, errmsg)
2002 == KMessageBox::Cancel)
2003 return false;
2005 return true;
2008 /******************************************************************************
2009 * If a url string is a local file, strip off the 'file:/' prefix.
2011 QString pathOrUrl(const QString& url)
2013 static const QRegExp localfile("^file:/+");
2014 return (localfile.indexIn(url) >= 0) ? url.mid(localfile.matchedLength() - 1) : url;
2017 /******************************************************************************
2018 * Display a modal dialog to choose an existing file, initially highlighting
2019 * any specified file.
2020 * @param initialFile The file to initially highlight - must be a full path name or URL.
2021 * @param defaultDir The directory to start in if @p initialFile is empty. If empty,
2022 * the user's home directory will be used. Updated to the
2023 * directory containing the selected file, if a file is chosen.
2024 * @param mode OR of KFile::Mode values, e.g. ExistingOnly, LocalOnly.
2025 * Reply = URL selected.
2026 * = empty, non-null string if no file was selected.
2027 * = null string if dialogue was deleted while visible (indicating that
2028 * the parent widget was probably also deleted).
2030 QString browseFile(const QString& caption, QString& defaultDir, const QString& initialFile,
2031 const QString& filter, KFile::Modes mode, QWidget* parent)
2033 QString initialDir = !initialFile.isEmpty() ? QString(initialFile).remove(QRegExp("/[^/]*$"))
2034 : !defaultDir.isEmpty() ? defaultDir
2035 : QDir::homePath();
2036 // Use AutoQPointer to guard against crash on application exit while
2037 // the dialogue is still open. It prevents double deletion (both on
2038 // deletion of parent, and on return from this function).
2039 AutoQPointer<KFileDialog> fileDlg = new KFileDialog(initialDir, filter, parent);
2040 fileDlg->setOperationMode(mode & KFile::ExistingOnly ? KFileDialog::Opening : KFileDialog::Saving);
2041 fileDlg->setMode(KFile::File | mode);
2042 fileDlg->setCaption(caption);
2043 if (!initialFile.isEmpty())
2044 fileDlg->setSelection(initialFile);
2045 if (fileDlg->exec() != QDialog::Accepted)
2046 return fileDlg ? QString("") : QString();
2047 KUrl url = fileDlg->selectedUrl();
2048 if (url.isEmpty())
2049 return QString("");
2050 defaultDir = url.isLocalFile() ? url.toLocalFile() : url.path();
2051 return (mode & KFile::LocalOnly) ? url.pathOrUrl() : url.prettyUrl();
2054 /******************************************************************************
2055 * Convert a date/time specification string into a local date/time or date value.
2056 * Parameters:
2057 * timeString = in the form [[[yyyy-]mm-]dd-]hh:mm [TZ] or yyyy-mm-dd [TZ].
2058 * dateTime = receives converted date/time value.
2059 * defaultDt = default date/time used for missing parts of timeString, or null
2060 * to use current date/time.
2061 * allowTZ = whether to allow a time zone specifier in timeString.
2062 * Reply = true if successful.
2064 bool convertTimeString(const QByteArray& timeString, KDateTime& dateTime, const KDateTime& defaultDt, bool allowTZ)
2066 #define MAX_DT_LEN 19
2067 int i = timeString.indexOf(' ');
2068 if (i > MAX_DT_LEN || (i >= 0 && !allowTZ))
2069 return false;
2070 QString zone = (i >= 0) ? QString::fromLatin1(timeString.mid(i)) : QString();
2071 char timeStr[MAX_DT_LEN+1];
2072 strcpy(timeStr, timeString.left(i >= 0 ? i : MAX_DT_LEN));
2073 int dt[5] = { -1, -1, -1, -1, -1 };
2074 char* s;
2075 char* end;
2076 bool noTime;
2077 // Get the minute value
2078 if ((s = strchr(timeStr, ':')) == 0)
2079 noTime = true;
2080 else
2082 noTime = false;
2083 *s++ = 0;
2084 dt[4] = strtoul(s, &end, 10);
2085 if (end == s || *end || dt[4] >= 60)
2086 return false;
2087 // Get the hour value
2088 if ((s = strrchr(timeStr, '-')) == 0)
2089 s = timeStr;
2090 else
2091 *s++ = 0;
2092 dt[3] = strtoul(s, &end, 10);
2093 if (end == s || *end || dt[3] >= 24)
2094 return false;
2096 bool noDate = true;
2097 if (s != timeStr)
2099 noDate = false;
2100 // Get the day value
2101 if ((s = strrchr(timeStr, '-')) == 0)
2102 s = timeStr;
2103 else
2104 *s++ = 0;
2105 dt[2] = strtoul(s, &end, 10);
2106 if (end == s || *end || dt[2] == 0 || dt[2] > 31)
2107 return false;
2108 if (s != timeStr)
2110 // Get the month value
2111 if ((s = strrchr(timeStr, '-')) == 0)
2112 s = timeStr;
2113 else
2114 *s++ = 0;
2115 dt[1] = strtoul(s, &end, 10);
2116 if (end == s || *end || dt[1] == 0 || dt[1] > 12)
2117 return false;
2118 if (s != timeStr)
2120 // Get the year value
2121 dt[0] = strtoul(timeStr, &end, 10);
2122 if (end == timeStr || *end)
2123 return false;
2128 QDate date;
2129 if (dt[0] >= 0)
2130 date = QDate(dt[0], dt[1], dt[2]);
2131 QTime time(0, 0, 0);
2132 if (noTime)
2134 // No time was specified, so the full date must have been specified
2135 if (dt[0] < 0 || !date.isValid())
2136 return false;
2137 dateTime = KAlarm::applyTimeZone(zone, date, time, false, defaultDt);
2139 else
2141 // Compile the values into a date/time structure
2142 time.setHMS(dt[3], dt[4], 0);
2143 if (dt[0] < 0)
2145 // Some or all of the date was omitted.
2146 // Use the default date/time if provided.
2147 if (defaultDt.isValid())
2149 dt[0] = defaultDt.date().year();
2150 date.setYMD(dt[0],
2151 (dt[1] < 0 ? defaultDt.date().month() : dt[1]),
2152 (dt[2] < 0 ? defaultDt.date().day() : dt[2]));
2154 else
2155 date.setYMD(2000, 1, 1); // temporary substitute for date
2157 dateTime = KAlarm::applyTimeZone(zone, date, time, true, defaultDt);
2158 if (!dateTime.isValid())
2159 return false;
2160 if (dt[0] < 0)
2162 // Some or all of the date was omitted.
2163 // Use the current date in the specified time zone as default.
2164 KDateTime now = KDateTime::currentDateTime(dateTime.timeSpec());
2165 date = dateTime.date();
2166 date.setYMD(now.date().year(),
2167 (dt[1] < 0 ? now.date().month() : dt[1]),
2168 (dt[2] < 0 ? now.date().day() : dt[2]));
2169 if (!date.isValid())
2170 return false;
2171 if (noDate && time < now.time())
2172 date = date.addDays(1);
2173 dateTime.setDate(date);
2176 return dateTime.isValid();
2179 /******************************************************************************
2180 * Convert a time zone specifier string and apply it to a given date and/or time.
2181 * The time zone specifier is a system time zone name, e.g. "Europe/London",
2182 * "UTC" or "Clock". If no time zone is specified, it defaults to the local time
2183 * zone.
2184 * If 'defaultDt' is valid, it supplies the time spec and default date.
2186 KDateTime applyTimeZone(const QString& tzstring, const QDate& date, const QTime& time,
2187 bool haveTime, const KDateTime& defaultDt)
2189 bool error = false;
2190 KDateTime::Spec spec = KDateTime::LocalZone;
2191 QString zone = tzstring.trimmed();
2192 if (!zone.isEmpty())
2194 if (zone == QLatin1String("Clock"))
2195 spec = KDateTime::ClockTime;
2196 else if (zone == QLatin1String("UTC"))
2197 spec = KDateTime::UTC;
2198 else
2200 KTimeZone tz = KSystemTimeZones::zone(zone);
2201 error = !tz.isValid();
2202 if (!error)
2203 spec = tz;
2206 else if (defaultDt.isValid())
2207 spec = defaultDt.timeSpec();
2209 KDateTime result;
2210 if (!error)
2212 if (!date.isValid())
2214 // It's a time without a date
2215 if (defaultDt.isValid())
2216 result = KDateTime(defaultDt.date(), time, spec);
2217 else if (spec == KDateTime::LocalZone || spec == KDateTime::ClockTime)
2218 result = KDateTime(KDateTime::currentLocalDate(), time, spec);
2220 else if (haveTime)
2222 // It's a date and time
2223 result = KDateTime(date, time, spec);
2225 else
2227 // It's a date without a time
2228 result = KDateTime(date, spec);
2231 return result;
2234 #ifndef NDEBUG
2235 /******************************************************************************
2236 * Set up KAlarm test conditions based on environment variables.
2237 * KALARM_TIME: specifies current system time (format [[[yyyy-]mm-]dd-]hh:mm [TZ]).
2239 void setTestModeConditions()
2241 const QByteArray newTime = qgetenv("KALARM_TIME");
2242 if (!newTime.isEmpty())
2244 KDateTime dt;
2245 if (convertTimeString(newTime, dt, KDateTime::realCurrentLocalDateTime(), true))
2246 setSimulatedSystemTime(dt);
2250 /******************************************************************************
2251 * Set the simulated system time.
2253 void setSimulatedSystemTime(const KDateTime& dt)
2255 KDateTime::setSimulatedSystemTime(dt);
2256 kDebug() << "New time =" << qPrintable(KDateTime::currentLocalDateTime().toString("%Y-%m-%d %H:%M %:Z"));
2258 #endif
2260 } // namespace KAlarm
2261 namespace
2264 /******************************************************************************
2265 * Tell KOrganizer to put an alarm in its calendar.
2266 * It will be held by KOrganizer as a simple event, without alarms - KAlarm
2267 * is still responsible for alarming.
2269 KAlarm::UpdateStatus sendToKOrganizer(const KAEvent* event)
2271 #ifdef USE_AKONADI
2272 Event::Ptr kcalEvent(new KCalCore::Event);
2273 event->updateKCalEvent(kcalEvent, KAEvent::UID_IGNORE);
2274 #else
2275 Event* kcalEvent = AlarmCalendar::resources()->createKCalEvent(event);
2276 #endif
2277 // Change the event ID to avoid duplicating the same unique ID as the original event
2278 QString uid = uidKOrganizer(event->id());
2279 kcalEvent->setUid(uid);
2280 kcalEvent->clearAlarms();
2281 QString userEmail;
2282 switch (event->actionTypes())
2284 case KAEvent::ACT_DISPLAY:
2285 case KAEvent::ACT_COMMAND:
2286 case KAEvent::ACT_DISPLAY_COMMAND:
2287 kcalEvent->setSummary(event->cleanText());
2288 userEmail = Preferences::emailAddress();
2289 break;
2290 case KAEvent::ACT_EMAIL:
2292 QString from = event->emailFromId()
2293 ? Identities::identityManager()->identityForUoid(event->emailFromId()).fullEmailAddr()
2294 : Preferences::emailAddress();
2295 AlarmText atext;
2296 atext.setEmail(event->emailAddresses(", "), from, QString(), QString(), event->emailSubject(), QString());
2297 kcalEvent->setSummary(atext.displayText());
2298 userEmail = from;
2299 break;
2301 case KAEvent::ACT_AUDIO:
2302 kcalEvent->setSummary(event->audioFile());
2303 break;
2304 default:
2305 break;
2307 #ifdef USE_AKONADI
2308 Person::Ptr person(new Person(QString(), userEmail));
2309 kcalEvent->setOrganizer(person);
2310 #else
2311 kcalEvent->setOrganizer(KCal::Person(QString(), userEmail));
2312 #endif
2313 kcalEvent->setDuration(Duration(Preferences::kOrgEventDuration() * 60, Duration::Seconds));
2315 // Translate the event into string format
2316 ICalFormat format;
2317 format.setTimeSpec(Preferences::timeZone(true));
2318 QString iCal = format.toICalString(kcalEvent);
2319 #ifndef USE_AKONADI
2320 delete kcalEvent;
2321 #endif
2323 // Send the event to KOrganizer
2324 KAlarm::UpdateStatus st = runKOrganizer(); // start KOrganizer if it isn't already running, and create its D-Bus interface
2325 if (st != KAlarm::UPDATE_OK)
2326 return st;
2327 QList<QVariant> args;
2328 args << iCal;
2329 QDBusReply<bool> reply = korgInterface->callWithArgumentList(QDBus::Block, QLatin1String("addIncidence"), args);
2330 if (!reply.isValid())
2332 if (reply.error().type() == QDBusError::UnknownObject)
2334 kError()<<"addIncidence() D-Bus error: still starting";
2335 return KAlarm::UPDATE_KORG_ERRSTART;
2337 kError() << "addIncidence(" << uid << ") D-Bus call failed:" << reply.error().message();
2338 return KAlarm::UPDATE_KORG_ERR;
2340 if (!reply.value())
2342 kDebug() << "addIncidence(" << uid << ") D-Bus call returned false";
2343 return KAlarm::UPDATE_KORG_FUNCERR;
2345 kDebug() << uid << ": success";
2346 return KAlarm::UPDATE_OK;
2349 /******************************************************************************
2350 * Tell KOrganizer to delete an event from its calendar.
2352 KAlarm::UpdateStatus deleteFromKOrganizer(const QString& eventID)
2354 KAlarm::UpdateStatus st = runKOrganizer(); // start KOrganizer if it isn't already running, and create its D-Bus interface
2355 if (st != KAlarm::UPDATE_OK)
2356 return st;
2357 QString newID = uidKOrganizer(eventID);
2358 QList<QVariant> args;
2359 args << newID << true;
2360 QDBusReply<bool> reply = korgInterface->callWithArgumentList(QDBus::Block, QLatin1String("deleteIncidence"), args);
2361 if (!reply.isValid())
2363 if (reply.error().type() == QDBusError::UnknownObject)
2365 kError()<<"deleteIncidence() D-Bus error: still starting";
2366 return KAlarm::UPDATE_KORG_ERRSTART;
2368 kError() << "deleteIncidence(" << newID << ") D-Bus call failed:" << reply.error().message();
2369 return KAlarm::UPDATE_KORG_ERR;
2371 if (!reply.value())
2373 kDebug() << "deleteIncidence(" << newID << ") D-Bus call returned false";
2374 return KAlarm::UPDATE_KORG_FUNCERR;
2376 kDebug() << newID << ": success";
2377 return KAlarm::UPDATE_OK;
2380 /******************************************************************************
2381 * Start KOrganizer if not already running, and create its D-Bus interface.
2383 KAlarm::UpdateStatus runKOrganizer()
2385 QString error, dbusService;
2386 int result = KDBusServiceStarter::self()->findServiceFor("DBUS/Organizer", QString(), &error, &dbusService);
2387 if (result)
2389 kWarning() << "Unable to start DBUS/Organizer:" << dbusService << error;
2390 return KAlarm::UPDATE_KORG_ERR;
2392 // If Kontact is running, there is be a load() method which needs to be called
2393 // to load KOrganizer into Kontact. But if KOrganizer is running independently,
2394 // the load() method doesn't exist.
2395 QDBusInterface iface(KORG_DBUS_SERVICE, KORG_DBUS_LOAD_PATH, "org.kde.KUniqueApplication");
2396 if (!iface.isValid())
2398 kWarning() << "Unable to access "KORG_DBUS_LOAD_PATH" D-Bus interface:" << iface.lastError().message();
2399 return KAlarm::UPDATE_KORG_ERR;
2401 QDBusReply<bool> reply = iface.call("load");
2402 if ((!reply.isValid() || !reply.value())
2403 && iface.lastError().type() != QDBusError::UnknownMethod)
2405 kWarning() << "Loading KOrganizer failed:" << iface.lastError().message();
2406 return KAlarm::UPDATE_KORG_ERR;
2409 // KOrganizer has been started, but it may not have the necessary
2410 // D-Bus interface available yet.
2411 if (!korgInterface || !korgInterface->isValid())
2413 delete korgInterface;
2414 korgInterface = new QDBusInterface(KORG_DBUS_SERVICE, KORG_DBUS_PATH, KORG_DBUS_IFACE);
2415 if (!korgInterface->isValid())
2417 kWarning() << "Unable to access "KORG_DBUS_PATH" D-Bus interface:" << korgInterface->lastError().message();
2418 delete korgInterface;
2419 korgInterface = 0;
2420 return KAlarm::UPDATE_KORG_ERRSTART;
2423 return KAlarm::UPDATE_OK;
2426 /******************************************************************************
2427 * Insert a KOrganizer string after the hyphen in the supplied event ID.
2429 QString uidKOrganizer(const QString& id)
2431 QString result = id;
2432 int i = result.lastIndexOf('-');
2433 if (i < 0)
2434 i = result.length();
2435 return result.insert(i, KORGANIZER_UID);
2438 } // namespace
2440 /******************************************************************************
2441 * Case insensitive comparison for use by qSort().
2443 bool caseInsensitiveLessThan(const QString& s1, const QString& s2)
2445 return s1.toLower() < s2.toLower();
2448 // vim: et sw=4: