File indexing completed on 2024-05-12 05:14:46
0001 /* 0002 * functions.cpp - miscellaneous functions 0003 * Program: kalarm 0004 * SPDX-FileCopyrightText: 2001-2023 David Jarvie <djarvie@kde.org> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "functions.h" 0010 #include "functions_p.h" 0011 0012 #include "displaycalendar.h" 0013 #include "kalarmapp.h" 0014 #include "kamail.h" 0015 #include "mainwindow.h" 0016 #include "preferences.h" 0017 #include "resourcescalendar.h" 0018 #include "resources/calendarfunctions.h" 0019 #include "resources/datamodel.h" 0020 #include "resources/eventmodel.h" 0021 #include "resources/resources.h" 0022 #include "lib/autoqpointer.h" 0023 #include "lib/dragdrop.h" 0024 #include "lib/filedialog.h" 0025 #include "lib/messagebox.h" 0026 #include "lib/shellprocess.h" 0027 #include "kalarmcalendar/identities.h" 0028 #include "akonadiplugin/akonadiplugin.h" 0029 #include "kalarm_debug.h" 0030 0031 #include <KCalendarCore/Event> 0032 #include <KCalendarCore/ICalFormat> 0033 #include <KCalendarCore/Person> 0034 #include <KCalendarCore/Duration> 0035 #include <KCalendarCore/MemoryCalendar> 0036 using namespace KCalendarCore; 0037 #include <KIdentityManagementCore/IdentityManager> 0038 #include <KIdentityManagementCore/Identity> 0039 0040 #include <KConfigGroup> 0041 #include <KSharedConfig> 0042 #include <KToggleAction> 0043 #include <KLocalizedString> 0044 0045 #if ENABLE_RTC_WAKE_FROM_SUSPEND 0046 #include <KAuth/ActionReply> 0047 #include <KAuth/ExecuteJob> 0048 #endif 0049 #include <KStandardShortcut> 0050 #include <KFileItem> 0051 #include <KJobWidgets> 0052 #include <KIO/StatJob> 0053 #include <KIO/StoredTransferJob> 0054 #include <KFileCustomDialog> 0055 #include <KWindowSystem> 0056 #if ENABLE_X11 0057 #include <KWindowInfo> 0058 #include <KX11Extras> 0059 #endif 0060 0061 #include <QAction> 0062 #include <QDBusConnectionInterface> 0063 #include <QDBusInterface> 0064 #include <QTimer> 0065 #include <QMimeData> 0066 #include <QStandardPaths> 0067 #include <QPushButton> 0068 #include <QTemporaryFile> 0069 0070 //clazy:excludeall=non-pod-global-static 0071 0072 namespace 0073 { 0074 bool refreshAlarmsQueued = false; 0075 QUrl lastImportUrl; // last URL for Import Alarms file dialogue 0076 QUrl lastExportUrl; // last URL for Export Alarms file dialogue 0077 0078 struct UpdateStatusData 0079 { 0080 KAlarm::UpdateResult status; // status code and error message if any 0081 int warnErr; 0082 int warnKOrg; 0083 0084 explicit UpdateStatusData(KAlarm::UpdateStatus s = KAlarm::UPDATE_OK) : status(s), warnErr(0), warnKOrg(0) {} 0085 int failedCount() const { return status.failed.count(); } 0086 void appendFailed(int index) { status.failed.append(index); } 0087 // Set an error status and increment to number of errors to warn about 0088 void setError(KAlarm::UpdateStatus st, int errorCount = -1, const QString& msg = QString()) 0089 { 0090 status.set(st, msg); 0091 if (errorCount < 0) 0092 ++warnErr; 0093 else 0094 warnErr = errorCount; 0095 } 0096 // Set an error status and increment to number of errors to warn about, 0097 // without changing the error message 0098 void updateError(KAlarm::UpdateStatus st, int errorCount = -1) 0099 { 0100 status.status = st; 0101 if (errorCount < 0) 0102 ++warnErr; 0103 else 0104 warnErr = errorCount; 0105 } 0106 // Update the error status with a KOrganizer related status 0107 void korgUpdate(const KAlarm::UpdateResult& result) 0108 { 0109 if (result.status != KAlarm::UPDATE_OK) 0110 { 0111 ++warnKOrg; 0112 if (result.status > status.status) 0113 status = result; 0114 } 0115 } 0116 }; 0117 0118 const QLatin1String KORG_DBUS_SERVICE("org.kde.korganizer"); 0119 const QLatin1String KORG_DBUS_IFACE("org.kde.korganizer.Korganizer"); 0120 // D-Bus object path of KOrganizer's notification interface 0121 #define KORG_DBUS_PATH "/Korganizer" 0122 #define KORG_DBUS_LOAD_PATH "/korganizer_PimApplication" 0123 const QLatin1String KORG_MIME_TYPE("application/x-vnd.akonadi.calendar.event"); 0124 const QLatin1String KORGANIZER_UID("korg-"); 0125 0126 const QLatin1String ALARM_OPTS_FILE("alarmopts"); 0127 const QLatin1String DONT_SHOW_ERRORS_GROUP("DontShowErrors"); 0128 0129 const QLatin1String GENERAL_GROUP("General"); 0130 0131 KAlarm::UpdateResult updateEvent(KAEvent&, KAlarm::UpdateError, QWidget* msgParent, bool saveIfReadOnly); 0132 void editNewTemplate(EditAlarmDlg::Type, const KAEvent& preset, QWidget* parent); 0133 void displayUpdateError(QWidget* parent, KAlarm::UpdateError, const UpdateStatusData&, bool showKOrgError = true); 0134 KAlarm::UpdateResult sendToKOrganizer(const KAEvent&); 0135 KAlarm::UpdateResult deleteFromKOrganizer(const QString& eventID); 0136 KAlarm::UpdateResult runKOrganizer(); 0137 QString uidKOrganizer(const QString& eventID); 0138 } 0139 0140 0141 namespace KAlarm 0142 { 0143 0144 // Collect these widget labels together to ensure consistent wording and 0145 // translations across different modules. 0146 QString i18n_act_StopPlay() { return i18nc("@action", "Stop Play"); } 0147 0148 Private* Private::mInstance = nullptr; 0149 0150 /****************************************************************************** 0151 * Display a main window with the specified event selected. 0152 */ 0153 MainWindow* displayMainWindowSelected(const QString& eventId) 0154 { 0155 MainWindow* win = MainWindow::firstWindow(); 0156 if (!win) 0157 { 0158 if (theApp()->checkCalendar()) // ensure calendar is open 0159 { 0160 win = MainWindow::create(); 0161 win->show(); 0162 } 0163 } 0164 else 0165 { 0166 // There is already a main window, so make it the active window 0167 #if ENABLE_X11 0168 KWindowInfo wi(win->winId(), NET::WMDesktop); 0169 if (!wi.valid() || !wi.isOnDesktop(KX11Extras::currentDesktop())) 0170 { 0171 // The main window isn't on the current virtual desktop. Hide it 0172 // first so that it will be shown on the current desktop when it is 0173 // shown again. Note that this shifts the window's position, so 0174 // don't hide it if it's already on the current desktop. 0175 win->hide(); 0176 } 0177 #endif 0178 win->setWindowState(win->windowState() & ~Qt::WindowMinimized); 0179 win->show(); 0180 win->raise(); 0181 KWindowSystem::activateWindow(win->windowHandle()); 0182 } 0183 if (win) 0184 win->selectEvent(eventId); 0185 return win; 0186 } 0187 0188 /****************************************************************************** 0189 * Create an "Alarms Enabled/Enable Alarms" action. 0190 */ 0191 KToggleAction* createAlarmEnableAction(QObject* parent) 0192 { 0193 KToggleAction* action = new KToggleAction(i18nc("@action", "Enable Alarms"), parent); 0194 action->setChecked(theApp()->alarmsEnabled()); 0195 QObject::connect(action, &QAction::toggled, theApp(), &KAlarmApp::setAlarmsEnabled); 0196 // The following line ensures that all instances are kept in the same state 0197 QObject::connect(theApp(), &KAlarmApp::alarmEnabledToggled, action, &QAction::setChecked); 0198 return action; 0199 } 0200 0201 /****************************************************************************** 0202 * Create a "Stop Play" action. 0203 */ 0204 QAction* createStopPlayAction(QObject* parent) 0205 { 0206 QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-stop")), i18n_act_StopPlay(), parent); 0207 action->setEnabled(MessageDisplay::isAudioPlaying()); 0208 QObject::connect(action, &QAction::triggered, theApp(), &KAlarmApp::stopAudio); 0209 // The following line ensures that all instances are kept in the same state 0210 QObject::connect(theApp(), &KAlarmApp::audioPlaying, action, &QAction::setEnabled); 0211 return action; 0212 } 0213 0214 /****************************************************************************** 0215 * Create a "Spread Windows" action. 0216 */ 0217 KToggleAction* createSpreadWindowsAction(QObject* parent) 0218 { 0219 KToggleAction* action = new KToggleAction(i18nc("@action", "Spread Windows"), parent); 0220 QObject::connect(action, &QAction::triggered, theApp(), &KAlarmApp::spreadWindows); 0221 // The following line ensures that all instances are kept in the same state 0222 QObject::connect(theApp(), &KAlarmApp::spreadWindowsToggled, action, &QAction::setChecked); 0223 return action; 0224 } 0225 0226 /****************************************************************************** 0227 * Add a new active (non-archived) alarm, and save it in its resource. 0228 * Parameters: msgParent = parent widget for any calendar selection prompt or 0229 * error message. 0230 * event - is updated with the actual event ID. 0231 */ 0232 UpdateResult addEvent(KAEvent& event, Resource& resource, QWidget* msgParent, int options, bool showKOrgErr) 0233 { 0234 qCDebug(KALARM_LOG) << "KAlarm::addEvent:" << event.id(); 0235 bool cancelled = false; 0236 UpdateStatusData status; 0237 if (!theApp()->checkCalendar()) // ensure calendar is open 0238 status.status = UPDATE_FAILED; 0239 else 0240 { 0241 // Save the event details in the calendar file, and get the new event ID 0242 // Note that ResourcesCalendar::addEvent() updates 'event'. 0243 ResourcesCalendar::AddEventOptions rc_options = {}; 0244 if (options & USE_EVENT_ID) 0245 rc_options |= ResourcesCalendar::UseEventId; 0246 if (options & NO_RESOURCE_PROMPT) 0247 rc_options |= ResourcesCalendar::NoResourcePrompt; 0248 if (!ResourcesCalendar::addEvent(event, resource, msgParent, rc_options, &cancelled)) 0249 { 0250 status.status = UPDATE_FAILED; 0251 } 0252 else 0253 { 0254 if (!resource.save(&status.status.message)) 0255 { 0256 resource.reload(true); // discard the new event 0257 status.status.status = SAVE_FAILED; 0258 } 0259 } 0260 if (status.status == UPDATE_OK) 0261 { 0262 if ((options & ALLOW_KORG_UPDATE) && event.copyToKOrganizer()) 0263 { 0264 UpdateResult st = sendToKOrganizer(event); // tell KOrganizer to show the event 0265 status.korgUpdate(st); 0266 } 0267 } 0268 } 0269 0270 if (status.status != UPDATE_OK && !cancelled && msgParent) 0271 displayUpdateError(msgParent, ERR_ADD, status, showKOrgErr); 0272 return status.status; 0273 } 0274 0275 /****************************************************************************** 0276 * Add a list of new active (non-archived) alarms, and save them in the resource. 0277 * The events are updated with their actual event IDs. 0278 */ 0279 UpdateResult addEvents(QList<KAEvent>& events, Resource& resource, QWidget* msgParent, 0280 bool allowKOrgUpdate, bool showKOrgErr) 0281 { 0282 qCDebug(KALARM_LOG) << "KAlarm::addEvents:" << events.count(); 0283 if (events.isEmpty()) 0284 return UpdateResult(UPDATE_OK); 0285 UpdateStatusData status; 0286 if (!theApp()->checkCalendar()) // ensure calendar is open 0287 status.status = UPDATE_FAILED; 0288 else 0289 { 0290 if (!resource.isValid()) 0291 resource = Resources::destination(CalEvent::ACTIVE, msgParent); 0292 if (!resource.isValid()) 0293 { 0294 qCDebug(KALARM_LOG) << "KAlarm::addEvents: No calendar"; 0295 status.status = UPDATE_FAILED; 0296 } 0297 else 0298 { 0299 for (int i = 0, end = events.count(); i < end; ++i) 0300 { 0301 // Save the event details in the calendar file, and get the new event ID 0302 KAEvent& event = events[i]; 0303 if (!ResourcesCalendar::addEvent(event, resource, msgParent)) 0304 { 0305 status.appendFailed(i); 0306 status.setError(UPDATE_ERROR); 0307 continue; 0308 } 0309 if (allowKOrgUpdate && event.copyToKOrganizer()) 0310 { 0311 UpdateResult st = sendToKOrganizer(event); // tell KOrganizer to show the event 0312 status.korgUpdate(st); 0313 } 0314 0315 } 0316 if (status.failedCount() == events.count()) 0317 status.status = UPDATE_FAILED; 0318 else if (!resource.save(&status.status.message)) 0319 { 0320 resource.reload(true); // discard the new events 0321 status.updateError(SAVE_FAILED, events.count()); // everything failed 0322 } 0323 } 0324 } 0325 0326 if (status.status != UPDATE_OK && msgParent) 0327 displayUpdateError(msgParent, ERR_ADD, status, showKOrgErr); 0328 return status.status; 0329 } 0330 0331 /****************************************************************************** 0332 * Save the event in the archived calendar. 0333 * The event's ID is changed to an archived ID if necessary. 0334 */ 0335 bool addArchivedEvent(KAEvent& event, Resource& resource) 0336 { 0337 qCDebug(KALARM_LOG) << "KAlarm::addArchivedEvent:" << event.id(); 0338 bool archiving = (event.category() == CalEvent::ACTIVE); 0339 if (archiving && !Preferences::archivedKeepDays()) 0340 return false; // expired alarms aren't being kept 0341 KAEvent newevent(event); 0342 KAEvent* const newev = &newevent; 0343 if (archiving) 0344 { 0345 newev->setCategory(CalEvent::ARCHIVED); // this changes the event ID 0346 newev->setCreatedDateTime(KADateTime::currentUtcDateTime()); // time stamp to control purging 0347 } 0348 // Add the event from the archived resource. It's not too important whether 0349 // the resource is saved successfully after the deletion, so allow it to be 0350 // saved automatically. 0351 if (!ResourcesCalendar::addEvent(newevent, resource)) 0352 return false; 0353 event = *newev; // update event ID etc. 0354 0355 return true; 0356 } 0357 0358 /****************************************************************************** 0359 * Add a new template. 0360 * 'event' is updated with the actual event ID. 0361 * Parameters: promptParent = parent widget for any calendar selection prompt. 0362 */ 0363 UpdateResult addTemplate(KAEvent& event, Resource& resource, QWidget* msgParent) 0364 { 0365 qCDebug(KALARM_LOG) << "KAlarm::addTemplate:" << event.id(); 0366 UpdateStatusData status; 0367 0368 // Add the template to the calendar file 0369 KAEvent newev(event); 0370 if (!ResourcesCalendar::addEvent(newev, resource, msgParent)) 0371 status.status = UPDATE_FAILED; 0372 else 0373 { 0374 event = newev; // update event ID etc. 0375 if (!resource.save(&status.status.message)) 0376 { 0377 resource.reload(true); // discard the new template 0378 status.status.status = SAVE_FAILED; 0379 } 0380 else 0381 return UpdateResult(UPDATE_OK); 0382 } 0383 0384 if (msgParent) 0385 displayUpdateError(msgParent, ERR_TEMPLATE, status); 0386 return status.status; 0387 } 0388 0389 /****************************************************************************** 0390 * Modify an active (non-archived) alarm in a resource. 0391 * The new event must have a different event ID from the old one. 0392 */ 0393 UpdateResult modifyEvent(KAEvent& oldEvent, KAEvent& newEvent, QWidget* msgParent, bool showKOrgErr) 0394 { 0395 qCDebug(KALARM_LOG) << "KAlarm::modifyEvent:" << oldEvent.id(); 0396 0397 UpdateStatusData status; 0398 Resource resource = Resources::resource(oldEvent.resourceId()); 0399 if (!newEvent.isValid()) 0400 { 0401 deleteEvent(oldEvent, resource, true); 0402 status.status = UPDATE_FAILED; 0403 } 0404 else 0405 { 0406 EventId oldId(oldEvent); 0407 if (oldEvent.copyToKOrganizer()) 0408 { 0409 // Tell KOrganizer to delete its old event. 0410 // But ignore errors, because the user could have manually 0411 // deleted it since KAlarm asked KOrganizer to set it up. 0412 deleteFromKOrganizer(oldId.eventId()); 0413 } 0414 // Update the event in the calendar file, and get the new event ID 0415 if (!ResourcesCalendar::modifyEvent(oldId, newEvent)) 0416 status.status = UPDATE_FAILED; 0417 else 0418 { 0419 if (!resource.save(&status.status.message)) 0420 { 0421 resource.reload(true); // retrieve the pre-update version of the event 0422 status.status.status = SAVE_FAILED; 0423 } 0424 if (status.status == UPDATE_OK) 0425 { 0426 if (newEvent.copyToKOrganizer()) 0427 { 0428 UpdateResult st = sendToKOrganizer(newEvent); // tell KOrganizer to show the new event 0429 status.korgUpdate(st); 0430 } 0431 0432 // Remove "Don't show error messages again" for the old alarm 0433 setDontShowErrors(oldId); 0434 } 0435 } 0436 } 0437 0438 if (status.status != UPDATE_OK && msgParent) 0439 displayUpdateError(msgParent, ERR_MODIFY, status, showKOrgErr); 0440 return status.status; 0441 } 0442 0443 /****************************************************************************** 0444 * Update an active (non-archived) alarm in its resource. 0445 * The new event will have the same event ID as the old one. 0446 * The event is not updated in KOrganizer, since this function is called when an 0447 * existing alarm is rescheduled (due to recurrence or deferral). 0448 */ 0449 UpdateResult updateEvent(KAEvent& event, QWidget* msgParent, bool archiveOnDelete, bool saveIfReadOnly) 0450 { 0451 qCDebug(KALARM_LOG) << "KAlarm::updateEvent:" << event.id(); 0452 0453 if (!event.isValid()) 0454 { 0455 Resource resource; 0456 deleteEvent(event, resource, archiveOnDelete); 0457 return UpdateResult(UPDATE_OK); 0458 } 0459 0460 // Update the event in the resource. 0461 return ::updateEvent(event, ERR_MODIFY, msgParent, saveIfReadOnly); 0462 } 0463 0464 /****************************************************************************** 0465 * Update a template in its resource. 0466 */ 0467 UpdateResult updateTemplate(KAEvent& event, QWidget* msgParent) 0468 { 0469 return ::updateEvent(event, ERR_TEMPLATE, msgParent, true); 0470 } 0471 0472 /****************************************************************************** 0473 * Delete alarms from a resource. 0474 * If the events are archived, the events' IDs are changed to archived IDs if necessary. 0475 */ 0476 UpdateResult deleteEvent(KAEvent& event, Resource& resource, bool archive, QWidget* msgParent, bool showKOrgErr) 0477 { 0478 QList<KAEvent> events(1, event); 0479 return deleteEvents(events, resource, archive, msgParent, showKOrgErr); 0480 } 0481 0482 UpdateResult deleteEvents(QList<KAEvent>& events, Resource& resource, bool archive, QWidget* msgParent, bool showKOrgErr) 0483 { 0484 qCDebug(KALARM_LOG) << "KAlarm::deleteEvents:" << events.count(); 0485 if (events.isEmpty()) 0486 return UpdateResult(UPDATE_OK); 0487 UpdateStatusData status; 0488 #if ENABLE_RTC_WAKE_FROM_SUSPEND 0489 bool deleteWakeFromSuspendAlarm = false; 0490 const QString wakeFromSuspendId = checkRtcWakeConfig().value(0); 0491 #endif 0492 QSet<Resource> resources; // resources which events have been deleted from 0493 for (int i = 0, end = events.count(); i < end; ++i) 0494 { 0495 // Save the event details in the calendar file, and get the new event ID 0496 KAEvent* event = &events[i]; 0497 const QString id = event->id(); 0498 0499 // Delete the event from the calendar file 0500 if (event->category() != CalEvent::ARCHIVED) 0501 { 0502 if (event->copyToKOrganizer()) 0503 { 0504 // The event was shown in KOrganizer, so tell KOrganizer to 0505 // delete it. But ignore errors, because the user could have 0506 // manually deleted it from KOrganizer since it was set up. 0507 UpdateResult st = deleteFromKOrganizer(id); 0508 status.korgUpdate(st); 0509 } 0510 if (archive && event->toBeArchived()) 0511 { 0512 Resource archResource; 0513 KAEvent ev(*event); 0514 addArchivedEvent(ev, archResource); // this changes the event ID to an archived ID 0515 } 0516 } 0517 Resource res = resource; 0518 if (ResourcesCalendar::deleteEvent(*event, res, false)) // don't save calendar after deleting 0519 resources.insert(res); 0520 else 0521 status.appendFailed(i); 0522 0523 #if ENABLE_RTC_WAKE_FROM_SUSPEND 0524 if (id == wakeFromSuspendId) 0525 deleteWakeFromSuspendAlarm = true; 0526 #endif 0527 0528 // Remove "Don't show error messages again" for this alarm 0529 setDontShowErrors(EventId(*event)); 0530 } 0531 0532 if (!resource.isValid() && resources.size() == 1) 0533 resource = *resources.constBegin(); 0534 0535 if (status.failedCount()) 0536 status.setError(status.failedCount() == events.count() ? UPDATE_FAILED : UPDATE_ERROR, status.failedCount()); 0537 if (status.failedCount() < events.count()) 0538 { 0539 QString msg; 0540 for (Resource res : resources) 0541 if (!res.save(&msg)) 0542 { 0543 res.reload(true); // retrieve the events which couldn't be deleted 0544 status.setError(SAVE_FAILED, status.failedCount(), msg); 0545 } 0546 } 0547 if (status.status != UPDATE_OK && msgParent) 0548 displayUpdateError(msgParent, ERR_DELETE, status, showKOrgErr); 0549 0550 #if ENABLE_RTC_WAKE_FROM_SUSPEND 0551 // Remove any wake-from-suspend scheduled for a deleted alarm 0552 if (deleteWakeFromSuspendAlarm && !wakeFromSuspendId.isEmpty()) 0553 cancelRtcWake(msgParent, wakeFromSuspendId); 0554 #endif 0555 0556 return status.status; 0557 } 0558 0559 /****************************************************************************** 0560 * Delete templates from the calendar file. 0561 */ 0562 UpdateResult deleteTemplates(const KAEvent::List& events, QWidget* msgParent) 0563 { 0564 int count = events.count(); 0565 qCDebug(KALARM_LOG) << "KAlarm::deleteTemplates:" << count; 0566 if (!count) 0567 return UpdateResult(UPDATE_OK); 0568 UpdateStatusData status; 0569 QSet<Resource> resources; // resources which events have been deleted from 0570 for (int i = 0, end = events.count(); i < end; ++i) 0571 { 0572 // Update the window lists 0573 // Delete the template from the calendar file 0574 const KAEvent* event = events[i]; 0575 Resource resource; 0576 if (ResourcesCalendar::deleteEvent(*event, resource, false)) // don't save calendar after deleting 0577 resources.insert(resource); 0578 else 0579 status.appendFailed(i); 0580 } 0581 0582 if (status.failedCount()) 0583 status.setError(status.failedCount() == events.count() ? UPDATE_FAILED : UPDATE_ERROR, status.failedCount()); 0584 if (status.failedCount() < events.count()) 0585 { 0586 QString msg; 0587 for (Resource res : resources) 0588 if (!res.save(&msg)) 0589 { 0590 res.reload(true); // retrieve the templates which couldn't be deleted 0591 status.status.message = msg; 0592 status.setError(SAVE_FAILED, status.failedCount(), msg); 0593 } 0594 } 0595 0596 if (status.status != UPDATE_OK && msgParent) 0597 displayUpdateError(msgParent, ERR_TEMPLATE, status); 0598 return status.status; 0599 } 0600 0601 /****************************************************************************** 0602 * Delete an alarm from the display calendar. 0603 */ 0604 void deleteDisplayEvent(const QString& eventID) 0605 { 0606 qCDebug(KALARM_LOG) << "KAlarm::deleteDisplayEvent:" << eventID; 0607 if (DisplayCalendar::open()) 0608 DisplayCalendar::deleteEvent(eventID, true); // save calendar after deleting 0609 } 0610 0611 /****************************************************************************** 0612 * Undelete archived alarms. 0613 * The archive bit is set to ensure that they get re-archived if deleted again. 0614 * Parameters: 0615 * calendar - the active alarms calendar to restore the alarms into, or null 0616 * to use the default way of determining the active alarm calendar. 0617 * ineligibleIndexes - will be filled in with the indexes to any ineligible events. 0618 */ 0619 UpdateResult reactivateEvent(KAEvent& event, Resource& resource, QWidget* msgParent, bool showKOrgErr) 0620 { 0621 QList<int> ids; 0622 QList<KAEvent> events(1, event); 0623 return reactivateEvents(events, ids, resource, msgParent, showKOrgErr); 0624 } 0625 0626 UpdateResult reactivateEvents(QList<KAEvent>& events, QList<int>& ineligibleIndexes, 0627 Resource& resource, QWidget* msgParent, bool showKOrgErr) 0628 { 0629 qCDebug(KALARM_LOG) << "KAlarm::reactivateEvents:" << events.count(); 0630 ineligibleIndexes.clear(); 0631 if (events.isEmpty()) 0632 return UpdateResult(UPDATE_OK); 0633 UpdateStatusData status; 0634 if (!resource.isValid()) 0635 resource = Resources::destination(CalEvent::ACTIVE, msgParent); 0636 if (!resource.isValid()) 0637 { 0638 qCDebug(KALARM_LOG) << "KAlarm::reactivateEvents: No calendar"; 0639 status.setError(UPDATE_FAILED, events.count()); 0640 } 0641 else 0642 { 0643 int count = 0; 0644 const KADateTime now = KADateTime::currentUtcDateTime(); 0645 QList<KAEvent> eventsToDelete; 0646 for (int i = 0, end = events.count(); i < end; ++i) 0647 { 0648 // Delete the event from the archived resource 0649 KAEvent* event = &events[i]; 0650 if (event->category() != CalEvent::ARCHIVED 0651 || !event->occursAfter(now, true)) 0652 { 0653 ineligibleIndexes += i; 0654 continue; 0655 } 0656 ++count; 0657 0658 KAEvent newevent(*event); 0659 KAEvent* const newev = &newevent; 0660 newev->setCategory(CalEvent::ACTIVE); // this changes the event ID 0661 if (newev->recurs() || newev->repetition()) 0662 newev->setNextOccurrence(now); // skip any recurrences in the past 0663 newev->setArchive(); // ensure that it gets re-archived if it is deleted 0664 0665 // Save the event details in the calendar file. 0666 // This converts the event ID. 0667 if (!ResourcesCalendar::addEvent(newevent, resource, msgParent, ResourcesCalendar::UseEventId)) 0668 { 0669 status.appendFailed(i); 0670 continue; 0671 } 0672 if (newev->copyToKOrganizer()) 0673 { 0674 UpdateResult st = sendToKOrganizer(*newev); // tell KOrganizer to show the event 0675 status.korgUpdate(st); 0676 } 0677 0678 if (ResourcesCalendar::event(EventId(*event)).isValid()) // no error if event doesn't exist in archived resource 0679 eventsToDelete.append(*event); 0680 events[i] = newevent; 0681 } 0682 0683 if (status.failedCount()) 0684 status.setError(status.failedCount() == events.count() ? UPDATE_FAILED : UPDATE_ERROR, status.failedCount()); 0685 if (status.failedCount() < events.count()) 0686 { 0687 if (!resource.save(&status.status.message)) 0688 { 0689 resource.reload(true); 0690 status.updateError(SAVE_FAILED, count); 0691 } 0692 else 0693 { 0694 // Delete the events from the archived resources. It's not too 0695 // important whether the resources are saved successfully after 0696 // the deletion, so allow them to be saved automatically. 0697 for (const KAEvent& event : eventsToDelete) 0698 { 0699 Resource res = Resources::resource(event.resourceId()); 0700 if (!ResourcesCalendar::deleteEvent(event, res, false)) 0701 status.setError(UPDATE_ERROR); 0702 } 0703 } 0704 } 0705 } 0706 if (status.status != UPDATE_OK && msgParent) 0707 displayUpdateError(msgParent, ERR_REACTIVATE, status, showKOrgErr); 0708 return status.status; 0709 } 0710 0711 /****************************************************************************** 0712 * Enable or disable alarms. 0713 * The new events will have the same event IDs as the old ones. 0714 */ 0715 UpdateResult enableEvents(QList<KAEvent>& events, bool enable, QWidget* msgParent) 0716 { 0717 qCDebug(KALARM_LOG) << "KAlarm::enableEvents:" << events.count(); 0718 if (events.isEmpty()) 0719 return UpdateResult(UPDATE_OK); 0720 UpdateStatusData status; 0721 #if ENABLE_RTC_WAKE_FROM_SUSPEND 0722 bool deleteWakeFromSuspendAlarm = false; 0723 const QString wakeFromSuspendId = checkRtcWakeConfig().value(0); 0724 #endif 0725 QSet<ResourceId> resourceIds; // resources whose events have been updated 0726 for (int i = 0, end = events.count(); i < end; ++i) 0727 { 0728 KAEvent* event = &events[i]; 0729 if (event->category() == CalEvent::ACTIVE 0730 && enable != event->enabled()) 0731 { 0732 event->setEnabled(enable); 0733 0734 #if ENABLE_RTC_WAKE_FROM_SUSPEND 0735 if (!enable && event->id() == wakeFromSuspendId) 0736 deleteWakeFromSuspendAlarm = true; 0737 #endif 0738 0739 // Update the event in the calendar file 0740 const KAEvent newev = ResourcesCalendar::updateEvent(*event); 0741 if (!newev.isValid()) 0742 { 0743 qCCritical(KALARM_LOG) << "KAlarm::enableEvents: Error updating event in calendar:" << event->id(); 0744 status.appendFailed(i); 0745 } 0746 else 0747 { 0748 resourceIds.insert(event->resourceId()); 0749 ResourcesCalendar::disabledChanged(newev); 0750 0751 // If we're disabling a display alarm, close any message display 0752 if (!enable && (event->actionTypes() & KAEvent::Action::Display)) 0753 { 0754 MessageDisplay* win = MessageDisplay::findEvent(EventId(*event)); 0755 delete win; 0756 } 0757 } 0758 } 0759 } 0760 0761 if (status.failedCount()) 0762 status.setError(status.failedCount() == events.count() ? UPDATE_FAILED : UPDATE_ERROR, status.failedCount()); 0763 if (status.failedCount() < events.count()) 0764 { 0765 QString msg; 0766 for (ResourceId id : resourceIds) 0767 { 0768 Resource res = Resources::resource(id); 0769 if (!res.save(&msg)) 0770 { 0771 // Don't reload resource after failed save. It's better to 0772 // keep the new enabled status of the alarms at least until 0773 // KAlarm is restarted. 0774 status.setError(SAVE_FAILED, status.failedCount(), msg); 0775 } 0776 } 0777 } 0778 if (status.status != UPDATE_OK && msgParent) 0779 displayUpdateError(msgParent, ERR_ADD, status); 0780 0781 #if ENABLE_RTC_WAKE_FROM_SUSPEND 0782 // Remove any wake-from-suspend scheduled for a disabled alarm 0783 if (deleteWakeFromSuspendAlarm && !wakeFromSuspendId.isEmpty()) 0784 cancelRtcWake(msgParent, wakeFromSuspendId); 0785 #endif 0786 0787 return status.status; 0788 } 0789 0790 /****************************************************************************** 0791 * This method must only be called from the main KAlarm queue processing loop, 0792 * to prevent asynchronous calendar operations interfering with one another. 0793 * 0794 * Purge all archived events from the default archived alarm resource whose end 0795 * time is longer ago than 'purgeDays'. All events are deleted if 'purgeDays' is 0796 * zero. 0797 */ 0798 void purgeArchive(int purgeDays) 0799 { 0800 if (purgeDays < 0) 0801 return; 0802 qCDebug(KALARM_LOG) << "KAlarm::purgeArchive:" << purgeDays; 0803 const QDate cutoff = KADateTime::currentLocalDate().addDays(-purgeDays); 0804 const Resource resource = Resources::getStandard(CalEvent::ARCHIVED, true); 0805 if (!resource.isValid()) 0806 return; 0807 QList<KAEvent> events = ResourcesCalendar::events(resource); 0808 for (int i = 0; i < events.count(); ) 0809 { 0810 if (purgeDays && events.at(i).createdDateTime().date() >= cutoff) 0811 events.remove(i); 0812 else 0813 ++i; 0814 } 0815 if (!events.isEmpty()) 0816 ResourcesCalendar::purgeEvents(events); // delete the events and save the calendar 0817 } 0818 0819 /****************************************************************************** 0820 * Return whether an event is read-only. 0821 */ 0822 bool eventReadOnly(const QString& eventId) 0823 { 0824 KAEvent event; 0825 const Resource resource = Resources::resourceForEvent(eventId, event); 0826 return !event.isValid() || event.isReadOnly() 0827 || !resource.isWritable(event.category()); 0828 } 0829 0830 /****************************************************************************** 0831 * Display an error message about an error when saving an event. 0832 * If 'model' is non-null, the AlarmListModel* which it points to is used; if 0833 * that is null, it is created. 0834 */ 0835 QList<KAEvent> getSortedActiveEvents(QObject* parent, AlarmListModel** model) 0836 { 0837 AlarmListModel* mdl = nullptr; 0838 if (!model) 0839 model = &mdl; 0840 if (!*model) 0841 { 0842 *model = DataModel::createAlarmListModel(parent); 0843 (*model)->setEventTypeFilter(CalEvent::ACTIVE); 0844 (*model)->sort(AlarmListModel::TimeColumn); 0845 } 0846 int count = (*model)->rowCount(); 0847 QList<KAEvent> result; 0848 result.reserve(count); 0849 for (int i = 0; i < count; ++i) 0850 { 0851 const KAEvent event = (*model)->event(i); 0852 if (event.enabled() && !event.expired()) 0853 result += event; 0854 } 0855 return result; 0856 } 0857 0858 /****************************************************************************** 0859 * Import alarms from an external calendar and merge them into KAlarm's calendar. 0860 * The alarms are given new unique event IDs. 0861 * Parameters: parent = parent widget for error message boxes 0862 * Reply = true if all alarms in the calendar were successfully imported 0863 * = false if any alarms failed to be imported. 0864 */ 0865 bool importAlarms(Resource& resource, QWidget* parent) 0866 { 0867 qCDebug(KALARM_LOG) << "KAlarm::importAlarms" << resource.displayId(); 0868 // Use KFileCustomDialog to allow files' mime types to be determined by 0869 // both file name and content, instead of QFileDialog which only looks at 0870 // files' names. This is needed in particular when importing an old KAlarm 0871 // calendar directory, in order to list the calendar files within it, since 0872 // each calendar file name is simply the UID of the event within it, without 0873 // a .ics extension. 0874 AutoQPointer<KFileCustomDialog> dlg = new KFileCustomDialog(lastImportUrl, parent); 0875 dlg->setWindowTitle(i18nc("@title:window", "Import Calendar Files")); 0876 KFileWidget* widget = dlg->fileWidget(); 0877 widget->setOperationMode(KFileWidget::Opening); 0878 widget->setMode(KFile::Files | KFile::ExistingOnly); 0879 widget->setFilters({KFileFilter::fromMimeType(QStringLiteral("text/calendar"))}); 0880 dlg->setWindowModality(Qt::WindowModal); 0881 dlg->exec(); 0882 if (!dlg) 0883 return false; 0884 const QList<QUrl> urls = widget->selectedUrls(); 0885 if (urls.isEmpty()) 0886 return false; 0887 lastImportUrl = urls[0].adjusted(QUrl::RemoveFilename); 0888 0889 const CalEvent::Types alarmTypes = resource.isValid() ? resource.alarmTypes() : CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE; 0890 0891 // Read all the selected calendar files and extract their alarms. 0892 QHash<CalEvent::Type, QList<KAEvent>> events; 0893 for (const QUrl& url : urls) 0894 { 0895 if (!url.isValid()) 0896 { 0897 qCDebug(KALARM_LOG) << "KAlarm::importAlarms: Invalid URL"; 0898 continue; 0899 } 0900 qCDebug(KALARM_LOG) << "KAlarm::importAlarms:" << url.toDisplayString(); 0901 importCalendarFile(url, alarmTypes, true, parent, events); 0902 } 0903 if (events.isEmpty()) 0904 return false; 0905 0906 // Add the alarms to the destination resource. 0907 bool success = true; 0908 for (auto it = events.constBegin(); it != events.constEnd(); ++it) 0909 { 0910 Resource res; 0911 if (resource.isValid()) 0912 res = resource; 0913 else 0914 res = Resources::destination(it.key()); 0915 0916 for (const KAEvent& event : std::as_const(it.value())) 0917 { 0918 if (!res.addEvent(event)) 0919 success = false; 0920 } 0921 } 0922 return success; 0923 } 0924 0925 /****************************************************************************** 0926 * Export all selected alarms to an external calendar. 0927 * The alarms are given new unique event IDs. 0928 * Parameters: parent = parent widget for error message boxes 0929 * Reply = true if all alarms in the calendar were successfully exported 0930 * = false if any alarms failed to be exported. 0931 */ 0932 bool exportAlarms(const QList<KAEvent>& events, QWidget* parent) 0933 { 0934 bool append; 0935 QString file = FileDialog::getSaveFileName(lastExportUrl, 0936 {KFileFilter(i18nc("@item:inlistbox File type selection filter", "Calendar Files"), {QStringLiteral("*.ics")}, {})}, 0937 parent, i18nc("@title:window", "Choose Export Calendar"), 0938 &append); 0939 if (file.isEmpty()) 0940 return false; 0941 const QUrl url = QUrl::fromLocalFile(file); 0942 if (!url.isValid()) 0943 { 0944 qCDebug(KALARM_LOG) << "KAlarm::exportAlarms: Invalid URL" << url; 0945 return false; 0946 } 0947 lastExportUrl = url.adjusted(QUrl::RemoveFilename); 0948 qCDebug(KALARM_LOG) << "KAlarm::exportAlarms:" << url.toDisplayString(); 0949 0950 MemoryCalendar::Ptr calendar(new MemoryCalendar(Preferences::timeSpecAsZone())); 0951 FileStorage::Ptr calStorage(new FileStorage(calendar, file)); 0952 if (append && !calStorage->load()) 0953 { 0954 auto statJob = KIO::stat(url, KIO::StatJob::SourceSide, KIO::StatDetail::StatDefaultDetails); 0955 KJobWidgets::setWindow(statJob, parent); 0956 statJob->exec(); 0957 KFileItem fi(statJob->statResult(), url); 0958 if (fi.size()) 0959 { 0960 qCCritical(KALARM_LOG) << "KAlarm::exportAlarms: Error loading calendar file" << file << "for append"; 0961 KAMessageBox::error(MainWindow::mainMainWindow(), 0962 xi18nc("@info", "Error loading calendar to append to:<nl/><filename>%1</filename>", url.toDisplayString())); 0963 return false; 0964 } 0965 } 0966 KACalendar::setKAlarmVersion(calendar); 0967 0968 // Add the alarms to the calendar 0969 bool success = true; 0970 bool exported = false; 0971 for (const KAEvent& event : events) 0972 { 0973 Event::Ptr kcalEvent(new Event); 0974 const CalEvent::Type type = event.category(); 0975 const QString id = CalEvent::uid(kcalEvent->uid(), type); 0976 kcalEvent->setUid(id); 0977 event.updateKCalEvent(kcalEvent, KAEvent::UidAction::Ignore); 0978 if (calendar->addEvent(kcalEvent)) 0979 exported = true; 0980 else 0981 success = false; 0982 } 0983 0984 if (exported) 0985 { 0986 // One or more alarms have been exported to the calendar. 0987 // Save the calendar to file. 0988 QTemporaryFile* tempFile = nullptr; 0989 bool local = url.isLocalFile(); 0990 if (!local) 0991 { 0992 tempFile = new QTemporaryFile; 0993 file = tempFile->fileName(); 0994 } 0995 calStorage->setFileName(file); 0996 calStorage->setSaveFormat(new ICalFormat); 0997 if (!calStorage->save()) 0998 { 0999 qCCritical(KALARM_LOG) << "KAlarm::exportAlarms:" << file << ": failed"; 1000 KAMessageBox::error(MainWindow::mainMainWindow(), 1001 xi18nc("@info", "Failed to save new calendar to:<nl/><filename>%1</filename>", url.toDisplayString())); 1002 success = false; 1003 } 1004 else if (!local) 1005 { 1006 QFile qFile(file); 1007 qFile.open(QIODevice::ReadOnly); 1008 auto uploadJob = KIO::storedPut(&qFile, url, -1); 1009 KJobWidgets::setWindow(uploadJob, parent); 1010 if (!uploadJob->exec()) 1011 { 1012 qCCritical(KALARM_LOG) << "KAlarm::exportAlarms:" << file << ": upload failed"; 1013 KAMessageBox::error(MainWindow::mainMainWindow(), 1014 xi18nc("@info", "Cannot upload new calendar to:<nl/><filename>%1</filename>", url.toDisplayString())); 1015 success = false; 1016 } 1017 } 1018 delete tempFile; 1019 } 1020 return success; 1021 } 1022 1023 /****************************************************************************** 1024 * Display an error message corresponding to a specified alarm update error code. 1025 */ 1026 void displayKOrgUpdateError(QWidget* parent, UpdateError code, const UpdateResult& korgError, int nAlarms) 1027 { 1028 QString errmsg; 1029 switch (code) 1030 { 1031 case ERR_ADD: 1032 case ERR_REACTIVATE: 1033 errmsg = (nAlarms > 1) ? i18nc("@info", "Unable to show alarms in KOrganizer") 1034 : i18nc("@info", "Unable to show alarm in KOrganizer"); 1035 break; 1036 case ERR_MODIFY: 1037 errmsg = i18nc("@info", "Unable to update alarm in KOrganizer"); 1038 break; 1039 case ERR_DELETE: 1040 errmsg = (nAlarms > 1) ? i18nc("@info", "Unable to delete alarms from KOrganizer") 1041 : i18nc("@info", "Unable to delete alarm from KOrganizer"); 1042 break; 1043 case ERR_TEMPLATE: 1044 return; 1045 } 1046 bool showDetail = !korgError.message.isEmpty(); 1047 QString msg; 1048 switch (korgError.status) 1049 { 1050 case UPDATE_KORG_ERRINIT: 1051 msg = xi18nc("@info", "<para>%1</para><para>(Could not start KOrganizer)</para>", errmsg); 1052 break; 1053 case UPDATE_KORG_ERRSTART: 1054 msg = xi18nc("@info", "<para>%1</para><para>(KOrganizer not fully started)</para>", errmsg); 1055 break; 1056 case UPDATE_KORG_ERR: 1057 msg = xi18nc("@info", "<para>%1</para><para>(Error communicating with KOrganizer)</para>", errmsg); 1058 break; 1059 default: 1060 msg = errmsg; 1061 showDetail = false; 1062 break; 1063 } 1064 if (showDetail) 1065 KAMessageBox::detailedError(parent, msg, korgError.message); 1066 else 1067 KAMessageBox::error(parent, msg); 1068 } 1069 1070 /****************************************************************************** 1071 * Execute a New Alarm dialog for the specified alarm type. 1072 */ 1073 void editNewAlarm(EditAlarmDlg::Type type, QWidget* parent) 1074 { 1075 editNewAlarm(type, QDate(), parent); 1076 } 1077 void editNewAlarm(EditAlarmDlg::Type type, const QDate& startDate, QWidget* parent) 1078 { 1079 EditAlarmDlg* editDlg = EditAlarmDlg::create(false, type, parent); 1080 if (editDlg) 1081 execNewAlarmDlg(editDlg, startDate); 1082 } 1083 1084 /****************************************************************************** 1085 * Execute a New Alarm dialog for the specified alarm type. 1086 */ 1087 void editNewAlarm(KAEvent::SubAction action, QWidget* parent, const AlarmText* text) 1088 { 1089 bool setAction = false; 1090 EditAlarmDlg::Type type; 1091 switch (action) 1092 { 1093 case KAEvent::SubAction::Message: 1094 case KAEvent::SubAction::File: 1095 type = EditAlarmDlg::DISPLAY; 1096 setAction = true; 1097 break; 1098 case KAEvent::SubAction::Command: 1099 type = EditAlarmDlg::COMMAND; 1100 break; 1101 case KAEvent::SubAction::Email: 1102 type = EditAlarmDlg::EMAIL; 1103 break; 1104 case KAEvent::SubAction::Audio: 1105 type = EditAlarmDlg::AUDIO; 1106 break; 1107 default: 1108 return; 1109 } 1110 EditAlarmDlg* editDlg = EditAlarmDlg::create(false, type, parent); 1111 if (!editDlg) 1112 return; 1113 if (setAction || text) 1114 editDlg->setAction(action, *text); 1115 execNewAlarmDlg(editDlg); 1116 } 1117 1118 /****************************************************************************** 1119 * Execute a New Alarm dialog, optionally either presetting it to the supplied 1120 * event, or setting the action and text. 1121 */ 1122 void editNewAlarm(const KAEvent& preset, QWidget* parent) 1123 { 1124 editNewAlarm(preset, QDate(), parent); 1125 } 1126 void editNewAlarm(const KAEvent& preset, const QDate& startDate, QWidget* parent) 1127 { 1128 EditAlarmDlg* editDlg = EditAlarmDlg::create(false, preset, true, parent); 1129 if (editDlg) 1130 execNewAlarmDlg(editDlg, startDate); 1131 } 1132 1133 /****************************************************************************** 1134 * Common code for editNewAlarm() variants. 1135 */ 1136 void execNewAlarmDlg(EditAlarmDlg* editDlg, const QDate& startDate) 1137 { 1138 if (startDate.isValid()) 1139 { 1140 KADateTime defaultTime = editDlg->time(); 1141 defaultTime.setDate(startDate); 1142 editDlg->setTime(defaultTime); 1143 } 1144 1145 // Create a PrivateNewAlarmDlg parented by editDlg. 1146 // It will be deleted when editDlg is closed. 1147 new PrivateNewAlarmDlg(editDlg); 1148 editDlg->show(); 1149 editDlg->raise(); 1150 editDlg->activateWindow(); 1151 } 1152 1153 PrivateNewAlarmDlg::PrivateNewAlarmDlg(EditAlarmDlg* dlg) 1154 : QObject(dlg) 1155 { 1156 connect(dlg, &QDialog::accepted, this, &PrivateNewAlarmDlg::okClicked); 1157 connect(dlg, &QDialog::rejected, this, &PrivateNewAlarmDlg::cancelClicked); 1158 } 1159 1160 /****************************************************************************** 1161 * Called when the dialogue is accepted (e.g. by clicking the OK button). 1162 * Creates the event specified in the instance's dialogue. 1163 */ 1164 void PrivateNewAlarmDlg::okClicked() 1165 { 1166 accept(static_cast<EditAlarmDlg*>(parent())); 1167 } 1168 1169 /****************************************************************************** 1170 * Creates the event specified in a given dialogue. 1171 */ 1172 void PrivateNewAlarmDlg::accept(EditAlarmDlg* editDlg) 1173 { 1174 KAEvent event; 1175 Resource resource; 1176 editDlg->getEvent(event, resource); 1177 1178 // Add the alarm to the displayed lists and to the calendar file 1179 const UpdateResult status = addEvent(event, resource, editDlg); 1180 switch (status.status) 1181 { 1182 case UPDATE_FAILED: 1183 case SAVE_FAILED: 1184 return; 1185 case UPDATE_KORG_ERR: 1186 case UPDATE_KORG_ERRINIT: 1187 case UPDATE_KORG_ERRSTART: 1188 case UPDATE_KORG_FUNCERR: 1189 displayKOrgUpdateError(editDlg, ERR_ADD, status); 1190 break; 1191 default: 1192 break; 1193 } 1194 Undo::saveAdd(event, resource); 1195 1196 outputAlarmWarnings(editDlg, &event); 1197 1198 editDlg->deleteLater(); 1199 } 1200 1201 /****************************************************************************** 1202 * Called when the dialogue is rejected (e.g. by clicking the Cancel button). 1203 */ 1204 void PrivateNewAlarmDlg::cancelClicked() 1205 { 1206 static_cast<EditAlarmDlg*>(parent())->deleteLater(); 1207 } 1208 1209 /****************************************************************************** 1210 * Display the alarm edit dialog to edit a new alarm, preset with a template. 1211 */ 1212 bool editNewAlarm(const QString& templateName, QWidget* parent) 1213 { 1214 if (!templateName.isEmpty()) 1215 { 1216 KAEvent templateEvent = ResourcesCalendar::templateEvent(templateName); 1217 if (templateEvent.isValid()) 1218 { 1219 editNewAlarm(templateEvent, parent); 1220 return true; 1221 } 1222 qCWarning(KALARM_LOG) << "KAlarm::editNewAlarm:" << templateName << ": template not found"; 1223 } 1224 return false; 1225 } 1226 1227 /****************************************************************************** 1228 * Create a new template. 1229 */ 1230 void editNewTemplate(EditAlarmDlg::Type type, QWidget* parent) 1231 { 1232 ::editNewTemplate(type, KAEvent(), parent); 1233 } 1234 1235 /****************************************************************************** 1236 * Create a new template, based on an existing event or template. 1237 */ 1238 void editNewTemplate(const KAEvent& preset, QWidget* parent) 1239 { 1240 ::editNewTemplate(EditAlarmDlg::Type(0), preset, parent); 1241 } 1242 1243 #if ENABLE_RTC_WAKE_FROM_SUSPEND 1244 /****************************************************************************** 1245 * Check the config as to whether there is a wake-on-suspend alarm pending, and 1246 * if so, delete it from the config if it has expired. 1247 * If 'checkExists' is true, the config entry will only be returned if the 1248 * event exists. 1249 * Reply = config entry: [0] = event's resource ID, 1250 * [1] = event ID, 1251 * [2] = trigger time (int64 seconds since epoch). 1252 * = empty list if none or expired. 1253 */ 1254 QStringList checkRtcWakeConfig(bool checkEventExists) 1255 { 1256 KConfigGroup config(KSharedConfig::openConfig(), GENERAL_GROUP); 1257 const QStringList params = config.readEntry("RtcWake", QStringList()); 1258 if (params.count() == 3 && params[2].toLongLong() > KADateTime::currentUtcDateTime().toSecsSinceEpoch()) 1259 { 1260 if (checkEventExists) 1261 { 1262 Resource resource = Resources::resource(params[0].toLongLong()); 1263 if (!resource.event(params[1]).isValid()) 1264 return {}; 1265 } 1266 return params; // config entry is valid 1267 } 1268 if (!params.isEmpty()) 1269 { 1270 config.deleteEntry("RtcWake"); // delete the expired config entry 1271 config.sync(); 1272 } 1273 return {}; 1274 } 1275 1276 /****************************************************************************** 1277 * Delete any wake-on-suspend alarm from the config. 1278 */ 1279 void deleteRtcWakeConfig() 1280 { 1281 KConfigGroup config(KSharedConfig::openConfig(), GENERAL_GROUP); 1282 config.deleteEntry("RtcWake"); 1283 config.sync(); 1284 } 1285 1286 /****************************************************************************** 1287 * Delete any wake-on-suspend alarm, optionally only for a specified event. 1288 */ 1289 void cancelRtcWake(QWidget* msgParent, const QString& eventId) 1290 { 1291 const QStringList wakeup = checkRtcWakeConfig(); 1292 if (!wakeup.isEmpty() && (eventId.isEmpty() || wakeup[0] == eventId)) 1293 { 1294 Private::instance()->mMsgParent = msgParent ? msgParent : MainWindow::mainMainWindow(); 1295 QTimer::singleShot(0, Private::instance(), &Private::cancelRtcWake); //NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) 1296 } 1297 } 1298 1299 /****************************************************************************** 1300 * Delete any wake-on-suspend alarm. 1301 */ 1302 void Private::cancelRtcWake() 1303 { 1304 // setRtcWakeTime will only work with a parent window specified 1305 setRtcWakeTime(0, mMsgParent); 1306 deleteRtcWakeConfig(); 1307 KAMessageBox::information(mMsgParent, i18nc("info", "The scheduled Wake from Suspend has been cancelled.")); 1308 } 1309 1310 /****************************************************************************** 1311 * Set the wakeup time for the system. 1312 * Set 'triggerTime' to zero to cancel the wakeup. 1313 * Reply = true if successful. 1314 */ 1315 bool setRtcWakeTime(unsigned triggerTime, QWidget* parent) 1316 { 1317 QVariantMap args; 1318 args[QStringLiteral("time")] = triggerTime; 1319 KAuth::Action action(QStringLiteral("org.kde.kalarm.rtcwake.settimer")); 1320 action.setHelperId(QStringLiteral("org.kde.kalarm.rtcwake")); 1321 parent->window()->winId(); 1322 action.setParentWindow(parent->window()->windowHandle()); 1323 action.setArguments(args); 1324 KAuth::ExecuteJob* job = action.execute(); 1325 if (!job->exec()) 1326 { 1327 QString errmsg = job->errorString(); 1328 qCDebug(KALARM_LOG) << "KAlarm::setRtcWakeTime: Error code=" << job->error() << errmsg; 1329 if (errmsg.isEmpty()) 1330 { 1331 int errcode = job->error(); 1332 switch (errcode) 1333 { 1334 case KAuth::ActionReply::AuthorizationDeniedError: 1335 case KAuth::ActionReply::UserCancelledError: 1336 qCDebug(KALARM_LOG) << "KAlarm::setRtcWakeTime: Authorization error:" << errcode; 1337 return false; // the user should already know about this 1338 default: 1339 break; 1340 } 1341 errmsg = i18nc("@info", "Error obtaining authorization (%1)", errcode); 1342 } 1343 KAMessageBox::information(parent, errmsg); 1344 return false; 1345 } 1346 return true; 1347 } 1348 #endif // ENABLE_RTC_WAKE_FROM_SUSPEND 1349 1350 } // namespace KAlarm 1351 namespace 1352 { 1353 1354 /****************************************************************************** 1355 * Update an event in its resource. 1356 */ 1357 KAlarm::UpdateResult updateEvent(KAEvent& event, KAlarm::UpdateError err, QWidget* msgParent, bool saveIfReadOnly) 1358 { 1359 UpdateStatusData status; 1360 const KAEvent newEvent = ResourcesCalendar::updateEvent(event, saveIfReadOnly); 1361 if (!newEvent.isValid()) 1362 status.status = KAlarm::UPDATE_FAILED; 1363 else 1364 { 1365 Resource resource = Resources::resource(event.resourceId()); 1366 if ((saveIfReadOnly || !resource.readOnly()) 1367 && !resource.save(&status.status.message)) 1368 { 1369 resource.reload(true); // retrieve the pre-update version of the event 1370 status.status.status = KAlarm::SAVE_FAILED; 1371 } 1372 } 1373 if (status.status != KAlarm::UPDATE_OK) 1374 { 1375 if (msgParent) 1376 displayUpdateError(msgParent, err, status); 1377 } 1378 return status.status; 1379 } 1380 1381 /****************************************************************************** 1382 * Create a new template. 1383 * 'preset' is non-null to base it on an existing event or template; otherwise, 1384 * the alarm type is set to 'type'. 1385 */ 1386 void editNewTemplate(EditAlarmDlg::Type type, const KAEvent& preset, QWidget* parent) 1387 { 1388 if (Resources::enabledResources(CalEvent::TEMPLATE, true).isEmpty()) 1389 { 1390 KAMessageBox::error(parent, i18nc("@info", "You must enable a template calendar to save the template in")); 1391 return; 1392 } 1393 // Use AutoQPointer to guard against crash on application exit while 1394 // the dialogue is still open. It prevents double deletion (both on 1395 // deletion of parent, and on return from this function). 1396 AutoQPointer<EditAlarmDlg> editDlg; 1397 if (preset.isValid()) 1398 editDlg = EditAlarmDlg::create(true, preset, true, parent); 1399 else 1400 editDlg = EditAlarmDlg::create(true, type, parent); 1401 if (editDlg && editDlg->exec() == QDialog::Accepted) 1402 { 1403 KAEvent event; 1404 Resource resource; 1405 editDlg->getEvent(event, resource); 1406 1407 // Add the template to the displayed lists and to the calendar file 1408 KAlarm::addTemplate(event, resource, editDlg); 1409 Undo::saveAdd(event, resource); 1410 } 1411 } 1412 1413 } // namespace 1414 namespace KAlarm 1415 { 1416 1417 /****************************************************************************** 1418 * Open the Edit Alarm dialog to edit the specified alarm. 1419 * If the alarm is read-only or archived, the dialog is opened read-only. 1420 */ 1421 void editAlarm(KAEvent& event, QWidget* parent) 1422 { 1423 if (event.expired() || eventReadOnly(event.id())) 1424 { 1425 viewAlarm(event, parent); 1426 return; 1427 } 1428 const EventId id(event); 1429 // Use AutoQPointer to guard against crash on application exit while 1430 // the dialogue is still open. It prevents double deletion (both on 1431 // deletion of parent, and on return from this function). 1432 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(false, event, false, parent, EditAlarmDlg::RES_USE_EVENT_ID); 1433 if (editDlg && editDlg->exec() == QDialog::Accepted) 1434 { 1435 if (!ResourcesCalendar::event(id).isValid()) 1436 { 1437 // Event has been deleted while the user was editing the alarm, 1438 // so treat it as a new alarm. 1439 PrivateNewAlarmDlg().accept(editDlg); 1440 return; 1441 } 1442 KAEvent newEvent; 1443 Resource resource; 1444 bool changeDeferral = !editDlg->getEvent(newEvent, resource); 1445 1446 // Update the event in the displays and in the calendar file 1447 const Undo::Event undo(event, resource); 1448 if (changeDeferral) 1449 { 1450 // The only change has been to an existing deferral 1451 if (updateEvent(newEvent, editDlg, true, true) != UPDATE_OK) // keep the same event ID 1452 return; // failed to save event 1453 } 1454 else 1455 { 1456 const UpdateResult status = modifyEvent(event, newEvent, editDlg); 1457 if (status.status != UPDATE_OK && status.status <= UPDATE_KORG_ERR) 1458 displayKOrgUpdateError(editDlg, ERR_MODIFY, status); 1459 } 1460 Undo::saveEdit(undo, newEvent); 1461 1462 outputAlarmWarnings(editDlg, &newEvent); 1463 } 1464 } 1465 1466 /****************************************************************************** 1467 * Display the alarm edit dialog to edit the alarm with the specified ID. 1468 * An error occurs if the alarm is not found, if there is more than one alarm 1469 * with the same ID, or if it is read-only or expired. 1470 */ 1471 bool editAlarmById(const EventId& id, QWidget* parent) 1472 { 1473 const QString eventID(id.eventId()); 1474 KAEvent event = ResourcesCalendar::event(id, true); 1475 if (!event.isValid()) 1476 { 1477 if (id.resourceId() != -1) 1478 qCWarning(KALARM_LOG) << "KAlarm::editAlarmById: Event ID not found, or duplicated:" << eventID; 1479 else 1480 qCWarning(KALARM_LOG) << "KAlarm::editAlarmById: Event ID not found:" << eventID; 1481 return false; 1482 } 1483 if (eventReadOnly(event.id())) 1484 { 1485 qCCritical(KALARM_LOG) << "KAlarm::editAlarmById:" << eventID << ": read-only"; 1486 return false; 1487 } 1488 switch (event.category()) 1489 { 1490 case CalEvent::ACTIVE: 1491 case CalEvent::TEMPLATE: 1492 break; 1493 default: 1494 qCCritical(KALARM_LOG) << "KAlarm::editAlarmById:" << eventID << ": event not active or template"; 1495 return false; 1496 } 1497 editAlarm(event, parent); 1498 return true; 1499 } 1500 1501 /****************************************************************************** 1502 * Open the Edit Alarm dialog to edit the specified template. 1503 * If the template is read-only, the dialog is opened read-only. 1504 */ 1505 void editTemplate(const KAEvent& event, QWidget* parent) 1506 { 1507 if (eventReadOnly(event.id())) 1508 { 1509 // The template is read-only, so make the dialogue read-only. 1510 // Use AutoQPointer to guard against crash on application exit while 1511 // the dialogue is still open. It prevents double deletion (both on 1512 // deletion of parent, and on return from this function). 1513 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(true, event, false, parent, EditAlarmDlg::RES_PROMPT, true); 1514 if (editDlg) 1515 editDlg->exec(); 1516 return; 1517 } 1518 // Use AutoQPointer to guard against crash on application exit while 1519 // the dialogue is still open. It prevents double deletion (both on 1520 // deletion of parent, and on return from this function). 1521 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(true, event, false, parent, EditAlarmDlg::RES_USE_EVENT_ID); 1522 if (editDlg && editDlg->exec() == QDialog::Accepted) 1523 { 1524 KAEvent newEvent; 1525 Resource resource; 1526 editDlg->getEvent(newEvent, resource); 1527 const QString id = event.id(); 1528 newEvent.setEventId(id); 1529 newEvent.setResourceId(event.resourceId()); 1530 1531 // Update the event in the displays and in the calendar file 1532 const Undo::Event undo(event, resource); 1533 updateTemplate(newEvent, editDlg); 1534 Undo::saveEdit(undo, newEvent); 1535 } 1536 } 1537 1538 /****************************************************************************** 1539 * Open the Edit Alarm dialog to view the specified alarm (read-only). 1540 */ 1541 void viewAlarm(const KAEvent& event, QWidget* parent) 1542 { 1543 // Use AutoQPointer to guard against crash on application exit while 1544 // the dialogue is still open. It prevents double deletion (both on 1545 // deletion of parent, and on return from this function). 1546 AutoQPointer<EditAlarmDlg> editDlg = EditAlarmDlg::create(false, event, false, parent, EditAlarmDlg::RES_PROMPT, true); 1547 if (editDlg) 1548 editDlg->exec(); 1549 } 1550 1551 /****************************************************************************** 1552 * Called when OK is clicked in the alarm edit dialog invoked by the Edit button 1553 * in an alarm message window. 1554 * Updates the alarm calendar and closes the dialog. 1555 */ 1556 void updateEditedAlarm(EditAlarmDlg* editDlg, KAEvent& event, Resource& resource) 1557 { 1558 qCDebug(KALARM_LOG) << "KAlarm::updateEditedAlarm"; 1559 KAEvent newEvent; 1560 Resource res; 1561 editDlg->getEvent(newEvent, res); 1562 1563 // Update the displayed lists and the calendar file 1564 UpdateResult status; 1565 if (ResourcesCalendar::event(EventId(event)).isValid()) 1566 { 1567 // The old alarm hasn't expired yet, so replace it 1568 const Undo::Event undo(event, resource); 1569 status = modifyEvent(event, newEvent, editDlg); 1570 Undo::saveEdit(undo, newEvent); 1571 } 1572 else 1573 { 1574 // The old event has expired, so simply create a new one 1575 status = addEvent(newEvent, resource, editDlg); 1576 Undo::saveAdd(newEvent, resource); 1577 } 1578 1579 if (status.status != UPDATE_OK && status.status <= UPDATE_KORG_ERR) 1580 displayKOrgUpdateError(editDlg, ERR_MODIFY, status); 1581 outputAlarmWarnings(editDlg, &newEvent); 1582 1583 editDlg->close(); 1584 } 1585 1586 /****************************************************************************** 1587 * Returns a list of all alarm templates. 1588 * If shell commands are disabled, command alarm templates are omitted. 1589 */ 1590 QList<KAEvent> templateList() 1591 { 1592 QList<KAEvent> templates; 1593 const bool includeCmdAlarms = ShellProcess::authorised(); 1594 const QList<KAEvent> events = ResourcesCalendar::events(CalEvent::TEMPLATE); 1595 for (const KAEvent& event : events) 1596 { 1597 if (includeCmdAlarms || !(event.actionTypes() & KAEvent::Action::Command)) 1598 templates.append(event); 1599 } 1600 return templates; 1601 } 1602 1603 /****************************************************************************** 1604 * To be called after an alarm has been edited. 1605 * Prompt the user to re-enable alarms if they are currently disabled, and if 1606 * it's an email alarm, warn if no 'From' email address is configured. 1607 */ 1608 void outputAlarmWarnings(QWidget* parent, const KAEvent* event) 1609 { 1610 if (event && event->actionTypes() == KAEvent::Action::Email 1611 && Preferences::emailAddress().isEmpty()) 1612 KAMessageBox::information(parent, xi18nc("@info Please set the 'From' email address...", 1613 "<para>%1</para><para>Please set it in the Configuration dialog.</para>", KAMail::i18n_NeedFromEmailAddress())); 1614 1615 if (!theApp()->alarmsEnabled()) 1616 { 1617 if (KAMessageBox::warningYesNo(parent, xi18nc("@info", "<para>Alarms are currently disabled.</para><para>Do you want to enable alarms now?</para>"), 1618 QString(), KGuiItem(i18nc("@action:button", "Enable")), KGuiItem(i18nc("@action:button", "Keep Disabled")), 1619 QStringLiteral("EditEnableAlarms")) 1620 == KMessageBox::ButtonCode::PrimaryAction) 1621 theApp()->setAlarmsEnabled(true); 1622 } 1623 } 1624 1625 /****************************************************************************** 1626 * Reload the calendar. 1627 */ 1628 void refreshAlarms() 1629 { 1630 qCDebug(KALARM_LOG) << "KAlarm::refreshAlarms"; 1631 if (!refreshAlarmsQueued) 1632 { 1633 refreshAlarmsQueued = true; 1634 theApp()->processQueue(); 1635 } 1636 } 1637 1638 /****************************************************************************** 1639 * This method must only be called from the main KAlarm queue processing loop, 1640 * to prevent asynchronous calendar operations interfering with one another. 1641 * 1642 * If refreshAlarms() has been called, reload the calendars. 1643 */ 1644 void refreshAlarmsIfQueued() 1645 { 1646 if (refreshAlarmsQueued) 1647 { 1648 qCDebug(KALARM_LOG) << "KAlarm::refreshAlarmsIfQueued"; 1649 QList<Resource> resources = Resources::enabledResources(); 1650 for (Resource& resource : resources) 1651 resource.reload(); 1652 1653 // Close any message displays for alarms which are now disabled 1654 const QList<KAEvent> events = ResourcesCalendar::events(CalEvent::ACTIVE); 1655 for (const KAEvent& event : events) 1656 { 1657 if (!event.enabled() && (event.actionTypes() & KAEvent::Action::Display)) 1658 { 1659 MessageDisplay* win = MessageDisplay::findEvent(EventId(event)); 1660 delete win; 1661 } 1662 } 1663 1664 MainWindow::refresh(); 1665 refreshAlarmsQueued = false; 1666 } 1667 } 1668 1669 /****************************************************************************** 1670 * The "Don't show again" option for error messages is personal to the user on a 1671 * particular computer. For example, he may want to inhibit error messages only 1672 * on his laptop. So the status is not stored in the alarm calendar, but in the 1673 * user's local KAlarm data directory. 1674 ******************************************************************************/ 1675 1676 /****************************************************************************** 1677 * Return the Don't-show-again error message tags set for a specified alarm ID. 1678 */ 1679 QStringList dontShowErrors(const EventId& eventId) 1680 { 1681 if (eventId.isEmpty()) 1682 return {}; 1683 KConfig config(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + ALARM_OPTS_FILE); 1684 KConfigGroup group(&config, DONT_SHOW_ERRORS_GROUP); 1685 const QString id = QStringLiteral("%1:%2").arg(eventId.resourceId()).arg(eventId.eventId()); 1686 return group.readEntry(id, QStringList()); 1687 } 1688 1689 /****************************************************************************** 1690 * Check whether the specified Don't-show-again error message tag is set for an 1691 * alarm ID. 1692 */ 1693 bool dontShowErrors(const EventId& eventId, const QString& tag) 1694 { 1695 if (tag.isEmpty()) 1696 return false; 1697 const QStringList tags = dontShowErrors(eventId); 1698 return tags.indexOf(tag) >= 0; 1699 } 1700 1701 /****************************************************************************** 1702 * Reset the Don't-show-again error message tags for an alarm ID. 1703 * If 'tags' is empty, the config entry is deleted. 1704 */ 1705 void setDontShowErrors(const EventId& eventId, const QStringList& tags) 1706 { 1707 if (eventId.isEmpty()) 1708 return; 1709 KConfig config(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + ALARM_OPTS_FILE); 1710 KConfigGroup group(&config, DONT_SHOW_ERRORS_GROUP); 1711 const QString id = QStringLiteral("%1:%2").arg(eventId.resourceId()).arg(eventId.eventId()); 1712 if (tags.isEmpty()) 1713 group.deleteEntry(id); 1714 else 1715 group.writeEntry(id, tags); 1716 group.sync(); 1717 } 1718 1719 /****************************************************************************** 1720 * Set the specified Don't-show-again error message tag for an alarm ID. 1721 * Existing tags are unaffected. 1722 */ 1723 void setDontShowErrors(const EventId& eventId, const QString& tag) 1724 { 1725 if (eventId.isEmpty() || tag.isEmpty()) 1726 return; 1727 KConfig config(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + ALARM_OPTS_FILE); 1728 KConfigGroup group(&config, DONT_SHOW_ERRORS_GROUP); 1729 const QString id = QStringLiteral("%1:%2").arg(eventId.resourceId()).arg(eventId.eventId()); 1730 QStringList tags = group.readEntry(id, QStringList()); 1731 if (tags.indexOf(tag) < 0) 1732 { 1733 tags += tag; 1734 group.writeEntry(id, tags); 1735 group.sync(); 1736 } 1737 } 1738 1739 /****************************************************************************** 1740 * Convert a date/time specification string into a local date/time or date value. 1741 * Parameters: 1742 * timeString = in the form [[[yyyy-]mm-]dd-]hh:mm [TZ] or yyyy-mm-dd [TZ]. 1743 * dateTime = receives converted date/time value. 1744 * defaultDt = default date/time used for missing parts of timeString, or null 1745 * to use current date/time. 1746 * allowTZ = whether to allow a time zone specifier in timeString. 1747 * Reply = true if successful. 1748 */ 1749 bool convertTimeString(const QByteArray& timeString, KADateTime& dateTime, const KADateTime& defaultDt, bool allowTZ) 1750 { 1751 #define MAX_DT_LEN 19 1752 int i = timeString.indexOf(' '); 1753 if (i > MAX_DT_LEN || (i >= 0 && !allowTZ)) 1754 return false; 1755 QString zone = (i >= 0) ? QString::fromLatin1(timeString.mid(i)) : QString(); 1756 char timeStr[MAX_DT_LEN+1]; 1757 strncpy(timeStr, timeString.left(i >= 0 ? i : MAX_DT_LEN).constData(), MAX_DT_LEN); 1758 timeStr[MAX_DT_LEN] = '\0'; 1759 int dt[5] = { -1, -1, -1, -1, -1 }; 1760 char* s; 1761 char* end; 1762 bool noTime; 1763 // Get the minute value 1764 if ((s = strchr(timeStr, ':')) == nullptr) 1765 noTime = true; 1766 else 1767 { 1768 noTime = false; 1769 *s++ = 0; 1770 dt[4] = strtoul(s, &end, 10); 1771 if (end == s || *end || dt[4] >= 60) 1772 return false; 1773 // Get the hour value 1774 if ((s = strrchr(timeStr, '-')) == nullptr) 1775 s = timeStr; 1776 else 1777 *s++ = 0; 1778 dt[3] = strtoul(s, &end, 10); 1779 if (end == s || *end || dt[3] >= 24) 1780 return false; 1781 } 1782 bool noDate = true; 1783 if (s != timeStr) 1784 { 1785 noDate = false; 1786 // Get the day value 1787 if ((s = strrchr(timeStr, '-')) == nullptr) 1788 s = timeStr; 1789 else 1790 *s++ = 0; 1791 dt[2] = strtoul(s, &end, 10); 1792 if (end == s || *end || dt[2] == 0 || dt[2] > 31) 1793 return false; 1794 if (s != timeStr) 1795 { 1796 // Get the month value 1797 if ((s = strrchr(timeStr, '-')) == nullptr) 1798 s = timeStr; 1799 else 1800 *s++ = 0; 1801 dt[1] = strtoul(s, &end, 10); 1802 if (end == s || *end || dt[1] == 0 || dt[1] > 12) 1803 return false; 1804 if (s != timeStr) 1805 { 1806 // Get the year value 1807 dt[0] = strtoul(timeStr, &end, 10); 1808 if (end == timeStr || *end) 1809 return false; 1810 } 1811 } 1812 } 1813 1814 QDate date; 1815 if (dt[0] >= 0) 1816 date = QDate(dt[0], dt[1], dt[2]); 1817 QTime time(0, 0, 0); 1818 if (noTime) 1819 { 1820 // No time was specified, so the full date must have been specified 1821 if (dt[0] < 0 || !date.isValid()) 1822 return false; 1823 dateTime = applyTimeZone(zone, date, time, false, defaultDt); 1824 } 1825 else 1826 { 1827 // Compile the values into a date/time structure 1828 time.setHMS(dt[3], dt[4], 0); 1829 if (dt[0] < 0) 1830 { 1831 // Some or all of the date was omitted. 1832 // Use the default date/time if provided. 1833 if (defaultDt.isValid()) 1834 { 1835 dt[0] = defaultDt.date().year(); 1836 date.setDate(dt[0], 1837 (dt[1] < 0 ? defaultDt.date().month() : dt[1]), 1838 (dt[2] < 0 ? defaultDt.date().day() : dt[2])); 1839 } 1840 else 1841 date.setDate(2000, 1, 1); // temporary substitute for date 1842 } 1843 dateTime = applyTimeZone(zone, date, time, true, defaultDt); 1844 if (!dateTime.isValid()) 1845 return false; 1846 if (dt[0] < 0) 1847 { 1848 // Some or all of the date was omitted. 1849 // Use the current date in the specified time zone as default. 1850 const KADateTime now = KADateTime::currentDateTime(dateTime.timeSpec()); 1851 date = dateTime.date(); 1852 date.setDate(now.date().year(), 1853 (dt[1] < 0 ? now.date().month() : dt[1]), 1854 (dt[2] < 0 ? now.date().day() : dt[2])); 1855 if (!date.isValid()) 1856 return false; 1857 if (noDate && time < now.time()) 1858 date = date.addDays(1); 1859 dateTime.setDate(date); 1860 } 1861 } 1862 return dateTime.isValid(); 1863 } 1864 1865 /****************************************************************************** 1866 * Extract dragged and dropped Akonadi RFC822 message data. 1867 */ 1868 bool dropAkonadiEmail(const QMimeData* data, QUrl& url, AlarmText& alarmText) 1869 { 1870 alarmText.clear(); 1871 const QList<QUrl> urls = data->urls(); 1872 if (urls.isEmpty()) 1873 url = QUrl(); 1874 else 1875 { 1876 url = urls.at(0); 1877 AkonadiPlugin* akonadiPlugin = Preferences::akonadiPlugin(); 1878 if (akonadiPlugin) 1879 { 1880 KAEvent::EmailId emailId; 1881 KMime::Message::Ptr message = akonadiPlugin->fetchAkonadiEmail(url, emailId); 1882 if (message) 1883 { 1884 // It's an email held in Akonadi 1885 if (message->hasContent()) 1886 alarmText = DragDrop::kMimeEmailToAlarmText(*message, emailId); 1887 return true; 1888 } 1889 } 1890 } 1891 return false; 1892 } 1893 1894 /****************************************************************************** 1895 * Convert a time zone specifier string and apply it to a given date and/or time. 1896 * The time zone specifier is a system time zone name, e.g. "Europe/London" or 1897 * "UTC". If no time zone is specified, it defaults to the local time zone. 1898 * If 'defaultDt' is valid, it supplies the time spec and default date. 1899 */ 1900 KADateTime applyTimeZone(const QString& tzstring, const QDate& date, const QTime& time, 1901 bool haveTime, const KADateTime& defaultDt) 1902 { 1903 bool error = false; 1904 KADateTime::Spec spec = KADateTime::LocalZone; 1905 const QString zone = tzstring.trimmed(); 1906 if (!zone.isEmpty()) 1907 { 1908 if (zone == QLatin1String("UTC")) 1909 spec = KADateTime::UTC; 1910 else 1911 { 1912 const QTimeZone tz(zone.toLatin1()); 1913 error = !tz.isValid(); 1914 if (!error) 1915 spec = tz; 1916 } 1917 } 1918 else if (defaultDt.isValid()) 1919 spec = defaultDt.timeSpec(); 1920 1921 KADateTime result; 1922 if (!error) 1923 { 1924 if (!date.isValid()) 1925 { 1926 // It's a time without a date 1927 if (defaultDt.isValid()) 1928 result = KADateTime(defaultDt.date(), time, spec); 1929 else if (spec == KADateTime::LocalZone) 1930 result = KADateTime(KADateTime::currentLocalDate(), time, spec); 1931 } 1932 else if (haveTime) 1933 { 1934 // It's a date and time 1935 result = KADateTime(date, time, spec); 1936 } 1937 else 1938 { 1939 // It's a date without a time 1940 result = KADateTime(date, spec); 1941 } 1942 } 1943 return result; 1944 } 1945 1946 #ifndef NDEBUG 1947 /****************************************************************************** 1948 * Set up KAlarm test conditions based on environment variables. 1949 * KALARM_TIME: specifies current system time (format [[[yyyy-]mm-]dd-]hh:mm [TZ]). 1950 */ 1951 void setTestModeConditions() 1952 { 1953 const QByteArray newTime = qgetenv("KALARM_TIME"); 1954 if (!newTime.isEmpty()) 1955 { 1956 KADateTime dt; 1957 if (convertTimeString(newTime, dt, KADateTime::realCurrentLocalDateTime(), true)) 1958 setSimulatedSystemTime(dt); 1959 } 1960 } 1961 1962 /****************************************************************************** 1963 * Set the simulated system time. 1964 */ 1965 void setSimulatedSystemTime(const KADateTime& dt) 1966 { 1967 KADateTime::setSimulatedSystemTime(dt); 1968 qCDebug(KALARM_LOG) << "New time =" << qPrintable(KADateTime::currentLocalDateTime().toString(QStringLiteral("%Y-%m-%d %H:%M %:Z"))); 1969 } 1970 #endif 1971 1972 } // namespace KAlarm 1973 namespace 1974 { 1975 1976 /****************************************************************************** 1977 * Display an error message about an error when saving an event. 1978 */ 1979 void displayUpdateError(QWidget* parent, KAlarm::UpdateError code, const UpdateStatusData& status, bool showKOrgError) 1980 { 1981 QString errmsg; 1982 if (status.status.status > KAlarm::UPDATE_KORG_ERR) 1983 { 1984 switch (code) 1985 { 1986 case KAlarm::ERR_ADD: 1987 case KAlarm::ERR_MODIFY: 1988 errmsg = (status.warnErr > 1) ? i18nc("@info", "Error saving alarms") 1989 : i18nc("@info", "Error saving alarm"); 1990 break; 1991 case KAlarm::ERR_DELETE: 1992 errmsg = (status.warnErr > 1) ? i18nc("@info", "Error deleting alarms") 1993 : i18nc("@info", "Error deleting alarm"); 1994 break; 1995 case KAlarm::ERR_REACTIVATE: 1996 errmsg = (status.warnErr > 1) ? i18nc("@info", "Error saving reactivated alarms") 1997 : i18nc("@info", "Error saving reactivated alarm"); 1998 break; 1999 case KAlarm::ERR_TEMPLATE: 2000 errmsg = (status.warnErr > 1) ? i18nc("@info", "Error saving alarm templates") 2001 : i18nc("@info", "Error saving alarm template"); 2002 break; 2003 } 2004 if (!status.status.message.isEmpty()) 2005 KAMessageBox::detailedError(parent, errmsg, status.status.message); 2006 else 2007 KAMessageBox::error(parent, errmsg); 2008 } 2009 else if (showKOrgError) 2010 displayKOrgUpdateError(parent, code, status.status, status.warnKOrg); 2011 } 2012 2013 /****************************************************************************** 2014 * Tell KOrganizer to put an alarm in its calendar. 2015 * It will be held by KOrganizer as a simple event, without alarms - KAlarm 2016 * is still responsible for alarming. 2017 */ 2018 KAlarm::UpdateResult sendToKOrganizer(const KAEvent& event) 2019 { 2020 Event::Ptr kcalEvent(new KCalendarCore::Event); 2021 event.updateKCalEvent(kcalEvent, KAEvent::UidAction::Ignore); 2022 // Change the event ID to avoid duplicating the same unique ID as the original event 2023 const QString uid = uidKOrganizer(event.id()); 2024 kcalEvent->setUid(uid); 2025 kcalEvent->clearAlarms(); 2026 QString userEmail; 2027 switch (event.actionTypes()) 2028 { 2029 case KAEvent::Action::Display: 2030 case KAEvent::Action::Command: 2031 case KAEvent::Action::DisplayCommand: 2032 kcalEvent->setSummary(event.cleanText()); 2033 userEmail = Preferences::emailAddress(); 2034 break; 2035 case KAEvent::Action::Email: 2036 { 2037 const QString from = event.emailFromId() 2038 ? Identities::identityManager()->identityForUoid(event.emailFromId()).fullEmailAddr() 2039 : Preferences::emailAddress(); 2040 AlarmText atext; 2041 atext.setEmail(event.emailAddresses(QStringLiteral(", ")), from, QString(), QString(), event.emailSubject(), QString()); 2042 kcalEvent->setSummary(atext.displayText()); 2043 userEmail = from; 2044 break; 2045 } 2046 case KAEvent::Action::Audio: 2047 kcalEvent->setSummary(event.audioFile()); 2048 break; 2049 default: 2050 break; 2051 } 2052 const Person person(QString(), userEmail); 2053 kcalEvent->setOrganizer(person); 2054 kcalEvent->setDuration(Duration(Preferences::kOrgEventDuration() * 60, Duration::Seconds)); 2055 2056 // Translate the event into string format 2057 ICalFormat format; 2058 format.setTimeZone(Preferences::timeSpecAsZone()); 2059 const QString iCal = format.toICalString(kcalEvent); 2060 2061 // Send the event to KOrganizer 2062 KAlarm::UpdateResult status = runKOrganizer(); // start KOrganizer if it isn't already running, and create its D-Bus interface 2063 if (status != KAlarm::UPDATE_OK) 2064 return status; 2065 QDBusInterface korgInterface(KORG_DBUS_SERVICE, QStringLiteral(KORG_DBUS_PATH), KORG_DBUS_IFACE); 2066 const QList<QVariant> args{iCal}; 2067 QDBusReply<bool> reply = korgInterface.callWithArgumentList(QDBus::Block, QStringLiteral("addIncidence"), args); 2068 if (!reply.isValid()) 2069 { 2070 if (reply.error().type() == QDBusError::UnknownObject) 2071 { 2072 status = KAlarm::UPDATE_KORG_ERRSTART; 2073 qCCritical(KALARM_LOG) << "KAlarm::sendToKOrganizer: addIncidence() D-Bus error: still starting"; 2074 } 2075 else 2076 { 2077 status.set(KAlarm::UPDATE_KORG_ERR, reply.error().message()); 2078 qCCritical(KALARM_LOG) << "KAlarm::sendToKOrganizer: addIncidence(" << uid << ") D-Bus call failed:" << status.message; 2079 } 2080 } 2081 else if (!reply.value()) 2082 { 2083 status = KAlarm::UPDATE_KORG_FUNCERR; 2084 qCDebug(KALARM_LOG) << "KAlarm::sendToKOrganizer: addIncidence(" << uid << ") D-Bus call returned false"; 2085 } 2086 else 2087 qCDebug(KALARM_LOG) << "KAlarm::sendToKOrganizer:" << uid << ": success"; 2088 return status; 2089 } 2090 2091 /****************************************************************************** 2092 * Tell KOrganizer to delete an event from its calendar. 2093 */ 2094 KAlarm::UpdateResult deleteFromKOrganizer(const QString& eventID) 2095 { 2096 AkonadiPlugin* akonadiPlugin = Preferences::akonadiPlugin(); 2097 if (!akonadiPlugin) 2098 return KAlarm::UpdateResult(KAlarm::UPDATE_KORG_ERR); 2099 2100 const QString newID = uidKOrganizer(eventID); 2101 akonadiPlugin->deleteEvent(KORG_MIME_TYPE, QString(), newID); 2102 // Ignore errors 2103 return KAlarm::UpdateResult(KAlarm::UPDATE_OK); 2104 } 2105 2106 /****************************************************************************** 2107 * Start KOrganizer if not already running, and create its D-Bus interface. 2108 */ 2109 KAlarm::UpdateResult runKOrganizer() 2110 { 2111 KAlarm::UpdateResult status; 2112 2113 // If Kontact is running, there is a load() method which needs to be called to 2114 // load KOrganizer into Kontact. But if KOrganizer is running independently, 2115 // the load() method doesn't exist. This call starts korganizer if needed, too. 2116 QDBusInterface iface(KORG_DBUS_SERVICE, QStringLiteral(KORG_DBUS_LOAD_PATH), QStringLiteral("org.kde.PIMUniqueApplication")); 2117 QDBusReply<bool> reply = iface.call(QStringLiteral("load")); 2118 if ((!reply.isValid() || !reply.value()) 2119 && iface.lastError().type() != QDBusError::UnknownMethod) 2120 { 2121 status.set(KAlarm::UPDATE_KORG_ERR, iface.lastError().message()); 2122 qCWarning(KALARM_LOG) << "Loading KOrganizer failed:" << status.message; 2123 return status; 2124 } 2125 2126 return status; 2127 } 2128 2129 /****************************************************************************** 2130 * Insert a KOrganizer string after the hyphen in the supplied event ID. 2131 */ 2132 QString uidKOrganizer(const QString& id) 2133 { 2134 if (id.startsWith(KORGANIZER_UID)) 2135 return id; 2136 QString result = id; 2137 return result.insert(0, KORGANIZER_UID); 2138 } 2139 2140 } // namespace 2141 2142 /****************************************************************************** 2143 * Case insensitive comparison for use by qSort(). 2144 */ 2145 bool caseInsensitiveLessThan(const QString& s1, const QString& s2) 2146 { 2147 return s1.toLower() < s2.toLower(); 2148 } 2149 2150 #include "moc_functions_p.cpp" 2151 2152 // vim: et sw=4: