1 /*
2 * messagedisplay.cpp - base class to display an alarm or error message
3 * Program: kalarm
4 * SPDX-FileCopyrightText: 2001-2022 David Jarvie <djarvie@kde.org>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9 #include "messagewindow.h"
10 #include "messagenotification.h"
11
12 #include "deferdlg.h"
13 #include "displaycalendar.h"
14 #include "functions.h"
15 #include "kalarmapp.h"
16 #include "mainwindow.h"
17 #include "resourcescalendar.h"
18 #include "resources/resources.h"
19 #include "lib/messagebox.h"
20
21 #include <KLocalizedString>
22
23 using namespace KAlarmCal;
24 using namespace KCalendarCore;
25
26
27 bool MessageDisplay::mRedisplayed = false;
28
29 /******************************************************************************
30 * Create a new instance of a MessageDisplay, the derived class being dependent
31 * on 'event.notify()'.
32 */
create(const KAEvent & event,const KAAlarm & alarm,int flags)33 MessageDisplay* MessageDisplay::create(const KAEvent& event, const KAAlarm& alarm, int flags)
34 {
35 if (event.notify())
36 return new MessageNotification(event, alarm, flags & ~AlwaysHide);
37 else
38 return new MessageWindow(event, alarm, flags);
39 }
40
41 /******************************************************************************
42 * Show an error message about the execution of an alarm.
43 * If 'dontShowAgain' is non-null, a "Don't show again" option is displayed. Note
44 * that the option is specific to 'event'.
45 */
showError(const KAEvent & event,const DateTime & alarmDateTime,const QStringList & errmsgs,const QString & dontShowAgain)46 void MessageDisplay::showError(const KAEvent& event, const DateTime& alarmDateTime,
47 const QStringList& errmsgs, const QString& dontShowAgain)
48 {
49 if (MessageDisplayHelper::shouldShowError(event, errmsgs, dontShowAgain))
50 {
51 MessageDisplay* disp;
52 if (event.notify())
53 disp = new MessageNotification(event, alarmDateTime, errmsgs, dontShowAgain);
54 else
55 disp = new MessageWindow(event, alarmDateTime, errmsgs, dontShowAgain);
56 disp->showDisplay();
57 }
58 }
59
60 /******************************************************************************
61 * Constructors.
62 */
MessageDisplay()63 MessageDisplay::MessageDisplay()
64 : mHelper(new MessageDisplayHelper(this))
65 {
66 }
67
MessageDisplay(const KAEvent & event,const KAAlarm & alarm,int flags)68 MessageDisplay::MessageDisplay(const KAEvent& event, const KAAlarm& alarm, int flags)
69 : mHelper(new MessageDisplayHelper(this, event, alarm, flags))
70 {
71 }
72
MessageDisplay(const KAEvent & event,const DateTime & alarmDateTime,const QStringList & errmsgs,const QString & dontShowAgain)73 MessageDisplay::MessageDisplay(const KAEvent& event, const DateTime& alarmDateTime,
74 const QStringList& errmsgs, const QString& dontShowAgain)
75 : mHelper(new MessageDisplayHelper(this, event, alarmDateTime, errmsgs, dontShowAgain))
76 {
77 }
78
MessageDisplay(MessageDisplayHelper * helper)79 MessageDisplay::MessageDisplay(MessageDisplayHelper* helper)
80 : mHelper(helper)
81 {
82 mHelper->setParent(this);
83 }
84
~MessageDisplay()85 MessageDisplay::~MessageDisplay()
86 {
87 delete mHelper;
88 if (!instanceCount(true))
89 theApp()->quitIf(); // no visible displays remain - check whether to quit
90 }
91
92 /******************************************************************************
93 * Redisplay alarms which were being shown when the program last exited.
94 * Normally, these alarms will have been displayed by session restoration, but
95 * if the program crashed or was killed, we can redisplay them here so that
96 * they won't be lost.
97 */
redisplayAlarms()98 void MessageDisplay::redisplayAlarms()
99 {
100 if (mRedisplayed)
101 return;
102 qCDebug(KALARM_LOG) << "MessageDisplay::redisplayAlarms";
103 mRedisplayed = true;
104 if (DisplayCalendar::isOpen())
105 {
106 KAEvent event;
107 Resource resource;
108 const Event::List kcalEvents = DisplayCalendar::kcalEvents();
109 for (const Event::Ptr& kcalEvent : kcalEvents)
110 {
111 bool showDefer, showEdit;
112 if (!reinstateFromDisplaying(kcalEvent, event, resource, showEdit, showDefer))
113 continue;
114 const EventId eventId(event);
115 if (findEvent(eventId))
116 qCDebug(KALARM_LOG) << "MessageDisplay::redisplayAlarms: Message display already exists:" << eventId;
117 else
118 {
119 // This event should be displayed, but currently isn't being
120 const KAAlarm alarm = event.convertDisplayingAlarm();
121 if (alarm.type() == KAAlarm::INVALID_ALARM)
122 {
123 qCCritical(KALARM_LOG) << "MessageDisplay::redisplayAlarms: Invalid alarm: id=" << eventId;
124 continue;
125 }
126 qCDebug(KALARM_LOG) << "MessageDisplay::redisplayAlarms:" << eventId;
127 const bool login = alarm.repeatAtLogin();
128 const int flags = NoReschedule | (login ? NoDefer : 0) | NoInitView;
129 MessageDisplay* d = create(event, alarm, flags);
130 MessageDisplayHelper* h = d->mHelper;
131 h->mResource = resource;
132 const bool rw = resource.isWritable(event.category());
133 h->mShowEdit = rw ? showEdit : false;
134 h->mNoDefer = (rw && !login) ? !showDefer : true;
135 d->setUpDisplay();
136 d->showDisplay();
137 }
138 }
139 }
140 }
141
142 /******************************************************************************
143 * Retrieves the event with the current ID from the displaying calendar file,
144 * or if not found there, from the archive calendar. 'resource' is set to the
145 * resource which originally contained the event, or invalid if not known.
146 */
retrieveEvent(const EventId & evntId,KAEvent & event,Resource & resource,bool & showEdit,bool & showDefer)147 bool MessageDisplay::retrieveEvent(const EventId& evntId, KAEvent& event, Resource& resource, bool& showEdit, bool& showDefer)
148 {
149 const QString eventId = evntId.eventId();
150 const Event::Ptr kcalEvent = DisplayCalendar::kcalEvent(CalEvent::uid(eventId, CalEvent::DISPLAYING));
151 if (!reinstateFromDisplaying(kcalEvent, event, resource, showEdit, showDefer))
152 {
153 // The event isn't in the displaying calendar.
154 // Try to retrieve it from the archive calendar.
155 KAEvent ev;
156 Resource archiveRes = Resources::getStandard(CalEvent::ARCHIVED, true);
157 if (archiveRes.isValid())
158 ev = ResourcesCalendar::event(EventId(archiveRes.id(), CalEvent::uid(eventId, CalEvent::ARCHIVED)));
159 if (!ev.isValid())
160 return false;
161 event = ev;
162 event.setArchive(); // ensure that it gets re-archived if it's saved
163 event.setCategory(CalEvent::ACTIVE);
164 if (eventId != event.id())
165 qCCritical(KALARM_LOG) << "MessageDisplay::retrieveEvent: Wrong event ID";
166 event.setEventId(eventId);
167 resource = Resource();
168 showEdit = false;
169 showDefer = false;
170 qCDebug(KALARM_LOG) << "MessageDisplay::retrieveEvent:" << event.id() << ": success";
171 }
172 return true;
173 }
174
175 /******************************************************************************
176 * Retrieves the displayed event from the calendar file, or if not found there,
177 * from the displaying calendar. 'resource' is set to the resource which
178 * originally contained the event.
179 */
reinstateFromDisplaying(const Event::Ptr & kcalEvent,KAEvent & event,Resource & resource,bool & showEdit,bool & showDefer)180 bool MessageDisplay::reinstateFromDisplaying(const Event::Ptr& kcalEvent, KAEvent& event, Resource& resource, bool& showEdit, bool& showDefer)
181 {
182 if (!kcalEvent)
183 return false;
184 ResourceId resourceId;
185 event.reinstateFromDisplaying(kcalEvent, resourceId, showEdit, showDefer);
186 event.setResourceId(resourceId);
187 resource = Resources::resource(resourceId);
188 qCDebug(KALARM_LOG) << "MessageDisplay::reinstateFromDisplaying:" << EventId(event) << ": success";
189 return true;
190 }
191
192 /******************************************************************************
193 * Display the main window, with the appropriate alarm selected.
194 */
displayMainWindow()195 void MessageDisplay::displayMainWindow()
196 {
197 KAlarm::displayMainWindowSelected(mEventId().eventId());
198 }
199
~DeferDlgData()200 MessageDisplay::DeferDlgData::~DeferDlgData()
201 {
202 delete dlg;
203 }
204
205 /******************************************************************************
206 * Create a defer message dialog.
207 */
createDeferDlg(QObject * thisObject,bool displayClosing)208 MessageDisplay::DeferDlgData* MessageDisplay::createDeferDlg(QObject* thisObject, bool displayClosing)
209 {
210 DeferAlarmDlg* dlg = new DeferAlarmDlg(KADateTime::currentDateTime(Preferences::timeSpec()).addSecs(60), mDateTime().isDateOnly(), false, MainWindow::mainMainWindow());
211 dlg->setObjectName(QStringLiteral("DeferDlg")); // used by LikeBack
212 dlg->setDeferMinutes(mDefaultDeferMinutes() > 0 ? mDefaultDeferMinutes() : Preferences::defaultDeferTime());
213 dlg->setLimit(mEvent());
214 auto data = new DeferDlgData(this, dlg);
215 if (!displayClosing)
216 data->displayObj = thisObject;
217 data->eventId = mEventId();
218 data->alarmType = mAlarmType();
219 data->commandError = mCommandError();
220 return data;
221 }
222
223 /******************************************************************************
224 * Display a defer message dialog.
225 */
executeDeferDlg(DeferDlgData * data)226 void MessageDisplay::executeDeferDlg(DeferDlgData* data)
227 {
228 MainWindow::mainMainWindow()->showDeferAlarmDlg(data);
229 }
230
231 /******************************************************************************
232 * Process the result of a defer message dialog.
233 */
processDeferDlg(DeferDlgData * data,int result)234 void MessageDisplay::processDeferDlg(DeferDlgData* data, int result)
235 {
236 MessageDisplay* display = data->displayObj ? data->display : nullptr;
237 if (result == QDialog::Accepted)
238 {
239 const DateTime dateTime = data->dlg->getDateTime();
240 const int delayMins = data->dlg->deferMinutes();
241 // Fetch the up-to-date alarm from the calendar. Note that it could have
242 // changed since it was displayed.
243 KAEvent event;
244 if (!data->eventId.isEmpty())
245 event = ResourcesCalendar::event(data->eventId);
246 if (event.isValid())
247 {
248 // The event still exists in the active calendar
249 qCDebug(KALARM_LOG) << "MessageDisplay::executeDeferDlg: Deferring event" << data->eventId;
250 KAEvent newev(event);
251 newev.defer(dateTime, (data->alarmType & KAAlarm::REMINDER_ALARM), true);
252 newev.setDeferDefaultMinutes(delayMins);
253 KAlarm::updateEvent(newev, data->dlg, true);
254 if (display)
255 {
256 if (newev.deferred())
257 display->mNoPostAction() = true;
258 }
259 }
260 else
261 {
262 // Try to retrieve the event from the displaying or archive calendars
263 Resource resource; // receives the event's original resource, if known
264 KAEvent event2;
265 bool showEdit, showDefer;
266 if (!retrieveEvent(data->eventId, event2, resource, showEdit, showDefer))
267 {
268 // The event doesn't exist any more !?!, so recurrence data,
269 // flags, and more, have been lost.
270 QWidget* par = display ? display->displayParent() : MainWindow::mainMainWindow();
271 KAMessageBox::error(par, xi18nc("@info", "<para>Cannot defer alarm:</para><para>Alarm not found.</para>"));
272 if (display)
273 {
274 display->raiseDisplay();
275 display->enableDeferButton(false);
276 display->enableEditButton(false);
277 }
278 delete data;
279 return;
280 }
281 qCDebug(KALARM_LOG) << "MessageDisplay::executeDeferDlg: Deferring retrieved event" << data->eventId;
282 event2.defer(dateTime, (data->alarmType & KAAlarm::REMINDER_ALARM), true);
283 event2.setDeferDefaultMinutes(delayMins);
284 event2.setCommandError(data->commandError);
285 // Add the event back into the calendar file, retaining its ID
286 // and not updating KOrganizer.
287 KAlarm::addEvent(event2, resource, data->dlg, KAlarm::USE_EVENT_ID);
288 if (display)
289 {
290 if (event2.deferred())
291 display->mNoPostAction() = true;
292 }
293 // Finally delete it from the archived calendar now that it has
294 // been reactivated.
295 event2.setCategory(CalEvent::ARCHIVED);
296 Resource res;
297 KAlarm::deleteEvent(event2, res, false);
298 }
299 if (theApp()->wantShowInSystemTray())
300 {
301 // Alarms are to be displayed only if the system tray icon is running,
302 // so start it if necessary so that the deferred alarm will be shown.
303 theApp()->displayTrayIcon(true);
304 }
305 if (display)
306 {
307 display->mHelper->mNoCloseConfirm = true; // allow window to close without confirmation prompt
308 display->closeDisplay();
309 }
310 }
311 else
312 {
313 if (display)
314 display->raiseDisplay();
315 }
316 delete data;
317 }
318
319 // vim: et sw=4:
320