SVN_SILENT made messages (after extraction)
[kdepim.git] / kalarm / functions.cpp
blob72ae3aebc992e59be9abd03f02f457f7dde28de7
1 /*
2 * functions.cpp - miscellaneous functions
3 * Program: kalarm
4 * Copyright © 2001-2014 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 #include "collectionmodel.h"
26 #include "collectionsearch.h"
27 #include "alarmcalendar.h"
28 #include "alarmtime.h"
29 #include "autoqpointer.h"
30 #include "alarmlistview.h"
31 #include "editdlg.h"
32 #include "kalarmapp.h"
33 #include "kamail.h"
34 #include "mainwindow.h"
35 #include "messagebox.h"
36 #include "messagewin.h"
37 #include "preferences.h"
38 #include "shellprocess.h"
39 #include "templatelistview.h"
40 #include "templatemenuaction.h"
42 #include "config-kdepim.h"
44 #include <kalarmcal/identities.h>
45 #include <kalarmcal/kaevent.h>
47 #include <KCalCore/Event>
48 #include <KCalCore/ICalFormat>
49 #include <KCalCore/Person>
50 #include <KCalCore/Duration>
51 using namespace KCalCore;
52 #include <KIdentityManagement/kidentitymanagement/identitymanager.h>
53 #include <KIdentityManagement/kidentitymanagement/identity.h>
54 #include <KHolidays/HolidayRegion>
56 #include <kconfiggroup.h>
57 #include <KSharedConfig>
58 #include <ktoggleaction.h>
59 #include <kactioncollection.h>
60 #include <kdbusservicestarter.h>
61 #include <KLocalizedString>
62 #include <kauth.h>
63 #include <ksystemtimezone.h>
64 #include <kstandardguiitem.h>
65 #include <kstandardshortcut.h>
66 #include <kfiledialog.h>
67 #include <KIO/StatJob>
68 #include <KJobWidgets>
69 #include <kfileitem.h>
70 #include <ktoolinvocation.h>
72 #if KDEPIM_HAVE_X11
73 #include <kwindowsystem.h>
74 #include <kxmessages.h>
75 #include <kstartupinfo.h>
76 #include <netwm.h>
77 #include <QX11Info>
78 #endif
80 #include <QAction>
81 #include <QDir>
82 #include <QRegExp>
83 #include <QDesktopWidget>
84 #include <QtDBus/QtDBus>
85 #include <QTimer>
86 #include <qglobal.h>
87 #include <QStandardPaths>
88 #include "kalarm_debug.h"
90 using namespace Akonadi;
93 namespace
95 bool refreshAlarmsQueued = false;
96 QDBusInterface* korgInterface = Q_NULLPTR;
98 struct UpdateStatusData
100 KAlarm::UpdateResult status; // status code and KOrganizer error message if any
101 int warnErr;
102 int warnKOrg;
104 explicit UpdateStatusData(KAlarm::UpdateStatus s = KAlarm::UPDATE_OK) : status(s), warnErr(0), warnKOrg(0) {}
105 // Set an error status and increment to number of errors to warn about
106 void setError(KAlarm::UpdateStatus st, int errorCount = -1)
108 status.set(st);
109 if (errorCount < 0)
110 ++warnErr;
111 else
112 warnErr = errorCount;
114 // Update the error status with a KOrganizer related status
115 void korgUpdate(KAlarm::UpdateResult result)
117 if (result.status != KAlarm::UPDATE_OK)
119 ++warnKOrg;
120 if (result.status > status.status)
121 status = result;
126 const QLatin1String KMAIL_DBUS_SERVICE("org.kde.kmail");
127 //const QLatin1String KMAIL_DBUS_IFACE("org.kde.kmail.kmail");
128 //const QLatin1String KMAIL_DBUS_WINDOW_PATH("/kmail/kmail_mainwindow_1");
129 const QLatin1String KORG_DBUS_SERVICE("org.kde.korganizer");
130 const QLatin1String KORG_DBUS_IFACE("org.kde.korganizer.Korganizer");
131 // D-Bus object path of KOrganizer's notification interface
132 #define KORG_DBUS_PATH "/Korganizer"
133 #define KORG_DBUS_LOAD_PATH "/korganizer_PimApplication"
134 //const QLatin1String KORG_DBUS_WINDOW_PATH("/korganizer/MainWindow_1");
135 const QLatin1String KORG_MIME_TYPE("application/x-vnd.akonadi.calendar.event");
136 const QLatin1String KORGANIZER_UID("-korg");
138 const QLatin1String ALARM_OPTS_FILE("alarmopts");
139 const char* DONT_SHOW_ERRORS_GROUP = "DontShowErrors";
141 void editNewTemplate(EditAlarmDlg::Type, const KAEvent* preset, QWidget* parent);
142 void displayUpdateError(QWidget* parent, KAlarm::UpdateError, const UpdateStatusData&, bool showKOrgError = true);
143 KAlarm::UpdateResult sendToKOrganizer(const KAEvent&);
144 KAlarm::UpdateResult deleteFromKOrganizer(const QString& eventID);
145 KAlarm::UpdateResult runKOrganizer();
146 QString uidKOrganizer(const QString& eventID);
150 namespace KAlarm
153 Private* Private::mInstance = Q_NULLPTR;
155 /******************************************************************************
156 * Display a main window with the specified event selected.
158 MainWindow* displayMainWindowSelected(Akonadi::Item::Id eventId)
160 MainWindow* win = MainWindow::firstWindow();
161 if (!win)
163 if (theApp()->checkCalendar()) // ensure calendar is open
165 win = MainWindow::create();
166 win->show();
169 else
171 // There is already a main window, so make it the active window
172 win->hide(); // in case it's on a different desktop
173 win->setWindowState(win->windowState() & ~Qt::WindowMinimized);
174 win->show();
175 win->raise();
176 win->activateWindow();
178 if (win && eventId >= 0)
179 win->selectEvent(eventId);
180 return win;
183 /******************************************************************************
184 * Create an "Alarms Enabled/Enable Alarms" action.
186 KToggleAction* createAlarmEnableAction(QObject* parent)
188 KToggleAction* action = new KToggleAction(i18nc("@action", "Enable &Alarms"), parent);
189 action->setChecked(theApp()->alarmsEnabled());
190 QObject::connect(action, &QAction::toggled, theApp(), &KAlarmApp::setAlarmsEnabled);
191 // The following line ensures that all instances are kept in the same state
192 QObject::connect(theApp(), &KAlarmApp::alarmEnabledToggled, action, &QAction::setChecked);
193 return action;
196 /******************************************************************************
197 * Create a "Stop Play" action.
199 QAction* createStopPlayAction(QObject* parent)
201 QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-stop")), i18nc("@action", "Stop Play"), parent);
202 action->setEnabled(MessageWin::isAudioPlaying());
203 QObject::connect(action, &QAction::triggered, theApp(), &KAlarmApp::stopAudio);
204 // The following line ensures that all instances are kept in the same state
205 QObject::connect(theApp(), &KAlarmApp::audioPlaying, action, &QAction::setEnabled);
206 return action;
209 /******************************************************************************
210 * Create a "Spread Windows" action.
212 KToggleAction* createSpreadWindowsAction(QObject* parent)
214 KToggleAction* action = new KToggleAction(i18nc("@action", "Spread Windows"), parent);
215 QObject::connect(action, &QAction::triggered, theApp(), &KAlarmApp::spreadWindows);
216 // The following line ensures that all instances are kept in the same state
217 QObject::connect(theApp(), &KAlarmApp::spreadWindowsToggled, action, &QAction::setChecked);
218 return action;
221 /******************************************************************************
222 * Add a new active (non-archived) alarm.
223 * Save it in the calendar file and add it to every main window instance.
224 * Parameters: msgParent = parent widget for any calendar selection prompt or
225 * error message.
226 * event - is updated with the actual event ID.
228 UpdateResult addEvent(KAEvent& event, Collection* calendar, QWidget* msgParent, int options, bool showKOrgErr)
230 qCDebug(KALARM_LOG) << event.id();
231 bool cancelled = false;
232 UpdateStatusData status;
233 if (!theApp()->checkCalendar()) // ensure calendar is open
234 status.status = UPDATE_FAILED;
235 else
237 // Save the event details in the calendar file, and get the new event ID
238 AlarmCalendar* cal = AlarmCalendar::resources();
239 // Note that AlarmCalendar::addEvent() updates 'event'.
240 if (!cal->addEvent(event, msgParent, (options & USE_EVENT_ID), calendar, (options & NO_RESOURCE_PROMPT), &cancelled))
242 status.status = UPDATE_FAILED;
244 else
246 if (!cal->save())
247 status.status = SAVE_FAILED;
249 if (status.status == UPDATE_OK)
251 if ((options & ALLOW_KORG_UPDATE) && event.copyToKOrganizer())
253 UpdateResult st = sendToKOrganizer(event); // tell KOrganizer to show the event
254 status.korgUpdate(st);
260 if (status.status != UPDATE_OK && !cancelled && msgParent)
261 displayUpdateError(msgParent, ERR_ADD, status, showKOrgErr);
262 return status.status;
265 /******************************************************************************
266 * Add a list of new active (non-archived) alarms.
267 * Save them in the calendar file and add them to every main window instance.
268 * The events are updated with their actual event IDs.
270 UpdateResult addEvents(QVector<KAEvent>& events, QWidget* msgParent, bool allowKOrgUpdate, bool showKOrgErr)
272 qCDebug(KALARM_LOG) << events.count();
273 if (events.isEmpty())
274 return UpdateResult(UPDATE_OK);
275 UpdateStatusData status;
276 Collection collection;
277 if (!theApp()->checkCalendar()) // ensure calendar is open
278 status.status = UPDATE_FAILED;
279 else
281 collection = CollectionControlModel::instance()->destination(CalEvent::ACTIVE, msgParent);
282 if (!collection.isValid())
284 qCDebug(KALARM_LOG) << "No calendar";
285 status.status = UPDATE_FAILED;
288 if (status.status == UPDATE_OK)
290 AlarmCalendar* cal = AlarmCalendar::resources();
291 for (int i = 0, end = events.count(); i < end; ++i)
293 // Save the event details in the calendar file, and get the new event ID
294 if (!cal->addEvent(events[i], msgParent, false, &collection))
296 status.setError(UPDATE_ERROR);
297 continue;
299 if (allowKOrgUpdate && events[i].copyToKOrganizer())
301 UpdateResult st = sendToKOrganizer(events[i]); // tell KOrganizer to show the event
302 status.korgUpdate(st);
306 if (status.warnErr == events.count())
307 status.status = UPDATE_FAILED;
308 else if (!cal->save())
309 status.setError(SAVE_FAILED, events.count()); // everything failed
312 if (status.status != UPDATE_OK && msgParent)
313 displayUpdateError(msgParent, ERR_ADD, status, showKOrgErr);
314 return status.status;
317 /******************************************************************************
318 * Save the event in the archived calendar and adjust every main window instance.
319 * The event's ID is changed to an archived ID if necessary.
321 bool addArchivedEvent(KAEvent& event, Collection* collection)
323 qCDebug(KALARM_LOG) << event.id();
324 bool archiving = (event.category() == CalEvent::ACTIVE);
325 if (archiving && !Preferences::archivedKeepDays())
326 return false; // expired alarms aren't being kept
327 AlarmCalendar* cal = AlarmCalendar::resources();
328 KAEvent newevent(event);
329 newevent.setItemId(-1); // invalidate the Akonadi item ID since it's a new item
330 KAEvent* const newev = &newevent;
331 if (archiving)
333 newev->setCategory(CalEvent::ARCHIVED); // this changes the event ID
334 newev->setCreatedDateTime(KDateTime::currentUtcDateTime()); // time stamp to control purging
336 // Note that archived resources are automatically saved after changes are made
337 if (!cal->addEvent(newevent, Q_NULLPTR, false, collection))
338 return false;
339 event = *newev; // update event ID etc.
341 return true;
344 /******************************************************************************
345 * Add a new template.
346 * Save it in the calendar file and add it to every template list view.
347 * 'event' is updated with the actual event ID.
348 * Parameters: promptParent = parent widget for any calendar selection prompt.
350 UpdateResult addTemplate(KAEvent& event, Collection* collection, QWidget* msgParent)
352 qCDebug(KALARM_LOG) << event.id();
353 UpdateStatusData status;
355 // Add the template to the calendar file
356 AlarmCalendar* cal = AlarmCalendar::resources();
357 KAEvent newev(event);
358 if (!cal->addEvent(newev, msgParent, false, collection))
359 status.status = UPDATE_FAILED;
360 else
362 event = newev; // update event ID etc.
363 if (!cal->save())
364 status.status = SAVE_FAILED;
365 else
367 return UpdateResult(UPDATE_OK);
371 if (msgParent)
372 displayUpdateError(msgParent, ERR_TEMPLATE, status);
373 return status.status;
376 /******************************************************************************
377 * Modify an active (non-archived) alarm in the calendar file and in every main
378 * window instance.
379 * The new event must have a different event ID from the old one.
381 UpdateResult modifyEvent(KAEvent& oldEvent, KAEvent& newEvent, QWidget* msgParent, bool showKOrgErr)
383 qCDebug(KALARM_LOG) << oldEvent.id();
385 UpdateStatusData status;
386 if (!newEvent.isValid())
388 deleteEvent(oldEvent, true);
389 status.status = UPDATE_FAILED;
391 else
393 EventId oldId(oldEvent);
394 if (oldEvent.copyToKOrganizer())
396 // Tell KOrganizer to delete its old event.
397 // But ignore errors, because the user could have manually
398 // deleted it since KAlarm asked KOrganizer to set it up.
399 deleteFromKOrganizer(oldId.eventId());
401 // Update the event in the calendar file, and get the new event ID
402 AlarmCalendar* cal = AlarmCalendar::resources();
403 if (!cal->modifyEvent(oldId, newEvent))
404 status.status = UPDATE_FAILED;
405 else
407 if (!cal->save())
408 status.status = SAVE_FAILED;
409 if (status.status == UPDATE_OK)
411 if (newEvent.copyToKOrganizer())
413 UpdateResult st = sendToKOrganizer(newEvent); // tell KOrganizer to show the new event
414 status.korgUpdate(st);
417 // Remove "Don't show error messages again" for the old alarm
418 setDontShowErrors(oldId);
424 if (status.status != UPDATE_OK && msgParent)
425 displayUpdateError(msgParent, ERR_MODIFY, status, showKOrgErr);
426 return status.status;
429 /******************************************************************************
430 * Update an active (non-archived) alarm from the calendar file and from every
431 * main window instance.
432 * The new event will have the same event ID as the old one.
433 * The event is not updated in KOrganizer, since this function is called when an
434 * existing alarm is rescheduled (due to recurrence or deferral).
436 UpdateResult updateEvent(KAEvent& event, QWidget* msgParent, bool archiveOnDelete)
438 qCDebug(KALARM_LOG) << event.id();
440 if (!event.isValid())
441 deleteEvent(event, archiveOnDelete);
442 else
444 // Update the event in the calendar file.
445 AlarmCalendar* cal = AlarmCalendar::resources();
446 cal->updateEvent(event);
447 if (!cal->save())
449 if (msgParent)
450 displayUpdateError(msgParent, ERR_ADD, UpdateStatusData(SAVE_FAILED));
451 return UpdateResult(SAVE_FAILED);
455 return UpdateResult(UPDATE_OK);
458 /******************************************************************************
459 * Update a template in the calendar file and in every template list view.
460 * If 'selectionView' is non-null, the selection highlight is moved to the
461 * updated event in that listView instance.
463 UpdateResult updateTemplate(KAEvent& event, QWidget* msgParent)
465 AlarmCalendar* cal = AlarmCalendar::resources();
466 KAEvent* newEvent = cal->updateEvent(event);
467 UpdateStatus status = UPDATE_OK;
468 if (!newEvent)
469 status = UPDATE_FAILED;
470 else if (!cal->save())
471 status = SAVE_FAILED;
472 if (status != UPDATE_OK)
474 if (msgParent)
475 displayUpdateError(msgParent, ERR_TEMPLATE, UpdateStatusData(SAVE_FAILED));
476 return UpdateResult(status);
479 return UpdateResult(UPDATE_OK);
482 /******************************************************************************
483 * Delete alarms from the calendar file and from every main window instance.
484 * If the events are archived, the events' IDs are changed to archived IDs if necessary.
486 UpdateResult deleteEvent(KAEvent& event, bool archive, QWidget* msgParent, bool showKOrgErr)
488 QVector<KAEvent> events(1, event);
489 return deleteEvents(events, archive, msgParent, showKOrgErr);
492 UpdateResult deleteEvents(QVector<KAEvent>& events, bool archive, QWidget* msgParent, bool showKOrgErr)
494 qCDebug(KALARM_LOG) << events.count();
495 if (events.isEmpty())
496 return UpdateResult(UPDATE_OK);
497 UpdateStatusData status;
498 AlarmCalendar* cal = AlarmCalendar::resources();
499 bool deleteWakeFromSuspendAlarm = false;
500 QString wakeFromSuspendId = checkRtcWakeConfig().value(0);
501 for (int i = 0, end = events.count(); i < end; ++i)
503 // Save the event details in the calendar file, and get the new event ID
504 KAEvent* event = &events[i];
505 QString id = event->id();
508 // Delete the event from the calendar file
509 if (event->category() != CalEvent::ARCHIVED)
511 if (event->copyToKOrganizer())
513 // The event was shown in KOrganizer, so tell KOrganizer to
514 // delete it. But ignore errors, because the user could have
515 // manually deleted it from KOrganizer since it was set up.
516 UpdateResult st = deleteFromKOrganizer(id);
517 status.korgUpdate(st);
519 if (archive && event->toBeArchived())
521 KAEvent ev(*event);
522 addArchivedEvent(ev); // this changes the event ID to an archived ID
525 if (!cal->deleteEvent(*event, false)) // don't save calendar after deleting
526 status.setError(UPDATE_ERROR);
528 if (id == wakeFromSuspendId)
529 deleteWakeFromSuspendAlarm = true;
531 // Remove "Don't show error messages again" for this alarm
532 setDontShowErrors(EventId(*event));
535 if (status.warnErr == events.count())
536 status.status = UPDATE_FAILED;
537 else if (!cal->save()) // save the calendars now
538 status.setError(SAVE_FAILED, events.count());
539 if (status.status != UPDATE_OK && msgParent)
540 displayUpdateError(msgParent, ERR_DELETE, status, showKOrgErr);
542 // Remove any wake-from-suspend scheduled for a deleted alarm
543 if (deleteWakeFromSuspendAlarm && !wakeFromSuspendId.isEmpty())
544 cancelRtcWake(msgParent, wakeFromSuspendId);
546 return status.status;
549 /******************************************************************************
550 * Delete templates from the calendar file and from every template list view.
552 UpdateResult deleteTemplates(const KAEvent::List& events, QWidget* msgParent)
554 int count = events.count();
555 qCDebug(KALARM_LOG) << count;
556 if (!count)
557 return UpdateResult(UPDATE_OK);
558 UpdateStatusData status;
559 AlarmCalendar* cal = AlarmCalendar::resources();
560 for (int i = 0, end = count; i < end; ++i)
562 // Update the window lists
563 // Delete the template from the calendar file
564 AlarmCalendar* cal = AlarmCalendar::resources();
565 if (!cal->deleteEvent(*events[i], false)) // don't save calendar after deleting
566 status.setError(UPDATE_ERROR);
569 if (status.warnErr == count)
570 status.status = UPDATE_FAILED;
571 else if (!cal->save()) // save the calendars now
572 status.setError(SAVE_FAILED, count);
573 if (status.status != UPDATE_OK && msgParent)
574 displayUpdateError(msgParent, ERR_TEMPLATE, status);
575 return status.status;
578 /******************************************************************************
579 * Delete an alarm from the display calendar.
581 void deleteDisplayEvent(const QString& eventID)
583 qCDebug(KALARM_LOG) << eventID;
584 AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen();
585 if (cal)
586 cal->deleteDisplayEvent(eventID, true); // save calendar after deleting
589 /******************************************************************************
590 * Undelete archived alarms, and update every main window instance.
591 * The archive bit is set to ensure that they get re-archived if deleted again.
592 * 'ineligibleIDs' is filled in with the IDs of any ineligible events.
594 UpdateResult reactivateEvent(KAEvent& event, Collection* calendar, QWidget* msgParent, bool showKOrgErr)
596 QVector<EventId> ids;
597 QVector<KAEvent> events(1, event);
598 return reactivateEvents(events, ids, calendar, msgParent, showKOrgErr);
601 UpdateResult reactivateEvents(QVector<KAEvent>& events, QVector<EventId>& ineligibleIDs, Collection* col, QWidget* msgParent, bool showKOrgErr)
603 qCDebug(KALARM_LOG) << events.count();
604 ineligibleIDs.clear();
605 if (events.isEmpty())
606 return UpdateResult(UPDATE_OK);
607 UpdateStatusData status;
608 Collection collection;
609 if (col)
610 collection = *col;
611 if (!collection.isValid())
612 collection = CollectionControlModel::instance()->destination(CalEvent::ACTIVE, msgParent);
613 if (!collection.isValid())
615 qCDebug(KALARM_LOG) << "No calendar";
616 status.setError(UPDATE_FAILED, events.count());
618 else
620 int count = 0;
621 AlarmCalendar* cal = AlarmCalendar::resources();
622 KDateTime now = KDateTime::currentUtcDateTime();
623 for (int i = 0, end = events.count(); i < end; ++i)
625 // Delete the event from the archived resource
626 KAEvent* event = &events[i];
627 if (event->category() != CalEvent::ARCHIVED
628 || !event->occursAfter(now, true))
630 ineligibleIDs += EventId(*event);
631 continue;
633 ++count;
635 KAEvent newevent(*event);
636 KAEvent* const newev = &newevent;
637 newev->setCategory(CalEvent::ACTIVE); // this changes the event ID
638 if (newev->recurs() || newev->repetition())
639 newev->setNextOccurrence(now); // skip any recurrences in the past
640 newev->setArchive(); // ensure that it gets re-archived if it is deleted
642 // Save the event details in the calendar file.
643 // This converts the event ID.
644 if (!cal->addEvent(newevent, msgParent, true, &collection))
646 status.setError(UPDATE_ERROR);
647 continue;
649 if (newev->copyToKOrganizer())
651 UpdateResult st = sendToKOrganizer(*newev); // tell KOrganizer to show the event
652 status.korgUpdate(st);
656 if (cal->event(EventId(*event)) // no error if event doesn't exist in archived resource
657 && !cal->deleteEvent(*event, false)) // don't save calendar after deleting
658 status.setError(UPDATE_ERROR);
659 events[i] = newevent;
662 if (status.warnErr == count)
663 status.status = UPDATE_FAILED;
664 // Save the calendars, even if all events failed, since more than one calendar was updated
665 if (!cal->save() && status.status != UPDATE_FAILED)
666 status.setError(SAVE_FAILED, count);
668 if (status.status != UPDATE_OK && msgParent)
669 displayUpdateError(msgParent, ERR_REACTIVATE, status, showKOrgErr);
670 return status.status;
673 /******************************************************************************
674 * Enable or disable alarms in the calendar file and in every main window instance.
675 * The new events will have the same event IDs as the old ones.
677 UpdateResult enableEvents(QVector<KAEvent>& events, bool enable, QWidget* msgParent)
679 qCDebug(KALARM_LOG) << events.count();
680 if (events.isEmpty())
681 return UpdateResult(UPDATE_OK);
682 UpdateStatusData status;
683 AlarmCalendar* cal = AlarmCalendar::resources();
684 bool deleteWakeFromSuspendAlarm = false;
685 QString wakeFromSuspendId = checkRtcWakeConfig().value(0);
686 for (int i = 0, end = events.count(); i < end; ++i)
688 KAEvent* event = &events[i];
689 if (event->category() == CalEvent::ACTIVE
690 && enable != event->enabled())
692 event->setEnabled(enable);
694 if (!enable && event->id() == wakeFromSuspendId)
695 deleteWakeFromSuspendAlarm = true;
697 // Update the event in the calendar file
698 KAEvent* newev = cal->updateEvent(event);
699 if (!newev)
700 qCCritical(KALARM_LOG) << "Error updating event in calendar:" << event->id();
701 else
703 cal->disabledChanged(newev);
705 // If we're disabling a display alarm, close any message window
706 if (!enable && (event->actionTypes() & KAEvent::ACT_DISPLAY))
708 MessageWin* win = MessageWin::findEvent(EventId(*event));
709 delete win;
715 if (!cal->save())
716 status.setError(SAVE_FAILED, events.count());
717 if (status.status != UPDATE_OK && msgParent)
718 displayUpdateError(msgParent, ERR_ADD, status);
720 // Remove any wake-from-suspend scheduled for a disabled alarm
721 if (deleteWakeFromSuspendAlarm && !wakeFromSuspendId.isEmpty())
722 cancelRtcWake(msgParent, wakeFromSuspendId);
724 return status.status;
727 /******************************************************************************
728 * This method must only be called from the main KAlarm queue processing loop,
729 * to prevent asynchronous calendar operations interfering with one another.
731 * Purge all archived events from the default archived alarm resource whose end
732 * time is longer ago than 'purgeDays'. All events are deleted if 'purgeDays' is
733 * zero.
735 void purgeArchive(int purgeDays)
737 if (purgeDays < 0)
738 return;
739 qCDebug(KALARM_LOG) << purgeDays;
740 QDate cutoff = KDateTime::currentLocalDate().addDays(-purgeDays);
741 Collection collection = CollectionControlModel::getStandard(CalEvent::ARCHIVED);
742 if (!collection.isValid())
743 return;
744 KAEvent::List events = AlarmCalendar::resources()->events(collection);
745 for (int i = 0; i < events.count(); )
747 if (purgeDays && events[i]->createdDateTime().date() >= cutoff)
748 events.remove(i);
749 else
750 ++i;
752 if (!events.isEmpty())
753 AlarmCalendar::resources()->purgeEvents(events); // delete the events and save the calendar
756 /******************************************************************************
757 * Display an error message about an error when saving an event.
758 * If 'model' is non-null, the AlarmListModel* which it points to is used; if
759 * that is null, it is created.
761 QVector<KAEvent> getSortedActiveEvents(QObject* parent, AlarmListModel** model)
763 AlarmListModel* mdl = Q_NULLPTR;
764 if (!model)
765 model = &mdl;
766 if (!*model)
768 *model = new AlarmListModel(parent);
769 (*model)->setEventTypeFilter(CalEvent::ACTIVE);
770 (*model)->sort(AlarmListModel::TimeColumn);
772 QVector<KAEvent> result;
773 for (int i = 0, count = (*model)->rowCount(); i < count; ++i)
775 KAEvent event = (*model)->event(i);
776 if (event.enabled() && !event.expired())
777 result += event;
779 return result;
782 /******************************************************************************
783 * Display an error message corresponding to a specified alarm update error code.
785 void displayKOrgUpdateError(QWidget* parent, UpdateError code, UpdateResult korgError, int nAlarms)
787 QString errmsg;
788 switch (code)
790 case ERR_ADD:
791 case ERR_REACTIVATE:
792 errmsg = (nAlarms > 1) ? i18nc("@info", "Unable to show alarms in KOrganizer")
793 : i18nc("@info", "Unable to show alarm in KOrganizer");
794 break;
795 case ERR_MODIFY:
796 errmsg = i18nc("@info", "Unable to update alarm in KOrganizer");
797 break;
798 case ERR_DELETE:
799 errmsg = (nAlarms > 1) ? i18nc("@info", "Unable to delete alarms from KOrganizer")
800 : i18nc("@info", "Unable to delete alarm from KOrganizer");
801 break;
802 case ERR_TEMPLATE:
803 return;
805 bool showDetail = !korgError.message.isEmpty();
806 QString msg;
807 switch (korgError.status)
809 case UPDATE_KORG_ERRINIT:
810 msg = xi18nc("@info", "<para>%1</para><para>(Could not start KOrganizer)</para>", errmsg);
811 break;
812 case UPDATE_KORG_ERRSTART:
813 msg = xi18nc("@info", "<para>%1</para><para>(KOrganizer not fully started)</para>", errmsg);
814 break;
815 case UPDATE_KORG_ERR:
816 msg = xi18nc("@info", "<para>%1</para><para>(Error communicating with KOrganizer)</para>", errmsg);
817 break;
818 default:
819 msg = errmsg;
820 showDetail = false;
821 break;
823 if (showDetail)
824 KAMessageBox::detailedError(parent, msg, korgError.message);
825 else
826 KAMessageBox::error(parent, msg);
829 /******************************************************************************
830 * Execute a New Alarm dialog for the specified alarm type.
832 void editNewAlarm(EditAlarmDlg::Type type, QWidget* parent)
834 execNewAlarmDlg(EditAlarmDlg::create(false, type, parent));
837 /******************************************************************************
838 * Execute a New Alarm dialog for the specified alarm type.
840 void editNewAlarm(KAEvent::SubAction action, QWidget* parent, const AlarmText* text)
842 bool setAction = false;
843 EditAlarmDlg::Type type;
844 switch (action)
846 case KAEvent::MESSAGE:
847 case KAEvent::FILE:
848 type = EditAlarmDlg::DISPLAY;
849 setAction = true;
850 break;
851 case KAEvent::COMMAND:
852 type = EditAlarmDlg::COMMAND;
853 break;
854 case KAEvent::EMAIL:
855 type = EditAlarmDlg::EMAIL;
856 break;
857 case KAEvent::AUDIO:
858 type = EditAlarmDlg::AUDIO;
859 break;
860 default:
861 return;
863 EditAlarmDlg* editDlg = EditAlarmDlg::create(false, type, parent);
864 if (setAction || text)
865 editDlg->setAction(action, *text);
866 execNewAlarmDlg(editDlg);
869 /******************************************************************************
870 * Execute a New Alarm dialog, optionally either presetting it to the supplied
871 * event, or setting the action and text.
873 void editNewAlarm(const KAEvent* preset, QWidget* parent)
875 execNewAlarmDlg(EditAlarmDlg::create(false, preset, true, parent));
878 /******************************************************************************
879 * Common code for editNewAlarm() variants.
881 void execNewAlarmDlg(EditAlarmDlg* editDlg)
883 // Create a PrivateNewAlarmDlg parented by editDlg.
884 // It will be deleted when editDlg is closed.
885 new PrivateNewAlarmDlg(editDlg);
886 editDlg->show();
887 editDlg->raise();
888 editDlg->activateWindow();
891 PrivateNewAlarmDlg::PrivateNewAlarmDlg(EditAlarmDlg* dlg)
892 : QObject(dlg)
894 connect(dlg, &QDialog::accepted, this, &PrivateNewAlarmDlg::okClicked);
895 connect(dlg, &QDialog::rejected, this, &PrivateNewAlarmDlg::cancelClicked);
898 /******************************************************************************
899 * Called when the dialogue is accepted (e.g. by clicking the OK button).
900 * Creates the event specified in the instance's dialogue.
902 void PrivateNewAlarmDlg::okClicked()
904 accept(static_cast<EditAlarmDlg*>(parent()));
907 /******************************************************************************
908 * Creates the event specified in a given dialogue.
910 void PrivateNewAlarmDlg::accept(EditAlarmDlg* editDlg)
912 KAEvent event;
913 Collection calendar;
914 editDlg->getEvent(event, calendar);
916 // Add the alarm to the displayed lists and to the calendar file
917 UpdateResult status = addEvent(event, &calendar, editDlg);
918 switch (status.status)
920 case UPDATE_FAILED:
921 return;
922 case UPDATE_KORG_ERR:
923 case UPDATE_KORG_ERRINIT:
924 case UPDATE_KORG_ERRSTART:
925 case UPDATE_KORG_FUNCERR:
926 displayKOrgUpdateError(editDlg, ERR_ADD, status);
927 break;
928 default:
929 break;
931 Undo::saveAdd(event, calendar);
933 outputAlarmWarnings(editDlg, &event);
935 editDlg->deleteLater();
938 /******************************************************************************
939 * Called when the dialogue is rejected (e.g. by clicking the Cancel button).
941 void PrivateNewAlarmDlg::cancelClicked()
943 static_cast<EditAlarmDlg*>(parent())->deleteLater();
946 /******************************************************************************
947 * Display the alarm edit dialog to edit a new alarm, preset with a template.
949 bool editNewAlarm(const QString& templateName, QWidget* parent)
951 if (!templateName.isEmpty())
953 KAEvent* templateEvent = AlarmCalendar::resources()->templateEvent(templateName);
954 if (templateEvent->isValid())
956 editNewAlarm(templateEvent, parent);
957 return true;
959 qCWarning(KALARM_LOG) << templateName << ": template not found";
961 return false;
964 /******************************************************************************
965 * Create a new template.
967 void editNewTemplate(EditAlarmDlg::Type type, QWidget* parent)
969 ::editNewTemplate(type, Q_NULLPTR, parent);
972 /******************************************************************************
973 * Create a new template, based on an existing event or template.
975 void editNewTemplate(const KAEvent* preset, QWidget* parent)
977 ::editNewTemplate(EditAlarmDlg::Type(0), preset, parent);
980 /******************************************************************************
981 * Check the config as to whether there is a wake-on-suspend alarm pending, and
982 * if so, delete it from the config if it has expired.
983 * If 'checkExists' is true, the config entry will only be returned if the
984 * event exists.
985 * Reply = config entry: [0] = event's collection ID (Akonadi only),
986 * [1] = event ID,
987 * [2] = trigger time (time_t).
988 * = empty list if none or expired.
990 QStringList checkRtcWakeConfig(bool checkEventExists)
992 KConfigGroup config(KSharedConfig::openConfig(), "General");
993 QStringList params = config.readEntry("RtcWake", QStringList());
994 if (params.count() == 3 && params[2].toUInt() > KDateTime::currentUtcDateTime().toTime_t())
996 if (checkEventExists && !AlarmCalendar::getEvent(EventId(params[0].toLongLong(), params[1])))
997 return QStringList();
998 return params; // config entry is valid
1000 if (!params.isEmpty())
1002 config.deleteEntry("RtcWake"); // delete the expired config entry
1003 config.sync();
1005 return QStringList();
1008 /******************************************************************************
1009 * Delete any wake-on-suspend alarm from the config.
1011 void deleteRtcWakeConfig()
1013 KConfigGroup config(KSharedConfig::openConfig(), "General");
1014 config.deleteEntry("RtcWake");
1015 config.sync();
1018 /******************************************************************************
1019 * Delete any wake-on-suspend alarm, optionally only for a specified event.
1021 void cancelRtcWake(QWidget* msgParent, const QString& eventId)
1023 QStringList wakeup = checkRtcWakeConfig();
1024 if (!wakeup.isEmpty() && (eventId.isEmpty() || wakeup[0] == eventId))
1026 Private::instance()->mMsgParent = msgParent ? msgParent : MainWindow::mainMainWindow();
1027 QTimer::singleShot(0, Private::instance(), &Private::cancelRtcWake);
1031 /******************************************************************************
1032 * Delete any wake-on-suspend alarm.
1034 void Private::cancelRtcWake()
1036 // setRtcWakeTime will only work with a parent window specified
1037 setRtcWakeTime(0, mMsgParent);
1038 deleteRtcWakeConfig();
1039 KAMessageBox::information(mMsgParent, i18nc("info", "The scheduled Wake from Suspend has been cancelled."));
1042 /******************************************************************************
1043 * Set the wakeup time for the system.
1044 * Set 'triggerTime' to zero to cancel the wakeup.
1045 * Reply = true if successful.
1047 bool setRtcWakeTime(unsigned triggerTime, QWidget* parent)
1049 #if 0 //QT5
1050 QVariantMap args;
1051 args[QLatin1String("time")] = triggerTime;
1052 KAuth::Action action(QLatin1String("org.kde.kalarmrtcwake.settimer"));
1053 action.setHelperID(QLatin1String("org.kde.kalarmrtcwake"));
1054 action.setParentWidget(parent);
1055 action.setArguments(args);
1056 KAuth::ActionReply reply = action.execute();
1057 if (reply.failed())
1059 QString errmsg = reply.errorDescription();
1060 qCDebug(KALARM_LOG) << "Error code=" << reply.errorCode() << errmsg;
1061 if (errmsg.isEmpty())
1063 int errcode = reply.errorCode();
1064 switch (reply.type())
1066 case KAuth::ActionReply::KAuthErrorType:
1067 qCDebug(KALARM_LOG) << "Authorization error:" << errcode;
1068 switch (errcode)
1070 case KAuth::ActionReply::AuthorizationDeniedError:
1071 case KAuth::ActionReply::UserCancelledError:
1072 return false; // the user should already know about this
1073 default:
1074 break;
1076 break;
1077 case KAuth::ActionReply::HelperErrorType:
1078 qCDebug(KALARM_LOG) << "Helper error:" << errcode;
1079 errcode += 100; // make code distinguishable from KAuthError type
1080 break;
1081 default:
1082 break;
1084 errmsg = i18nc("@info", "Error obtaining authorization (%1)", errcode);
1086 KAMessageBox::information(parent, errmsg);
1087 return false;
1089 #endif
1090 return true;
1093 } // namespace KAlarm
1094 namespace
1097 /******************************************************************************
1098 * Create a new template.
1099 * 'preset' is non-null to base it on an existing event or template; otherwise,
1100 * the alarm type is set to 'type'.
1102 void editNewTemplate(EditAlarmDlg::Type type, const KAEvent* preset, QWidget* parent)
1104 if (CollectionControlModel::enabledCollections(CalEvent::TEMPLATE, true).isEmpty())
1106 KAMessageBox::sorry(parent, i18nc("@info", "You must enable a template calendar to save the template in"));
1107 return;
1109 // Use AutoQPointer to guard against crash on application exit while
1110 // the dialogue is still open. It prevents double deletion (both on
1111 // deletion of parent, and on return from this function).
1112 AutoQPointer<EditAlarmDlg> editDlg;
1113 if (preset)
1114 editDlg = EditAlarmDlg::create(true, preset, true, parent);
1115 else
1116 editDlg = EditAlarmDlg::create(true, type, parent);
1117 if (editDlg->exec() == QDialog::Accepted)
1119 KAEvent event;
1120 Akonadi::Collection calendar;
1121 editDlg->getEvent(event, calendar);
1123 // Add the template to the displayed lists and to the calendar file
1124 KAlarm::addTemplate(event, &calendar, editDlg);
1125 Undo::saveAdd(event, calendar);
1129 } // namespace
1130 namespace KAlarm
1133 /******************************************************************************
1134 * Open the Edit Alarm dialog to edit the specified alarm.
1135 * If the alarm is read-only or archived, the dialog is opened read-only.
1137 void editAlarm(KAEvent* event, QWidget* parent)
1139 if (event->expired() || AlarmCalendar::resources()->eventReadOnly(event->itemId()))
1141 viewAlarm(event, parent);
1142 return;
1144 EventId id(*event);
1145 // Use AutoQPointer to guard against crash on application exit while
1146 // the dialogue is still open. It prevents double deletion (both on
1147 // deletion of parent, and on return from this function).
1148 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(false, event, false, parent, EditAlarmDlg::RES_USE_EVENT_ID);
1149 if (editDlg->exec() == QDialog::Accepted)
1151 if (!AlarmCalendar::resources()->event(id))
1153 // Event has been deleted while the user was editing the alarm,
1154 // so treat it as a new alarm.
1155 PrivateNewAlarmDlg().accept(editDlg);
1156 return;
1158 KAEvent newEvent;
1159 Collection calendar;
1160 bool changeDeferral = !editDlg->getEvent(newEvent, calendar);
1162 // Update the event in the displays and in the calendar file
1163 Undo::Event undo(*event, calendar);
1164 if (changeDeferral)
1166 // The only change has been to an existing deferral
1167 if (updateEvent(newEvent, editDlg, true) != UPDATE_OK) // keep the same event ID
1168 return; // failed to save event
1170 else
1172 UpdateResult status = modifyEvent(*event, newEvent, editDlg);
1173 if (status.status != UPDATE_OK && status.status <= UPDATE_KORG_ERR)
1174 displayKOrgUpdateError(editDlg, ERR_MODIFY, status);
1176 Undo::saveEdit(undo, newEvent);
1178 outputAlarmWarnings(editDlg, &newEvent);
1182 /******************************************************************************
1183 * Display the alarm edit dialog to edit the alarm with the specified ID.
1184 * An error occurs if the alarm is not found, if there is more than one alarm
1185 * with the same ID, or if it is read-only or expired.
1187 bool editAlarmById(const EventId& id, QWidget* parent)
1189 const QString eventID(id.eventId());
1190 KAEvent* event = AlarmCalendar::resources()->event(id, true);
1191 if (!event)
1193 if (id.collectionId() != -1)
1194 qCWarning(KALARM_LOG) << "Event ID not found, or duplicated:" << eventID;
1195 else
1196 qCWarning(KALARM_LOG) << "Event ID not found:" << eventID;
1197 return false;
1199 if (AlarmCalendar::resources()->eventReadOnly(event->itemId()))
1201 qCCritical(KALARM_LOG) << eventID << ": read-only";
1202 return false;
1204 switch (event->category())
1206 case CalEvent::ACTIVE:
1207 case CalEvent::TEMPLATE:
1208 break;
1209 default:
1210 qCCritical(KALARM_LOG) << eventID << ": event not active or template";
1211 return false;
1213 editAlarm(event, parent);
1214 return true;
1217 /******************************************************************************
1218 * Open the Edit Alarm dialog to edit the specified template.
1219 * If the template is read-only, the dialog is opened read-only.
1221 void editTemplate(KAEvent* event, QWidget* parent)
1223 if (AlarmCalendar::resources()->eventReadOnly(event->itemId()))
1225 // The template is read-only, so make the dialogue read-only.
1226 // Use AutoQPointer to guard against crash on application exit while
1227 // the dialogue is still open. It prevents double deletion (both on
1228 // deletion of parent, and on return from this function).
1229 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(true, event, false, parent, EditAlarmDlg::RES_PROMPT, true);
1230 editDlg->exec();
1231 return;
1233 // Use AutoQPointer to guard against crash on application exit while
1234 // the dialogue is still open. It prevents double deletion (both on
1235 // deletion of parent, and on return from this function).
1236 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(true, event, false, parent, EditAlarmDlg::RES_USE_EVENT_ID);
1237 if (editDlg->exec() == QDialog::Accepted)
1239 KAEvent newEvent;
1240 Akonadi::Collection calendar;
1241 editDlg->getEvent(newEvent, calendar);
1242 QString id = event->id();
1243 newEvent.setEventId(id);
1244 newEvent.setCollectionId(event->collectionId());
1245 newEvent.setItemId(event->itemId());
1247 // Update the event in the displays and in the calendar file
1248 Undo::Event undo(*event, calendar);
1249 updateTemplate(newEvent, editDlg);
1250 Undo::saveEdit(undo, newEvent);
1254 /******************************************************************************
1255 * Open the Edit Alarm dialog to view the specified alarm (read-only).
1257 void viewAlarm(const KAEvent* event, QWidget* parent)
1259 // Use AutoQPointer to guard against crash on application exit while
1260 // the dialogue is still open. It prevents double deletion (both on
1261 // deletion of parent, and on return from this function).
1262 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(false, event, false, parent, EditAlarmDlg::RES_PROMPT, true);
1263 editDlg->exec();
1266 /******************************************************************************
1267 * Called when OK is clicked in the alarm edit dialog invoked by the Edit button
1268 * in an alarm message window.
1269 * Updates the alarm calendar and closes the dialog.
1271 void updateEditedAlarm(EditAlarmDlg* editDlg, KAEvent& event, Collection& calendar)
1273 qCDebug(KALARM_LOG);
1274 KAEvent newEvent;
1275 Akonadi::Collection cal;
1276 editDlg->getEvent(newEvent, cal);
1278 // Update the displayed lists and the calendar file
1279 UpdateResult status;
1280 if (AlarmCalendar::resources()->event(EventId(event)))
1282 // The old alarm hasn't expired yet, so replace it
1283 Undo::Event undo(event, calendar);
1284 status = modifyEvent(event, newEvent, editDlg);
1285 Undo::saveEdit(undo, newEvent);
1287 else
1289 // The old event has expired, so simply create a new one
1290 status = addEvent(newEvent, &calendar, editDlg);
1291 Undo::saveAdd(newEvent, calendar);
1294 if (status.status != UPDATE_OK && status.status <= UPDATE_KORG_ERR)
1295 displayKOrgUpdateError(editDlg, ERR_MODIFY, status);
1296 outputAlarmWarnings(editDlg, &newEvent);
1298 editDlg->close();
1301 /******************************************************************************
1302 * Returns a list of all alarm templates.
1303 * If shell commands are disabled, command alarm templates are omitted.
1305 KAEvent::List templateList()
1307 KAEvent::List templates;
1308 bool includeCmdAlarms = ShellProcess::authorised();
1309 KAEvent::List events = AlarmCalendar::resources()->events(CalEvent::TEMPLATE);
1310 for (int i = 0, end = events.count(); i < end; ++i)
1312 KAEvent* event = events[i];
1313 if (includeCmdAlarms || !(event->actionTypes() & KAEvent::ACT_COMMAND))
1314 templates.append(event);
1316 return templates;
1319 /******************************************************************************
1320 * To be called after an alarm has been edited.
1321 * Prompt the user to re-enable alarms if they are currently disabled, and if
1322 * it's an email alarm, warn if no 'From' email address is configured.
1324 void outputAlarmWarnings(QWidget* parent, const KAEvent* event)
1326 if (event && event->actionTypes() == KAEvent::ACT_EMAIL
1327 && Preferences::emailAddress().isEmpty())
1328 KAMessageBox::information(parent, xi18nc("@info Please set the 'From' email address...",
1329 "<para>%1</para><para>Please set it in the Configuration dialog.</para>", KAMail::i18n_NeedFromEmailAddress()));
1331 if (!theApp()->alarmsEnabled())
1333 if (KAMessageBox::warningYesNo(parent, xi18nc("@info", "<para>Alarms are currently disabled.</para><para>Do you want to enable alarms now?</para>"),
1334 QString(), KGuiItem(i18nc("@action:button", "Enable")), KGuiItem(i18nc("@action:button", "Keep Disabled")),
1335 QStringLiteral("EditEnableAlarms"))
1336 == KMessageBox::Yes)
1337 theApp()->setAlarmsEnabled(true);
1341 /******************************************************************************
1342 * Reload the calendar.
1344 void refreshAlarms()
1346 qCDebug(KALARM_LOG);
1347 if (!refreshAlarmsQueued)
1349 refreshAlarmsQueued = true;
1350 theApp()->processQueue();
1354 /******************************************************************************
1355 * This method must only be called from the main KAlarm queue processing loop,
1356 * to prevent asynchronous calendar operations interfering with one another.
1358 * If refreshAlarms() has been called, reload the calendars.
1360 void refreshAlarmsIfQueued()
1362 if (refreshAlarmsQueued)
1364 qCDebug(KALARM_LOG);
1365 AlarmCalendar::resources()->reload();
1367 // Close any message windows for alarms which are now disabled
1368 KAEvent::List events = AlarmCalendar::resources()->events(CalEvent::ACTIVE);
1369 for (int i = 0, end = events.count(); i < end; ++i)
1371 KAEvent* event = events[i];
1372 if (!event->enabled() && (event->actionTypes() & KAEvent::ACT_DISPLAY))
1374 MessageWin* win = MessageWin::findEvent(EventId(*event));
1375 delete win;
1379 MainWindow::refresh();
1380 refreshAlarmsQueued = false;
1384 /******************************************************************************
1385 * Start KMail if it isn't already running, optionally minimised.
1386 * Reply = reason for failure to run KMail (which may be the empty string)
1387 * = null string if success.
1389 QString runKMail(bool minimise)
1391 QDBusReply<bool> reply = QDBusConnection::sessionBus().interface()->isServiceRegistered(KMAIL_DBUS_SERVICE);
1392 if (!reply.isValid() || !reply.value())
1394 // Program is not already running, so start it
1395 QString errmsg;
1396 if (minimise && Private::startKMailMinimised())
1397 return QString();
1398 if (KToolInvocation::startServiceByDesktopName(QStringLiteral("kmail"), QString(), &errmsg))
1400 qCCritical(KALARM_LOG) << "Couldn't start KMail (" << errmsg << ")";
1401 return xi18nc("@info", "Unable to start <application>KMail</application><nl/>(<message>%1</message>)", errmsg);
1404 return QString();
1407 /******************************************************************************
1408 * Start KMail, minimised.
1409 * This code is taken from kstart in kdebase.
1411 bool Private::startKMailMinimised()
1413 #if 0 //PORT QT5
1414 #if KDEPIM_HAVE_X11
1415 NETRootInfo i(QX11Info::display(), NET::Supported);
1416 if (i.isSupported(NET::WM2KDETemporaryRules))
1418 qCDebug(KALARM_LOG) << "using rules";
1419 KXMessages msg;
1420 QString message = QLatin1String("wmclass=kmail\nwmclassmatch=1\n" // 1 = exact match
1421 "wmclasscomplete=false\n"
1422 "minimize=true\nminimizerule=3\n"
1423 "type=") + QString().setNum(NET::Normal) + QLatin1String("\ntyperule=2");
1424 msg.broadcastMessage("_KDE_NET_WM_TEMPORARY_RULES", message, -1);
1425 qApp->flush();
1427 else
1429 // Connect to window add to get the NEW windows
1430 qCDebug(KALARM_LOG) << "connecting to window add";
1431 connect(KWindowSystem::self(), SIGNAL(windowAdded(WId)), instance(), SLOT(windowAdded(WId)));
1433 // Propagate the app startup notification info to the started app.
1434 // We are not using KApplication, so the env remained set.
1435 KStartupInfoId id = KStartupInfo::currentStartupIdEnv();
1436 KProcess* proc = new KProcess;
1437 (*proc) << QStringLiteral("kmail");
1438 int pid = proc->startDetached();
1439 if (!pid)
1441 KStartupInfo::sendFinish(id); // failed to start
1442 return false;
1444 KStartupInfoData data;
1445 data.addPid(pid);
1446 data.setName(QLatin1String("kmail"));
1447 data.setBin(QLatin1String("kmail"));
1448 KStartupInfo::sendChange(id, data);
1449 return true;
1450 #else
1451 return false;
1452 #endif
1453 #else
1454 return false;
1455 #endif
1458 /******************************************************************************
1459 * Called when a window is created, to minimise it.
1460 * This code is taken from kstart in kdebase.
1462 void Private::windowAdded(WId w)
1464 #if 0 //Port QT5
1465 #if KDEPIM_HAVE_X11
1466 static const int SUPPORTED_TYPES = NET::NormalMask | NET::DesktopMask | NET::DockMask
1467 | NET::ToolbarMask | NET::MenuMask | NET::DialogMask
1468 | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask;
1469 KWindowInfo kwinfo = KWindowSystem::windowInfo(w, NET::WMWindowType | NET::WMName);
1470 if (kwinfo.windowType(SUPPORTED_TYPES) == NET::TopMenu
1471 || kwinfo.windowType(SUPPORTED_TYPES) == NET::Toolbar
1472 || kwinfo.windowType(SUPPORTED_TYPES) == NET::Desktop)
1473 return; // always ignore these window types
1475 QX11Info qxinfo;
1476 XWithdrawWindow(QX11Info::display(), w, qxinfo.screen());
1477 QApplication::flush();
1479 NETWinInfo info(QX11Info::display(), w, QX11Info::appRootWindow(), NET::WMState);
1480 XWMHints* hints = XGetWMHints(QX11Info::display(), w);
1481 if (hints)
1483 hints->flags |= StateHint;
1484 hints->initial_state = IconicState;
1485 XSetWMHints(QX11Info::display(), w, hints);
1486 XFree(hints);
1488 info.setWindowType(NET::Normal);
1490 XSync(QX11Info::display(), False);
1491 XMapWindow(QX11Info::display(), w);
1492 XSync(QX11Info::display(), False);
1493 QApplication::flush();
1494 #endif
1495 #endif
1498 /******************************************************************************
1499 * The "Don't show again" option for error messages is personal to the user on a
1500 * particular computer. For example, he may want to inhibit error messages only
1501 * on his laptop. So the status is not stored in the alarm calendar, but in the
1502 * user's local KAlarm data directory.
1503 ******************************************************************************/
1505 /******************************************************************************
1506 * Return the Don't-show-again error message tags set for a specified alarm ID.
1508 QStringList dontShowErrors(const EventId& eventId)
1510 if (eventId.isEmpty())
1511 return QStringList();
1512 KConfig config(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + ALARM_OPTS_FILE);
1513 KConfigGroup group(&config, DONT_SHOW_ERRORS_GROUP);
1514 const QString id = QStringLiteral("%1:%2").arg(eventId.collectionId()).arg(eventId.eventId());
1515 return group.readEntry(id, QStringList());
1518 /******************************************************************************
1519 * Check whether the specified Don't-show-again error message tag is set for an
1520 * alarm ID.
1522 bool dontShowErrors(const EventId& eventId, const QString& tag)
1524 if (tag.isEmpty())
1525 return false;
1526 QStringList tags = dontShowErrors(eventId);
1527 return tags.indexOf(tag) >= 0;
1530 /******************************************************************************
1531 * Reset the Don't-show-again error message tags for an alarm ID.
1532 * If 'tags' is empty, the config entry is deleted.
1534 void setDontShowErrors(const EventId& eventId, const QStringList& tags)
1536 if (eventId.isEmpty())
1537 return;
1538 KConfig config(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + ALARM_OPTS_FILE);
1539 KConfigGroup group(&config, DONT_SHOW_ERRORS_GROUP);
1540 const QString id = QStringLiteral("%1:%2").arg(eventId.collectionId()).arg(eventId.eventId());
1541 if (tags.isEmpty())
1542 group.deleteEntry(id);
1543 else
1544 group.writeEntry(id, tags);
1545 group.sync();
1548 /******************************************************************************
1549 * Set the specified Don't-show-again error message tag for an alarm ID.
1550 * Existing tags are unaffected.
1552 void setDontShowErrors(const EventId& eventId, const QString& tag)
1554 if (eventId.isEmpty() || tag.isEmpty())
1555 return;
1556 KConfig config(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + ALARM_OPTS_FILE);
1557 KConfigGroup group(&config, DONT_SHOW_ERRORS_GROUP);
1558 const QString id = QStringLiteral("%1:%2").arg(eventId.collectionId()).arg(eventId.eventId());
1559 QStringList tags = group.readEntry(id, QStringList());
1560 if (tags.indexOf(tag) < 0)
1562 tags += tag;
1563 group.writeEntry(id, tags);
1564 group.sync();
1568 /******************************************************************************
1569 * Read the size for the specified window from the config file, for the
1570 * current screen resolution.
1571 * Reply = true if size set in the config file, in which case 'result' is set
1572 * = false if no size is set, in which case 'result' is unchanged.
1574 bool readConfigWindowSize(const char* window, QSize& result, int* splitterWidth)
1576 KConfigGroup config(KSharedConfig::openConfig(), window);
1577 QWidget* desktop = qApp->desktop();
1578 QSize s = QSize(config.readEntry(QStringLiteral("Width %1").arg(desktop->width()), (int)0),
1579 config.readEntry(QStringLiteral("Height %1").arg(desktop->height()), (int)0));
1580 if (s.isEmpty())
1581 return false;
1582 result = s;
1583 if (splitterWidth)
1584 *splitterWidth = config.readEntry(QStringLiteral("Splitter %1").arg(desktop->width()), -1);
1585 return true;
1588 /******************************************************************************
1589 * Write the size for the specified window to the config file, for the
1590 * current screen resolution.
1592 void writeConfigWindowSize(const char* window, const QSize& size, int splitterWidth)
1594 KConfigGroup config(KSharedConfig::openConfig(), window);
1595 QWidget* desktop = qApp->desktop();
1596 config.writeEntry(QStringLiteral("Width %1").arg(desktop->width()), size.width());
1597 config.writeEntry(QStringLiteral("Height %1").arg(desktop->height()), size.height());
1598 if (splitterWidth >= 0)
1599 config.writeEntry(QStringLiteral("Splitter %1").arg(desktop->width()), splitterWidth);
1600 config.sync();
1603 /******************************************************************************
1604 * Check from its mime type whether a file appears to be a text or image file.
1605 * If a text file, its type is distinguished.
1606 * Reply = file type.
1608 FileType fileType(const QMimeType& mimetype)
1610 if (mimetype.inherits(QStringLiteral("text/html")))
1611 return TextFormatted;
1612 if (mimetype.inherits(QStringLiteral("application/x-executable")))
1613 return TextApplication;
1614 if (mimetype.inherits(QStringLiteral("text/plain")))
1615 return TextPlain;
1616 if (mimetype.name().startsWith(QLatin1String("image/")))
1617 return Image;
1618 return Unknown;
1621 /******************************************************************************
1622 * Check that a file exists and is a plain readable file.
1623 * Updates 'filename' and 'url' even if an error occurs, since 'filename' may
1624 * be needed subsequently by showFileErrMessage().
1626 FileErr checkFileExists(QString& filename, QUrl& url)
1628 url = QUrl();
1629 FileErr err = FileErr_None;
1630 QString file = filename;
1631 QRegExp f(QStringLiteral("^file:/+"));
1632 if (f.indexIn(file) >= 0)
1633 file = file.mid(f.matchedLength() - 1);
1634 // Convert any relative file path to absolute
1635 // (using home directory as the default)
1636 int i = file.indexOf(QLatin1Char('/'));
1637 if (i > 0 && file[i - 1] == QLatin1Char(':'))
1639 url = file;
1640 const QString displayStr = url.toDisplayString();
1641 filename = displayStr.mid(displayStr.lastIndexOf(QLatin1Char('/')) + 1);
1642 auto statJob = KIO::stat(url, KIO::StatJob::SourceSide, 2);
1643 KJobWidgets::setWindow(statJob, MainWindow::mainMainWindow());
1644 if (!statJob->exec())
1645 err = FileErr_Nonexistent;
1646 else
1648 KFileItem fi(statJob->statResult(), url);
1649 if (fi.isDir()) err = FileErr_Directory;
1650 else if (!fi.isReadable()) err = FileErr_Unreadable;
1653 else if (file.isEmpty())
1654 err = FileErr_Blank; // blank file name
1655 else
1657 // It's a local file - convert to absolute path & check validity
1658 QFileInfo info(file);
1659 QDir::setCurrent(QDir::homePath());
1660 filename = info.absoluteFilePath();
1661 url.setPath(filename);
1662 if (info.isDir()) err = FileErr_Directory;
1663 else if (!info.exists()) err = FileErr_Nonexistent;
1664 else if (!info.isReadable()) err = FileErr_Unreadable;
1666 return err;
1669 /******************************************************************************
1670 * Display an error message appropriate to 'err'.
1671 * Display a Continue/Cancel error message if 'errmsgParent' non-null.
1672 * Reply = true to continue, false to cancel.
1674 bool showFileErrMessage(const QString& filename, FileErr err, FileErr blankError, QWidget* errmsgParent)
1676 if (err != FileErr_None)
1678 // If file is a local file, remove "file://" from name
1679 QString file = filename;
1680 QRegExp f(QStringLiteral("^file:/+"));
1681 if (f.indexIn(file) >= 0)
1682 file = file.mid(f.matchedLength() - 1);
1684 QString errmsg;
1685 switch (err)
1687 case FileErr_Blank:
1688 if (blankError == FileErr_BlankDisplay)
1689 errmsg = i18nc("@info", "Please select a file to display");
1690 else if (blankError == FileErr_BlankPlay)
1691 errmsg = i18nc("@info", "Please select a file to play");
1692 else
1693 qFatal("Program error");
1694 KAMessageBox::sorry(errmsgParent, errmsg);
1695 return false;
1696 case FileErr_Directory:
1697 KAMessageBox::sorry(errmsgParent, xi18nc("@info", "<filename>%1</filename> is a folder", file));
1698 return false;
1699 case FileErr_Nonexistent: errmsg = xi18nc("@info", "<filename>%1</filename> not found", file); break;
1700 case FileErr_Unreadable: errmsg = xi18nc("@info", "<filename>%1</filename> is not readable", file); break;
1701 case FileErr_NotTextImage: errmsg = xi18nc("@info", "<filename>%1</filename> appears not to be a text or image file", file); break;
1702 default:
1703 break;
1705 if (KAMessageBox::warningContinueCancel(errmsgParent, errmsg)
1706 == KMessageBox::Cancel)
1707 return false;
1709 return true;
1712 /******************************************************************************
1713 * If a url string is a local file, strip off the 'file:/' prefix.
1715 QString pathOrUrl(const QString& url)
1717 static const QRegExp localfile(QStringLiteral("^file:/+"));
1718 return (localfile.indexIn(url) >= 0) ? url.mid(localfile.matchedLength() - 1) : url;
1721 /******************************************************************************
1722 * Display a modal dialog to choose an existing file, initially highlighting
1723 * any specified file.
1724 * @param initialFile The file to initially highlight - must be a full path name or URL.
1725 * @param defaultDir The directory to start in if @p initialFile is empty. If empty,
1726 * the user's home directory will be used. Updated to the
1727 * directory containing the selected file, if a file is chosen.
1728 * @param mode OR of KFile::Mode values, e.g. ExistingOnly, LocalOnly.
1729 * Reply = URL selected.
1730 * = empty, non-null string if no file was selected.
1731 * = null string if dialogue was deleted while visible (indicating that
1732 * the parent widget was probably also deleted).
1734 QString browseFile(const QString& caption, QString& defaultDir, const QString& initialFile,
1735 const QString& filter, KFile::Modes mode, QWidget* parent)
1737 QString initialDir = !initialFile.isEmpty() ? QString(initialFile).remove(QRegExp(QLatin1String("/[^/]*$")))
1738 : !defaultDir.isEmpty() ? defaultDir
1739 : QDir::homePath();
1740 // Use AutoQPointer to guard against crash on application exit while
1741 // the dialogue is still open. It prevents double deletion (both on
1742 // deletion of parent, and on return from this function).
1743 AutoQPointer<KFileDialog> fileDlg = new KFileDialog(initialDir, filter, parent);
1744 fileDlg->setOperationMode(mode & KFile::ExistingOnly ? KFileDialog::Opening : KFileDialog::Saving);
1745 fileDlg->setMode(KFile::File | mode);
1746 fileDlg->setWindowTitle(caption);
1747 if (!initialFile.isEmpty())
1748 fileDlg->setSelection(initialFile);
1749 if (fileDlg->exec() != QDialog::Accepted)
1750 return fileDlg ? QStringLiteral("") : QString(); // return null only if dialog was deleted
1751 QUrl url = fileDlg->selectedUrl();
1752 if (url.isEmpty())
1753 return QStringLiteral(""); // return empty, non-null string
1754 defaultDir = url.isLocalFile() ? KIO::upUrl(url).toLocalFile() : url.adjusted(QUrl::RemoveFilename).path();
1755 return (mode & KFile::LocalOnly) ? url.toDisplayString(QUrl::PreferLocalFile) : url.toDisplayString();
1758 /******************************************************************************
1759 * Return a prompt string to ask the user whether to convert the calendar to the
1760 * current format.
1761 * If 'whole' is true, the whole calendar needs to be converted; else only some
1762 * alarms may need to be converted.
1764 * Note: This method is defined here to avoid duplicating the i18n string
1765 * definition between the Akonadi and KResources code.
1767 QString conversionPrompt(const QString& calendarName, const QString& calendarVersion, bool whole)
1769 QString msg = whole
1770 ? xi18nc("@info", "Calendar <resource>%1</resource> is in an old format (<application>KAlarm</application> version %2), "
1771 "and will be read-only unless you choose to update it to the current format.",
1772 calendarName, calendarVersion)
1773 : xi18nc("@info", "Some or all of the alarms in calendar <resource>%1</resource> are in an old <application>KAlarm</application> format, "
1774 "and will be read-only unless you choose to update them to the current format.",
1775 calendarName);
1776 return xi18nc("@info", "<para>%1</para><para>"
1777 "<warning>Do not update the calendar if it is also used with an older version of <application>KAlarm</application> "
1778 "(e.g. on another computer). If you do so, the calendar may become unusable there.</warning></para>"
1779 "<para>Do you wish to update the calendar?</para>", msg);
1782 #ifndef NDEBUG
1783 /******************************************************************************
1784 * Set up KAlarm test conditions based on environment variables.
1785 * KALARM_TIME: specifies current system time (format [[[yyyy-]mm-]dd-]hh:mm [TZ]).
1787 void setTestModeConditions()
1789 const QByteArray newTime = qgetenv("KALARM_TIME");
1790 if (!newTime.isEmpty())
1792 KDateTime dt;
1793 if (AlarmTime::convertTimeString(newTime, dt, KDateTime::realCurrentLocalDateTime(), true))
1794 setSimulatedSystemTime(dt);
1798 /******************************************************************************
1799 * Set the simulated system time.
1801 void setSimulatedSystemTime(const KDateTime& dt)
1803 KDateTime::setSimulatedSystemTime(dt);
1804 qCDebug(KALARM_LOG) << "New time =" << qPrintable(KDateTime::currentLocalDateTime().toString(QStringLiteral("%Y-%m-%d %H:%M %:Z")));
1806 #endif
1808 } // namespace KAlarm
1809 namespace
1812 /******************************************************************************
1813 * Display an error message about an error when saving an event.
1815 void displayUpdateError(QWidget* parent, KAlarm::UpdateError code, const UpdateStatusData& status, bool showKOrgError)
1817 QString errmsg;
1818 if (status.status.status > KAlarm::UPDATE_KORG_ERR)
1820 switch (code)
1822 case KAlarm::ERR_ADD:
1823 case KAlarm::ERR_MODIFY:
1824 errmsg = (status.warnErr > 1) ? i18nc("@info", "Error saving alarms")
1825 : i18nc("@info", "Error saving alarm");
1826 break;
1827 case KAlarm::ERR_DELETE:
1828 errmsg = (status.warnErr > 1) ? i18nc("@info", "Error deleting alarms")
1829 : i18nc("@info", "Error deleting alarm");
1830 break;
1831 case KAlarm::ERR_REACTIVATE:
1832 errmsg = (status.warnErr > 1) ? i18nc("@info", "Error saving reactivated alarms")
1833 : i18nc("@info", "Error saving reactivated alarm");
1834 break;
1835 case KAlarm::ERR_TEMPLATE:
1836 errmsg = (status.warnErr > 1) ? i18nc("@info", "Error saving alarm templates")
1837 : i18nc("@info", "Error saving alarm template");
1838 break;
1840 KAMessageBox::error(parent, errmsg);
1842 else if (showKOrgError)
1843 displayKOrgUpdateError(parent, code, status.status, status.warnKOrg);
1846 /******************************************************************************
1847 * Tell KOrganizer to put an alarm in its calendar.
1848 * It will be held by KOrganizer as a simple event, without alarms - KAlarm
1849 * is still responsible for alarming.
1851 KAlarm::UpdateResult sendToKOrganizer(const KAEvent& event)
1853 Event::Ptr kcalEvent(new KCalCore::Event);
1854 event.updateKCalEvent(kcalEvent, KAEvent::UID_IGNORE);
1855 // Change the event ID to avoid duplicating the same unique ID as the original event
1856 QString uid = uidKOrganizer(event.id());
1857 kcalEvent->setUid(uid);
1858 kcalEvent->clearAlarms();
1859 QString userEmail;
1860 switch (event.actionTypes())
1862 case KAEvent::ACT_DISPLAY:
1863 case KAEvent::ACT_COMMAND:
1864 case KAEvent::ACT_DISPLAY_COMMAND:
1865 kcalEvent->setSummary(event.cleanText());
1866 userEmail = Preferences::emailAddress();
1867 break;
1868 case KAEvent::ACT_EMAIL:
1870 QString from = event.emailFromId()
1871 ? Identities::identityManager()->identityForUoid(event.emailFromId()).fullEmailAddr()
1872 : Preferences::emailAddress();
1873 AlarmText atext;
1874 atext.setEmail(event.emailAddresses(QStringLiteral(", ")), from, QString(), QString(), event.emailSubject(), QString());
1875 kcalEvent->setSummary(atext.displayText());
1876 userEmail = from;
1877 break;
1879 case KAEvent::ACT_AUDIO:
1880 kcalEvent->setSummary(event.audioFile());
1881 break;
1882 default:
1883 break;
1885 Person::Ptr person(new Person(QString(), userEmail));
1886 kcalEvent->setOrganizer(person);
1887 kcalEvent->setDuration(Duration(Preferences::kOrgEventDuration() * 60, Duration::Seconds));
1889 // Translate the event into string format
1890 ICalFormat format;
1891 format.setTimeSpec(Preferences::timeZone(true));
1892 QString iCal = format.toICalString(kcalEvent);
1894 // Send the event to KOrganizer
1895 KAlarm::UpdateResult status = runKOrganizer(); // start KOrganizer if it isn't already running, and create its D-Bus interface
1896 if (status != KAlarm::UPDATE_OK)
1897 return status;
1898 QList<QVariant> args;
1899 args << iCal;
1900 QDBusReply<bool> reply = korgInterface->callWithArgumentList(QDBus::Block, QStringLiteral("addIncidence"), args);
1901 if (!reply.isValid())
1903 if (reply.error().type() == QDBusError::UnknownObject)
1905 status = KAlarm::UPDATE_KORG_ERRSTART;
1906 qCCritical(KALARM_LOG) << "addIncidence() D-Bus error: still starting";
1908 else
1910 status.set(KAlarm::UPDATE_KORG_ERR, reply.error().message());
1911 qCCritical(KALARM_LOG) << "addIncidence(" << uid << ") D-Bus call failed:" << status.message;
1914 else if (!reply.value())
1916 status = KAlarm::UPDATE_KORG_FUNCERR;
1917 qCDebug(KALARM_LOG) << "addIncidence(" << uid << ") D-Bus call returned false";
1919 else
1920 qCDebug(KALARM_LOG) << uid << ": success";
1921 return status;
1924 /******************************************************************************
1925 * Tell KOrganizer to delete an event from its calendar.
1927 KAlarm::UpdateResult deleteFromKOrganizer(const QString& eventID)
1929 const QString newID = uidKOrganizer(eventID);
1930 new CollectionSearch(KORG_MIME_TYPE, newID, true); // this auto-deletes when complete
1931 // Ignore errors
1932 return KAlarm::UpdateResult(KAlarm::UPDATE_OK);
1935 /******************************************************************************
1936 * Start KOrganizer if not already running, and create its D-Bus interface.
1938 KAlarm::UpdateResult runKOrganizer()
1940 KAlarm::UpdateResult status;
1941 QString error, dbusService;
1942 int result = KDBusServiceStarter::self()->findServiceFor(QStringLiteral("DBUS/Organizer"), QString(), &error, &dbusService);
1943 if (result)
1945 status.set(KAlarm::UPDATE_KORG_ERRINIT, error);
1946 qCWarning(KALARM_LOG) << "Unable to start DBUS/Organizer:" << status.message;
1947 return status;
1949 // If Kontact is running, there is a load() method which needs to be called to
1950 // load KOrganizer into Kontact. But if KOrganizer is running independently,
1951 // the load() method doesn't exist.
1952 QDBusInterface iface(KORG_DBUS_SERVICE, QStringLiteral(KORG_DBUS_LOAD_PATH), QStringLiteral("org.kde.PIMUniqueApplication"));
1953 if (!iface.isValid())
1955 status.set(KAlarm::UPDATE_KORG_ERR, iface.lastError().message());
1956 qCWarning(KALARM_LOG) << "Unable to access " KORG_DBUS_LOAD_PATH " D-Bus interface:" << status.message;
1957 return status;
1959 QDBusReply<bool> reply = iface.call(QStringLiteral("load"));
1960 if ((!reply.isValid() || !reply.value())
1961 && iface.lastError().type() != QDBusError::UnknownMethod)
1963 status.set(KAlarm::UPDATE_KORG_ERR, iface.lastError().message());
1964 qCWarning(KALARM_LOG) << "Loading KOrganizer failed:" << status.message;
1965 return status;
1968 // KOrganizer has been started, but it may not have the necessary
1969 // D-Bus interface available yet.
1970 if (!korgInterface || !korgInterface->isValid())
1972 delete korgInterface;
1973 korgInterface = new QDBusInterface(KORG_DBUS_SERVICE, QStringLiteral(KORG_DBUS_PATH), KORG_DBUS_IFACE);
1974 if (!korgInterface->isValid())
1976 status.set(KAlarm::UPDATE_KORG_ERRSTART, korgInterface->lastError().message());
1977 qCWarning(KALARM_LOG) << "Unable to access " KORG_DBUS_PATH " D-Bus interface:" << status.message;
1978 delete korgInterface;
1979 korgInterface = Q_NULLPTR;
1982 return status;
1985 /******************************************************************************
1986 * Insert a KOrganizer string after the hyphen in the supplied event ID.
1988 QString uidKOrganizer(const QString& id)
1990 QString result = id;
1991 int i = result.lastIndexOf(QLatin1Char('-'));
1992 if (i < 0)
1993 i = result.length();
1994 return result.insert(i, KORGANIZER_UID);
1997 } // namespace
1999 /******************************************************************************
2000 * Case insensitive comparison for use by qSort().
2002 bool caseInsensitiveLessThan(const QString& s1, const QString& s2)
2004 return s1.toLower() < s2.toLower();
2007 // vim: et sw=4: