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