2 * functions.cpp - miscellaneous functions
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"
32 #include "kalarmapp.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>
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>
73 #include <kwindowsystem.h>
74 #include <kxmessages.h>
75 #include <kstartupinfo.h>
83 #include <QDesktopWidget>
84 #include <QtDBus/QtDBus>
87 #include <QStandardPaths>
88 #include "kalarm_debug.h"
90 using namespace Akonadi
;
95 bool refreshAlarmsQueued
= false;
96 QDBusInterface
* korgInterface
= Q_NULLPTR
;
98 struct UpdateStatusData
100 KAlarm::UpdateResult status
; // status code and KOrganizer error message if any
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)
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
)
120 if (result
.status
> status
.status
)
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
);
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();
163 if (theApp()->checkCalendar()) // ensure calendar is open
165 win
= MainWindow::create();
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
);
176 win
->activateWindow();
178 if (win
&& eventId
>= 0)
179 win
->selectEvent(eventId
);
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
);
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
);
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
);
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
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
;
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
;
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
;
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
);
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
;
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
))
339 event
= *newev
; // update event ID etc.
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
;
362 event
= newev
; // update event ID etc.
364 status
.status
= SAVE_FAILED
;
367 return UpdateResult(UPDATE_OK
);
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
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
;
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
;
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
);
444 // Update the event in the calendar file.
445 AlarmCalendar
* cal
= AlarmCalendar::resources();
446 cal
->updateEvent(event
);
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
;
469 status
= UPDATE_FAILED
;
470 else if (!cal
->save())
471 status
= SAVE_FAILED
;
472 if (status
!= UPDATE_OK
)
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())
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
;
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();
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
;
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());
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
);
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
);
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
);
700 qCCritical(KALARM_LOG
) << "Error updating event in calendar:" << event
->id();
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
));
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
735 void purgeArchive(int purgeDays
)
739 qCDebug(KALARM_LOG
) << purgeDays
;
740 QDate cutoff
= KDateTime::currentLocalDate().addDays(-purgeDays
);
741 Collection collection
= CollectionControlModel::getStandard(CalEvent::ARCHIVED
);
742 if (!collection
.isValid())
744 KAEvent::List events
= AlarmCalendar::resources()->events(collection
);
745 for (int i
= 0; i
< events
.count(); )
747 if (purgeDays
&& events
[i
]->createdDateTime().date() >= cutoff
)
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
;
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())
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
)
792 errmsg
= (nAlarms
> 1) ? i18nc("@info", "Unable to show alarms in KOrganizer")
793 : i18nc("@info", "Unable to show alarm in KOrganizer");
796 errmsg
= i18nc("@info", "Unable to update alarm in KOrganizer");
799 errmsg
= (nAlarms
> 1) ? i18nc("@info", "Unable to delete alarms from KOrganizer")
800 : i18nc("@info", "Unable to delete alarm from KOrganizer");
805 bool showDetail
= !korgError
.message
.isEmpty();
807 switch (korgError
.status
)
809 case UPDATE_KORG_ERRINIT
:
810 msg
= xi18nc("@info", "<para>%1</para><para>(Could not start KOrganizer)</para>", errmsg
);
812 case UPDATE_KORG_ERRSTART
:
813 msg
= xi18nc("@info", "<para>%1</para><para>(KOrganizer not fully started)</para>", errmsg
);
815 case UPDATE_KORG_ERR
:
816 msg
= xi18nc("@info", "<para>%1</para><para>(Error communicating with KOrganizer)</para>", errmsg
);
824 KAMessageBox::detailedError(parent
, msg
, korgError
.message
);
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
;
846 case KAEvent::MESSAGE
:
848 type
= EditAlarmDlg::DISPLAY
;
851 case KAEvent::COMMAND
:
852 type
= EditAlarmDlg::COMMAND
;
855 type
= EditAlarmDlg::EMAIL
;
858 type
= EditAlarmDlg::AUDIO
;
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
);
888 editDlg
->activateWindow();
891 PrivateNewAlarmDlg::PrivateNewAlarmDlg(EditAlarmDlg
* 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
)
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
)
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
);
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
);
959 qCWarning(KALARM_LOG
) << templateName
<< ": template not found";
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
985 * Reply = config entry: [0] = event's collection ID (Akonadi only),
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
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");
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
)
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();
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
;
1070 case KAuth::ActionReply::AuthorizationDeniedError
:
1071 case KAuth::ActionReply::UserCancelledError
:
1072 return false; // the user should already know about this
1077 case KAuth::ActionReply::HelperErrorType
:
1078 qCDebug(KALARM_LOG
) << "Helper error:" << errcode
;
1079 errcode
+= 100; // make code distinguishable from KAuthError type
1084 errmsg
= i18nc("@info", "Error obtaining authorization (%1)", errcode
);
1086 KAMessageBox::information(parent
, errmsg
);
1093 } // namespace KAlarm
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"));
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
;
1114 editDlg
= EditAlarmDlg::create(true, preset
, true, parent
);
1116 editDlg
= EditAlarmDlg::create(true, type
, parent
);
1117 if (editDlg
->exec() == QDialog::Accepted
)
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
);
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
);
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
);
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
);
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
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);
1193 if (id
.collectionId() != -1)
1194 qCWarning(KALARM_LOG
) << "Event ID not found, or duplicated:" << eventID
;
1196 qCWarning(KALARM_LOG
) << "Event ID not found:" << eventID
;
1199 if (AlarmCalendar::resources()->eventReadOnly(event
->itemId()))
1201 qCCritical(KALARM_LOG
) << eventID
<< ": read-only";
1204 switch (event
->category())
1206 case CalEvent::ACTIVE
:
1207 case CalEvent::TEMPLATE
:
1210 qCCritical(KALARM_LOG
) << eventID
<< ": event not active or template";
1213 editAlarm(event
, parent
);
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);
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
)
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);
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
);
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
);
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
);
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
);
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
));
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
1396 if (minimise
&& Private::startKMailMinimised())
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
);
1407 /******************************************************************************
1408 * Start KMail, minimised.
1409 * This code is taken from kstart in kdebase.
1411 bool Private::startKMailMinimised()
1415 NETRootInfo
i(QX11Info::display(), NET::Supported
);
1416 if (i
.isSupported(NET::WM2KDETemporaryRules
))
1418 qCDebug(KALARM_LOG
) << "using rules";
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);
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();
1441 KStartupInfo::sendFinish(id
); // failed to start
1444 KStartupInfoData data
;
1446 data
.setName(QLatin1String("kmail"));
1447 data
.setBin(QLatin1String("kmail"));
1448 KStartupInfo::sendChange(id
, data
);
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
)
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
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
);
1483 hints
->flags
|= StateHint
;
1484 hints
->initial_state
= IconicState
;
1485 XSetWMHints(QX11Info::display(), w
, hints
);
1488 info
.setWindowType(NET::Normal
);
1490 XSync(QX11Info::display(), False
);
1491 XMapWindow(QX11Info::display(), w
);
1492 XSync(QX11Info::display(), False
);
1493 QApplication::flush();
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
1522 bool dontShowErrors(const EventId
& eventId
, const QString
& tag
)
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())
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());
1542 group
.deleteEntry(id
);
1544 group
.writeEntry(id
, tags
);
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())
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)
1563 group
.writeEntry(id
, tags
);
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
= KApplication::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));
1584 *splitterWidth
= config
.readEntry(QStringLiteral("Splitter %1").arg(desktop
->width()), -1);
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
= KApplication::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
);
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")))
1616 if (mimetype
.name().startsWith(QLatin1String("image/")))
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
)
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(':'))
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
;
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
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
;
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);
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");
1693 qFatal("Program error");
1694 KAMessageBox::sorry(errmsgParent
, errmsg
);
1696 case FileErr_Directory
:
1697 KAMessageBox::sorry(errmsgParent
, xi18nc("@info", "<filename>%1</filename> is a folder", file
));
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;
1705 if (KAMessageBox::warningContinueCancel(errmsgParent
, errmsg
)
1706 == KMessageBox::Cancel
)
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
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();
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
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
)
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.",
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
);
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())
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")));
1808 } // namespace KAlarm
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
)
1818 if (status
.status
.status
> KAlarm::UPDATE_KORG_ERR
)
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");
1827 case KAlarm::ERR_DELETE
:
1828 errmsg
= (status
.warnErr
> 1) ? i18nc("@info", "Error deleting alarms")
1829 : i18nc("@info", "Error deleting alarm");
1831 case KAlarm::ERR_REACTIVATE
:
1832 errmsg
= (status
.warnErr
> 1) ? i18nc("@info", "Error saving reactivated alarms")
1833 : i18nc("@info", "Error saving reactivated alarm");
1835 case KAlarm::ERR_TEMPLATE
:
1836 errmsg
= (status
.warnErr
> 1) ? i18nc("@info", "Error saving alarm templates")
1837 : i18nc("@info", "Error saving alarm template");
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();
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();
1868 case KAEvent::ACT_EMAIL
:
1870 QString from
= event
.emailFromId()
1871 ? Identities::identityManager()->identityForUoid(event
.emailFromId()).fullEmailAddr()
1872 : Preferences::emailAddress();
1874 atext
.setEmail(event
.emailAddresses(QStringLiteral(", ")), from
, QString(), QString(), event
.emailSubject(), QString());
1875 kcalEvent
->setSummary(atext
.displayText());
1879 case KAEvent::ACT_AUDIO
:
1880 kcalEvent
->setSummary(event
.audioFile());
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
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
)
1898 QList
<QVariant
> args
;
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";
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";
1920 qCDebug(KALARM_LOG
) << uid
<< ": success";
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
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
);
1945 status
.set(KAlarm::UPDATE_KORG_ERRINIT
, error
);
1946 qCWarning(KALARM_LOG
) << "Unable to start DBUS/Organizer:" << status
.message
;
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
;
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
;
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
;
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('-'));
1993 i
= result
.length();
1994 return result
.insert(i
, KORGANIZER_UID
);
1999 /******************************************************************************
2000 * Case insensitive comparison for use by qSort().
2002 bool caseInsensitiveLessThan(const QString
& s1
, const QString
& s2
)
2004 return s1
.toLower() < s2
.toLower();