functions.cpp 74.3 KB
Newer Older
1 2
/*
 *  functions.cpp  -  miscellaneous functions
David Jarvie's avatar
David Jarvie committed
3
 *  Program:  kalarm
4
 *  Copyright © 2001-2014 by David Jarvie <djarvie@kde.org>
David Jarvie's avatar
David Jarvie committed
5 6 7 8 9 10 11 12 13 14 15
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
16 17
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
David Jarvie's avatar
David Jarvie committed
18
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
David Jarvie's avatar
David Jarvie committed
19 20
 */

David Jarvie's avatar
David Jarvie committed
21 22
#include "kalarm.h"   //krazy:exclude=includes (kalarm.h must be first)
#include "functions.h"
23
#include "functions_p.h"
David Jarvie's avatar
David Jarvie committed
24

25
#include "collectionmodel.h"
26
#include "collectionsearch.h"
David Jarvie's avatar
David Jarvie committed
27
#include "alarmcalendar.h"
Till Adam's avatar
Till Adam committed
28
#include "alarmtime.h"
29
#include "autoqpointer.h"
David Jarvie's avatar
David Jarvie committed
30 31 32 33 34
#include "alarmlistview.h"
#include "editdlg.h"
#include "kalarmapp.h"
#include "kamail.h"
#include "mainwindow.h"
35
#include "messagebox.h"
David Jarvie's avatar
David Jarvie committed
36 37 38 39 40
#include "messagewin.h"
#include "preferences.h"
#include "shellprocess.h"
#include "templatelistview.h"
#include "templatemenuaction.h"
David Jarvie's avatar
David Jarvie committed
41

Laurent Montel's avatar
Laurent Montel committed
42
#include "config-kalarm.h"
David Jarvie's avatar
Spacing  
David Jarvie committed
43

Laurent Montel's avatar
Laurent Montel committed
44 45
#include <kalarmcal/identities.h>
#include <kalarmcal/kaevent.h>
46

Christophe Giboudeaux's avatar
Christophe Giboudeaux committed
47
#include <KCalCore/Event>
Laurent Montel's avatar
Laurent Montel committed
48
#include <KCalCore/ICalFormat>
Christophe Giboudeaux's avatar
Christophe Giboudeaux committed
49 50
#include <KCalCore/Person>
#include <KCalCore/Duration>
51
using namespace KCalCore;
Laurent Montel's avatar
Laurent Montel committed
52 53
#include <KIdentityManagement/kidentitymanagement/identitymanager.h>
#include <KIdentityManagement/kidentitymanagement/identity.h>
John Layt's avatar
John Layt committed
54
#include <KHolidays/HolidayRegion>
55

David Jarvie's avatar
David Jarvie committed
56
#include <kconfiggroup.h>
David Jarvie's avatar
Spacing  
David Jarvie committed
57
#include <KSharedConfig>
58
#include <ktoggleaction.h>
59
#include <kactioncollection.h>
60
#include <kdbusservicestarter.h>
61
#include <KLocalizedString>
62
#include <kauth.h>
63
#include <ksystemtimezone.h>
64
#include <kstandardguiitem.h>
Aaron J. Seigo's avatar
build  
Aaron J. Seigo committed
65
#include <kstandardshortcut.h>
66
#include <kfiledialog.h>
67 68
#include <KIO/StatJob>
#include <KJobWidgets>
69
#include <kfileitem.h>
70
#include <ktoolinvocation.h>
David Jarvie's avatar
David Jarvie committed
71

Laurent Montel's avatar
Laurent Montel committed
72
#if KDEPIM_HAVE_X11
73 74 75 76 77 78 79
#include <kwindowsystem.h>
#include <kxmessages.h>
#include <kstartupinfo.h>
#include <netwm.h>
#include <QX11Info>
#endif

David Jarvie's avatar
Tidy up  
David Jarvie committed
80
#include <QAction>
81 82 83 84
#include <QDir>
#include <QRegExp>
#include <QDesktopWidget>
#include <QtDBus/QtDBus>
David Jarvie's avatar
David Jarvie committed
85
#include <QTimer>
86
#include <qglobal.h>
Laurent Montel's avatar
Laurent Montel committed
87
#include <QStandardPaths>
Laurent Montel's avatar
Laurent Montel committed
88
#include "kalarm_debug.h"
89 90 91

using namespace Akonadi;

David Jarvie's avatar
David Jarvie committed
92 93 94

namespace
{
David Jarvie's avatar
David Jarvie committed
95
bool            refreshAlarmsQueued = false;
Laurent Montel's avatar
Laurent Montel committed
96
QDBusInterface* korgInterface = Q_NULLPTR;
David Jarvie's avatar
David Jarvie committed
97

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
struct UpdateStatusData
{
    KAlarm::UpdateResult status;   // status code and KOrganizer error message if any
    int                  warnErr;
    int                  warnKOrg;

    explicit UpdateStatusData(KAlarm::UpdateStatus s = KAlarm::UPDATE_OK) : status(s), warnErr(0), warnKOrg(0) {}
    // Set an error status and increment to number of errors to warn about
    void setError(KAlarm::UpdateStatus st, int errorCount = -1)
    {
        status.set(st);
        if (errorCount < 0)
            ++warnErr;
        else
            warnErr = errorCount;
    }
    // Update the error status with a KOrganizer related status
    void korgUpdate(KAlarm::UpdateResult result)
    {
        if (result.status != KAlarm::UPDATE_OK)
        {
            ++warnKOrg;
            if (result.status > status.status)
                status = result;
        }
    }
};

David Jarvie's avatar
David Jarvie committed
126 127 128 129 130
const QLatin1String KMAIL_DBUS_SERVICE("org.kde.kmail");
//const QLatin1String KMAIL_DBUS_IFACE("org.kde.kmail.kmail");
//const QLatin1String KMAIL_DBUS_WINDOW_PATH("/kmail/kmail_mainwindow_1");
const QLatin1String KORG_DBUS_SERVICE("org.kde.korganizer");
const QLatin1String KORG_DBUS_IFACE("org.kde.korganizer.Korganizer");
131 132 133
// D-Bus object path of KOrganizer's notification interface
#define       KORG_DBUS_PATH            "/Korganizer"
#define       KORG_DBUS_LOAD_PATH       "/korganizer_PimApplication"
David Jarvie's avatar
David Jarvie committed
134
//const QLatin1String KORG_DBUS_WINDOW_PATH("/korganizer/MainWindow_1");
135 136
const QLatin1String KORG_MIME_TYPE("application/x-vnd.akonadi.calendar.event");
const QLatin1String KORGANIZER_UID("-korg");
137

David Jarvie's avatar
David Jarvie committed
138 139
const QLatin1String ALARM_OPTS_FILE("alarmopts");
const char*         DONT_SHOW_ERRORS_GROUP = "DontShowErrors";
140

141
void editNewTemplate(EditAlarmDlg::Type, const KAEvent* preset, QWidget* parent);
142 143 144 145
void displayUpdateError(QWidget* parent, KAlarm::UpdateError, const UpdateStatusData&, bool showKOrgError = true);
KAlarm::UpdateResult sendToKOrganizer(const KAEvent&);
KAlarm::UpdateResult deleteFromKOrganizer(const QString& eventID);
KAlarm::UpdateResult runKOrganizer();
146
QString uidKOrganizer(const QString& eventID);
David Jarvie's avatar
David Jarvie committed
147 148 149 150 151 152
}


namespace KAlarm
{

Laurent Montel's avatar
Laurent Montel committed
153
Private* Private::mInstance = Q_NULLPTR;
154

David Jarvie's avatar
David Jarvie committed
155
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
156
* Display a main window with the specified event selected.
David Jarvie's avatar
David Jarvie committed
157
*/
158
MainWindow* displayMainWindowSelected(Akonadi::Item::Id eventId)
David Jarvie's avatar
David Jarvie committed
159
{
160 161 162 163 164 165 166 167 168 169 170 171
    MainWindow* win = MainWindow::firstWindow();
    if (!win)
    {
        if (theApp()->checkCalendar())    // ensure calendar is open
        {
            win = MainWindow::create();
            win->show();
        }
    }
    else
    {
        // There is already a main window, so make it the active window
172 173 174
        win->hide();        // in case it's on a different desktop
        win->setWindowState(win->windowState() & ~Qt::WindowMinimized);
        win->show();
175 176 177 178 179 180
        win->raise();
        win->activateWindow();
    }
    if (win  &&  eventId >= 0)
        win->selectEvent(eventId);
    return win;
David Jarvie's avatar
David Jarvie committed
181 182
}

183 184 185 186 187
/******************************************************************************
* Create an "Alarms Enabled/Enable Alarms" action.
*/
KToggleAction* createAlarmEnableAction(QObject* parent)
{
188 189
    KToggleAction* action = new KToggleAction(i18nc("@action", "Enable &Alarms"), parent);
    action->setChecked(theApp()->alarmsEnabled());
190
    QObject::connect(action, &QAction::toggled, theApp(), &KAlarmApp::setAlarmsEnabled);
191
    // The following line ensures that all instances are kept in the same state
192
    QObject::connect(theApp(), &KAlarmApp::alarmEnabledToggled, action, &QAction::setChecked);
193
    return action;
194 195
}

196 197 198
/******************************************************************************
* Create a "Stop Play" action.
*/
David Jarvie's avatar
Spacing  
David Jarvie committed
199
QAction* createStopPlayAction(QObject* parent)
200
{
Laurent Montel's avatar
Laurent Montel committed
201
    QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-stop")), i18nc("@action", "Stop Play"), parent);
202
    action->setEnabled(MessageWin::isAudioPlaying());
203
    QObject::connect(action, &QAction::triggered, theApp(), &KAlarmApp::stopAudio);
204
    // The following line ensures that all instances are kept in the same state
205
    QObject::connect(theApp(), &KAlarmApp::audioPlaying, action, &QAction::setEnabled);
206
    return action;
207 208
}

209 210 211 212 213
/******************************************************************************
* Create a "Spread Windows" action.
*/
KToggleAction* createSpreadWindowsAction(QObject* parent)
{
214
    KToggleAction* action = new KToggleAction(i18nc("@action", "Spread Windows"), parent);
215
    QObject::connect(action, &QAction::triggered, theApp(), &KAlarmApp::spreadWindows);
216
    // The following line ensures that all instances are kept in the same state
217
    QObject::connect(theApp(), &KAlarmApp::spreadWindowsToggled, action, &QAction::setChecked);
218
    return action;
219 220
}

David Jarvie's avatar
David Jarvie committed
221
/******************************************************************************
222
* Add a new active (non-archived) alarm.
David Jarvie's avatar
David Jarvie committed
223
* Save it in the calendar file and add it to every main window instance.
224 225 226
* Parameters: msgParent = parent widget for any calendar selection prompt or
*                         error message.
*             event - is updated with the actual event ID.
David Jarvie's avatar
David Jarvie committed
227
*/
228
UpdateResult addEvent(KAEvent& event, Collection* calendar, QWidget* msgParent, int options, bool showKOrgErr)
David Jarvie's avatar
David Jarvie committed
229
{
Laurent Montel's avatar
Laurent Montel committed
230
    qCDebug(KALARM_LOG) << event.id();
231
    bool cancelled = false;
232
    UpdateStatusData status;
233
    if (!theApp()->checkCalendar())    // ensure calendar is open
234
        status.status = UPDATE_FAILED;
235 236 237 238
    else
    {
        // Save the event details in the calendar file, and get the new event ID
        AlarmCalendar* cal = AlarmCalendar::resources();
David Jarvie's avatar
David Jarvie committed
239 240
        // Note that AlarmCalendar::addEvent() updates 'event'.
        if (!cal->addEvent(event, msgParent, (options & USE_EVENT_ID), calendar, (options & NO_RESOURCE_PROMPT), &cancelled))
241
        {
242
            status.status = UPDATE_FAILED;
243 244 245 246
        }
        else
        {
            if (!cal->save())
247
                status.status = SAVE_FAILED;
248
        }
249
        if (status.status == UPDATE_OK)
250 251 252
        {
            if ((options & ALLOW_KORG_UPDATE)  &&  event.copyToKOrganizer())
            {
253 254
                UpdateResult st = sendToKOrganizer(event);    // tell KOrganizer to show the event
                status.korgUpdate(st);
255
            }
256

257 258
        }
    }
259

260 261 262
    if (status.status != UPDATE_OK  &&  !cancelled  &&  msgParent)
        displayUpdateError(msgParent, ERR_ADD, status, showKOrgErr);
    return status.status;
263 264 265
}

/******************************************************************************
266
* Add a list of new active (non-archived) alarms.
267 268 269
* Save them in the calendar file and add them to every main window instance.
* The events are updated with their actual event IDs.
*/
270
UpdateResult addEvents(QVector<KAEvent>& events, QWidget* msgParent, bool allowKOrgUpdate, bool showKOrgErr)
271
{
Laurent Montel's avatar
Laurent Montel committed
272
    qCDebug(KALARM_LOG) << events.count();
273
    if (events.isEmpty())
274 275
        return UpdateResult(UPDATE_OK);
    UpdateStatusData status;
276 277
    Collection collection;
    if (!theApp()->checkCalendar())    // ensure calendar is open
278
        status.status = UPDATE_FAILED;
279 280
    else
    {
281
        collection = CollectionControlModel::instance()->destination(CalEvent::ACTIVE, msgParent);
282 283
        if (!collection.isValid())
        {
Laurent Montel's avatar
Laurent Montel committed
284
            qCDebug(KALARM_LOG) << "No calendar";
285
            status.status = UPDATE_FAILED;
286 287
        }
    }
288
    if (status.status == UPDATE_OK)
289 290 291 292 293 294 295
    {
        AlarmCalendar* cal = AlarmCalendar::resources();
        for (int i = 0, end = events.count();  i < end;  ++i)
        {
            // Save the event details in the calendar file, and get the new event ID
            if (!cal->addEvent(events[i], msgParent, false, &collection))
            {
296
                status.setError(UPDATE_ERROR);
297 298
                continue;
            }
David Jarvie's avatar
David Jarvie committed
299
            if (allowKOrgUpdate  &&  events[i].copyToKOrganizer())
300
            {
301 302
                UpdateResult st = sendToKOrganizer(events[i]);    // tell KOrganizer to show the event
                status.korgUpdate(st);
303
            }
304

305
        }
306 307
        if (status.warnErr == events.count())
            status.status = UPDATE_FAILED;
308
        else if (!cal->save())
309
            status.setError(SAVE_FAILED, events.count());  // everything failed
310 311
    }

312 313 314
    if (status.status != UPDATE_OK  &&  msgParent)
        displayUpdateError(msgParent, ERR_ADD, status, showKOrgErr);
    return status.status;
David Jarvie's avatar
David Jarvie committed
315 316
}

317
/******************************************************************************
318
* Save the event in the archived calendar and adjust every main window instance.
319
* The event's ID is changed to an archived ID if necessary.
320
*/
321
bool addArchivedEvent(KAEvent& event, Collection* collection)
322
{
Laurent Montel's avatar
Laurent Montel committed
323
    qCDebug(KALARM_LOG) << event.id();
324
    bool archiving = (event.category() == CalEvent::ACTIVE);
325 326 327 328 329 330 331 332
    if (archiving  &&  !Preferences::archivedKeepDays())
        return false;   // expired alarms aren't being kept
    AlarmCalendar* cal = AlarmCalendar::resources();
    KAEvent newevent(event);
    newevent.setItemId(-1);    // invalidate the Akonadi item ID since it's a new item
    KAEvent* const newev = &newevent;
    if (archiving)
    {
333
        newev->setCategory(CalEvent::ARCHIVED);    // this changes the event ID
334
        newev->setCreatedDateTime(KDateTime::currentUtcDateTime());   // time stamp to control purging
335 336
    }
    // Note that archived resources are automatically saved after changes are made
Laurent Montel's avatar
Laurent Montel committed
337
    if (!cal->addEvent(newevent, Q_NULLPTR, false, collection))
338 339
        return false;
    event = *newev;   // update event ID etc.
340

341
    return true;
342 343 344 345 346 347
}

/******************************************************************************
* Add a new template.
* Save it in the calendar file and add it to every template list view.
* 'event' is updated with the actual event ID.
348
* Parameters: promptParent = parent widget for any calendar selection prompt.
349
*/
350
UpdateResult addTemplate(KAEvent& event, Collection* collection, QWidget* msgParent)
351
{
Laurent Montel's avatar
Laurent Montel committed
352
    qCDebug(KALARM_LOG) << event.id();
353
    UpdateStatusData status;
354

355 356 357 358
    // Add the template to the calendar file
    AlarmCalendar* cal = AlarmCalendar::resources();
    KAEvent newev(event);
    if (!cal->addEvent(newev, msgParent, false, collection))
359
        status.status = UPDATE_FAILED;
360 361 362 363
    else
    {
        event = newev;   // update event ID etc.
        if (!cal->save())
364
            status.status = SAVE_FAILED;
365 366
        else
        {
367
            return UpdateResult(UPDATE_OK);
368 369
        }
    }
370

371
    if (msgParent)
372 373
        displayUpdateError(msgParent, ERR_TEMPLATE, status);
    return status.status;
374 375
}

David Jarvie's avatar
David Jarvie committed
376
/******************************************************************************
377
* Modify an active (non-archived) alarm in the calendar file and in every main
378
* window instance.
379
* The new event must have a different event ID from the old one.
David Jarvie's avatar
David Jarvie committed
380
*/
381
UpdateResult modifyEvent(KAEvent& oldEvent, KAEvent& newEvent, QWidget* msgParent, bool showKOrgErr)
David Jarvie's avatar
David Jarvie committed
382
{
Laurent Montel's avatar
Laurent Montel committed
383
    qCDebug(KALARM_LOG) << oldEvent.id();
384

385
    UpdateStatusData status;
386 387 388
    if (!newEvent.isValid())
    {
        deleteEvent(oldEvent, true);
389
        status.status = UPDATE_FAILED;
390 391 392
    }
    else
    {
393
        EventId oldId(oldEvent);
394 395 396 397 398
        if (oldEvent.copyToKOrganizer())
        {
            // Tell KOrganizer to delete its old event.
            // But ignore errors, because the user could have manually
            // deleted it since KAlarm asked KOrganizer to set it up.
399
            deleteFromKOrganizer(oldId.eventId());
400
        }
401 402 403
        // Update the event in the calendar file, and get the new event ID
        AlarmCalendar* cal = AlarmCalendar::resources();
        if (!cal->modifyEvent(oldId, newEvent))
404
            status.status = UPDATE_FAILED;
405 406 407
        else
        {
            if (!cal->save())
408 409
                status.status = SAVE_FAILED;
            if (status.status == UPDATE_OK)
410
            {
411
                if (newEvent.copyToKOrganizer())
412
                {
413 414
                    UpdateResult st = sendToKOrganizer(newEvent);    // tell KOrganizer to show the new event
                    status.korgUpdate(st);
415 416 417 418
                }

                // Remove "Don't show error messages again" for the old alarm
                setDontShowErrors(oldId);
419

420 421 422
            }
        }
    }
423

424 425 426
    if (status.status != UPDATE_OK  &&  msgParent)
        displayUpdateError(msgParent, ERR_MODIFY, status, showKOrgErr);
    return status.status;
David Jarvie's avatar
David Jarvie committed
427 428 429
}

/******************************************************************************
430
* Update an active (non-archived) alarm from the calendar file and from every
431
* main window instance.
David Jarvie's avatar
David Jarvie committed
432
* The new event will have the same event ID as the old one.
433 434
* The event is not updated in KOrganizer, since this function is called when an
* existing alarm is rescheduled (due to recurrence or deferral).
David Jarvie's avatar
David Jarvie committed
435
*/
436
UpdateResult updateEvent(KAEvent& event, QWidget* msgParent, bool archiveOnDelete)
David Jarvie's avatar
David Jarvie committed
437
{
Laurent Montel's avatar
Laurent Montel committed
438
    qCDebug(KALARM_LOG) << event.id();
David Jarvie's avatar
David Jarvie committed
439

440 441 442 443 444 445 446 447 448 449
    if (!event.isValid())
        deleteEvent(event, archiveOnDelete);
    else
    {
        // Update the event in the calendar file.
        AlarmCalendar* cal = AlarmCalendar::resources();
        cal->updateEvent(event);
        if (!cal->save())
        {
            if (msgParent)
450 451
                displayUpdateError(msgParent, ERR_ADD, UpdateStatusData(SAVE_FAILED));
            return UpdateResult(SAVE_FAILED);
452
        }
David Jarvie's avatar
David Jarvie committed
453

454
    }
455
    return UpdateResult(UPDATE_OK);
David Jarvie's avatar
David Jarvie committed
456 457
}

458 459 460 461 462
/******************************************************************************
* Update a template in the calendar file and in every template list view.
* If 'selectionView' is non-null, the selection highlight is moved to the
* updated event in that listView instance.
*/
463
UpdateResult updateTemplate(KAEvent& event, QWidget* msgParent)
464
{
465 466 467 468 469 470 471 472 473 474
    AlarmCalendar* cal = AlarmCalendar::resources();
    KAEvent* newEvent = cal->updateEvent(event);
    UpdateStatus status = UPDATE_OK;
    if (!newEvent)
        status = UPDATE_FAILED;
    else if (!cal->save())
        status = SAVE_FAILED;
    if (status != UPDATE_OK)
    {
        if (msgParent)
475 476
            displayUpdateError(msgParent, ERR_TEMPLATE, UpdateStatusData(SAVE_FAILED));
        return UpdateResult(status);
477
    }
478

479
    return UpdateResult(UPDATE_OK);
480 481
}

David Jarvie's avatar
David Jarvie committed
482
/******************************************************************************
483
* Delete alarms from the calendar file and from every main window instance.
484
* If the events are archived, the events' IDs are changed to archived IDs if necessary.
David Jarvie's avatar
David Jarvie committed
485
*/
486
UpdateResult deleteEvent(KAEvent& event, bool archive, QWidget* msgParent, bool showKOrgErr)
David Jarvie's avatar
David Jarvie committed
487
{
488
    QVector<KAEvent> events(1, event);
489
    return deleteEvents(events, archive, msgParent, showKOrgErr);
490
}
David Jarvie's avatar
David Jarvie committed
491

492
UpdateResult deleteEvents(QVector<KAEvent>& events, bool archive, QWidget* msgParent, bool showKOrgErr)
493
{
Laurent Montel's avatar
Laurent Montel committed
494
    qCDebug(KALARM_LOG) << events.count();
495
    if (events.isEmpty())
496 497
        return UpdateResult(UPDATE_OK);
    UpdateStatusData status;
498
    AlarmCalendar* cal = AlarmCalendar::resources();
David Jarvie's avatar
David Jarvie committed
499 500
    bool deleteWakeFromSuspendAlarm = false;
    QString wakeFromSuspendId = checkRtcWakeConfig().value(0);
501 502 503 504 505
    for (int i = 0, end = events.count();  i < end;  ++i)
    {
        // Save the event details in the calendar file, and get the new event ID
        KAEvent* event = &events[i];
        QString id = event->id();
506

507 508

        // Delete the event from the calendar file
509
        if (event->category() != CalEvent::ARCHIVED)
510 511 512 513 514 515
        {
            if (event->copyToKOrganizer())
            {
                // The event was shown in KOrganizer, so tell KOrganizer to
                // delete it. But ignore errors, because the user could have
                // manually deleted it from KOrganizer since it was set up.
516 517
                UpdateResult st = deleteFromKOrganizer(id);
                status.korgUpdate(st);
518 519 520 521 522 523 524 525
            }
            if (archive  &&  event->toBeArchived())
            {
                KAEvent ev(*event);
                addArchivedEvent(ev);     // this changes the event ID to an archived ID
            }
        }
        if (!cal->deleteEvent(*event, false))   // don't save calendar after deleting
526
            status.setError(UPDATE_ERROR);
527

David Jarvie's avatar
David Jarvie committed
528 529
        if (id == wakeFromSuspendId)
            deleteWakeFromSuspendAlarm = true;
530

531
        // Remove "Don't show error messages again" for this alarm
532
        setDontShowErrors(EventId(*event));
533 534
    }

535 536
    if (status.warnErr == events.count())
        status.status = UPDATE_FAILED;
537
    else if (!cal->save())      // save the calendars now
538 539 540
        status.setError(SAVE_FAILED, events.count());
    if (status.status != UPDATE_OK  &&  msgParent)
        displayUpdateError(msgParent, ERR_DELETE, status, showKOrgErr);
David Jarvie's avatar
David Jarvie committed
541 542 543 544 545

    // Remove any wake-from-suspend scheduled for a deleted alarm
    if (deleteWakeFromSuspendAlarm  &&  !wakeFromSuspendId.isEmpty())
        cancelRtcWake(msgParent, wakeFromSuspendId);

546
    return status.status;
David Jarvie's avatar
David Jarvie committed
547 548
}

549 550 551
/******************************************************************************
* Delete templates from the calendar file and from every template list view.
*/
552
UpdateResult deleteTemplates(const KAEvent::List& events, QWidget* msgParent)
553
{
554
    int count = events.count();
Laurent Montel's avatar
Laurent Montel committed
555
    qCDebug(KALARM_LOG) << count;
556
    if (!count)
557 558
        return UpdateResult(UPDATE_OK);
    UpdateStatusData status;
559 560 561 562 563 564 565
    AlarmCalendar* cal = AlarmCalendar::resources();
    for (int i = 0, end = count;  i < end;  ++i)
    {
        // Update the window lists
        // Delete the template from the calendar file
        AlarmCalendar* cal = AlarmCalendar::resources();
        if (!cal->deleteEvent(*events[i], false))   // don't save calendar after deleting
566
            status.setError(UPDATE_ERROR);
567 568
    }

569 570
    if (status.warnErr == count)
        status.status = UPDATE_FAILED;
571
    else if (!cal->save())      // save the calendars now
572 573 574 575
        status.setError(SAVE_FAILED, count);
    if (status.status != UPDATE_OK  &&  msgParent)
        displayUpdateError(msgParent, ERR_TEMPLATE, status);
    return status.status;
576 577
}

David Jarvie's avatar
David Jarvie committed
578 579 580 581 582
/******************************************************************************
* Delete an alarm from the display calendar.
*/
void deleteDisplayEvent(const QString& eventID)
{
Laurent Montel's avatar
Laurent Montel committed
583
    qCDebug(KALARM_LOG) << eventID;
584 585 586
    AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen();
    if (cal)
        cal->deleteDisplayEvent(eventID, true);   // save calendar after deleting
David Jarvie's avatar
David Jarvie committed
587 588 589
}

/******************************************************************************
590
* Undelete archived alarms, and update every main window instance.
591 592
* The archive bit is set to ensure that they get re-archived if deleted again.
* 'ineligibleIDs' is filled in with the IDs of any ineligible events.
David Jarvie's avatar
David Jarvie committed
593
*/
594
UpdateResult reactivateEvent(KAEvent& event, Collection* calendar, QWidget* msgParent, bool showKOrgErr)
David Jarvie's avatar
David Jarvie committed
595
{
596
    QVector<EventId> ids;
597
    QVector<KAEvent> events(1, event);
598
    return reactivateEvents(events, ids, calendar, msgParent, showKOrgErr);
599
}
David Jarvie's avatar
David Jarvie committed
600

601
UpdateResult reactivateEvents(QVector<KAEvent>& events, QVector<EventId>& ineligibleIDs, Collection* col, QWidget* msgParent, bool showKOrgErr)
602
{
Laurent Montel's avatar
Laurent Montel committed
603
    qCDebug(KALARM_LOG) << events.count();
604 605
    ineligibleIDs.clear();
    if (events.isEmpty())
606 607
        return UpdateResult(UPDATE_OK);
    UpdateStatusData status;
608 609 610 611
    Collection collection;
    if (col)
        collection = *col;
    if (!collection.isValid())
612
        collection = CollectionControlModel::instance()->destination(CalEvent::ACTIVE, msgParent);
613 614
    if (!collection.isValid())
    {
Laurent Montel's avatar
Laurent Montel committed
615
        qCDebug(KALARM_LOG) << "No calendar";
616
        status.setError(UPDATE_FAILED, events.count());
617 618 619 620 621 622 623 624 625 626
    }
    else
    {
        int count = 0;
        AlarmCalendar* cal = AlarmCalendar::resources();
        KDateTime now = KDateTime::currentUtcDateTime();
        for (int i = 0, end = events.count();  i < end;  ++i)
        {
            // Delete the event from the archived resource
            KAEvent* event = &events[i];
627
            if (event->category() != CalEvent::ARCHIVED
628 629
            ||  !event->occursAfter(now, true))
            {
630
                ineligibleIDs += EventId(*event);
631 632 633
                continue;
            }
            ++count;
634

635 636
            KAEvent newevent(*event);
            KAEvent* const newev = &newevent;
637
            newev->setCategory(CalEvent::ACTIVE);    // this changes the event ID
638 639 640
            if (newev->recurs()  ||  newev->repetition())
                newev->setNextOccurrence(now);   // skip any recurrences in the past
            newev->setArchive();    // ensure that it gets re-archived if it is deleted
David Jarvie's avatar
David Jarvie committed
641

642 643 644 645
            // Save the event details in the calendar file.
            // This converts the event ID.
            if (!cal->addEvent(newevent, msgParent, true, &collection))
            {
646
                status.setError(UPDATE_ERROR);
647 648 649 650
                continue;
            }
            if (newev->copyToKOrganizer())
            {
651 652
                UpdateResult st = sendToKOrganizer(*newev);    // tell KOrganizer to show the event
                status.korgUpdate(st);
653
            }
654

David Jarvie's avatar
David Jarvie committed
655

656
            if (cal->event(EventId(*event))  // no error if event doesn't exist in archived resource
657
            &&  !cal->deleteEvent(*event, false))   // don't save calendar after deleting
658
                status.setError(UPDATE_ERROR);
659 660 661
            events[i] = newevent;
        }

662 663
        if (status.warnErr == count)
            status.status = UPDATE_FAILED;
664
        // Save the calendars, even if all events failed, since more than one calendar was updated
665 666
        if (!cal->save()  &&  status.status != UPDATE_FAILED)
            status.setError(SAVE_FAILED, count);
667
    }
668 669 670
    if (status.status != UPDATE_OK  &&  msgParent)
        displayUpdateError(msgParent, ERR_REACTIVATE, status, showKOrgErr);
    return status.status;
David Jarvie's avatar
David Jarvie committed
671 672
}

673
/******************************************************************************
674 675
* Enable or disable alarms in the calendar file and in every main window instance.
* The new events will have the same event IDs as the old ones.
676
*/
677
UpdateResult enableEvents(QVector<KAEvent>& events, bool enable, QWidget* msgParent)
678
{
Laurent Montel's avatar
Laurent Montel committed
679
    qCDebug(KALARM_LOG) << events.count();
680
    if (events.isEmpty())
681 682
        return UpdateResult(UPDATE_OK);
    UpdateStatusData status;
683
    AlarmCalendar* cal = AlarmCalendar::resources();
David Jarvie's avatar
David Jarvie committed
684 685
    bool deleteWakeFromSuspendAlarm = false;
    QString wakeFromSuspendId = checkRtcWakeConfig().value(0);
686 687 688
    for (int i = 0, end = events.count();  i < end;  ++i)
    {
        KAEvent* event = &events[i];
689
        if (event->category() == CalEvent::ACTIVE
690 691 692 693
        &&  enable != event->enabled())
        {
            event->setEnabled(enable);

David Jarvie's avatar
David Jarvie committed
694 695 696
            if (!enable  &&  event->id() == wakeFromSuspendId)
                deleteWakeFromSuspendAlarm = true;

697 698 699
            // Update the event in the calendar file
            KAEvent* newev = cal->updateEvent(event);
            if (!newev)
Laurent Montel's avatar
Laurent Montel committed
700
                qCCritical(KALARM_LOG) << "Error updating event in calendar:" << event->id();
701 702 703 704 705
            else
            {
                cal->disabledChanged(newev);

                // If we're disabling a display alarm, close any message window
706
                if (!enable  &&  (event->actionTypes() & KAEvent::ACT_DISPLAY))
707
                {
708
                    MessageWin* win = MessageWin::findEvent(EventId(*event));
709 710 711 712 713 714 715
                    delete win;
                }
            }
        }
    }

    if (!cal->save())
716 717 718
        status.setError(SAVE_FAILED, events.count());
    if (status.status != UPDATE_OK  &&  msgParent)
        displayUpdateError(msgParent, ERR_ADD, status);
David Jarvie's avatar
David Jarvie committed
719 720 721 722 723

    // Remove any wake-from-suspend scheduled for a disabled alarm
    if (deleteWakeFromSuspendAlarm  &&  !wakeFromSuspendId.isEmpty())
        cancelRtcWake(msgParent, wakeFromSuspendId);

724
    return status.status;
725 726 727
}

/******************************************************************************
728 729 730 731 732 733 734 735 736
* This method must only be called from the main KAlarm queue processing loop,
* to prevent asynchronous calendar operations interfering with one another.
*
* Purge all archived events from the default archived alarm resource whose end
* time is longer ago than 'purgeDays'. All events are deleted if 'purgeDays' is
* zero.
*/
void purgeArchive(int purgeDays)
{
737 738
    if (purgeDays < 0)
        return;
Laurent Montel's avatar
Laurent Montel committed
739
    qCDebug(KALARM_LOG) << purgeDays;
740
    QDate cutoff = KDateTime::currentLocalDate().addDays(-purgeDays);
741
    Collection collection = CollectionControlModel::getStandard(CalEvent::ARCHIVED);
742 743 744 745 746 747
    if (!collection.isValid())
        return;
    KAEvent::List events = AlarmCalendar::resources()->events(collection);
    for (int i = 0;  i < events.count();  )
    {
        if (purgeDays  &&  events[i]->createdDateTime().date() >= cutoff)
748
            events.remove(i);
749 750 751 752 753
        else
            ++i;
    }
    if (!events.isEmpty())
        AlarmCalendar::resources()->purgeEvents(events);   // delete the events and save the calendar
754 755
}

756 757 758 759 760 761 762
/******************************************************************************
* Display an error message about an error when saving an event.
* If 'model' is non-null, the AlarmListModel* which it points to is used; if
* that is null, it is created.
*/
QVector<KAEvent> getSortedActiveEvents(QObject* parent, AlarmListModel** model)
{
Laurent Montel's avatar
Laurent Montel committed
763
    AlarmListModel* mdl = Q_NULLPTR;
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
    if (!model)
        model = &mdl;
    if (!*model)
    {
        *model = new AlarmListModel(parent);
        (*model)->setEventTypeFilter(CalEvent::ACTIVE);
        (*model)->sort(AlarmListModel::TimeColumn);
    }
    QVector<KAEvent> result;
    for (int i = 0, count = (*model)->rowCount();  i < count;  ++i)
    {
        KAEvent event = (*model)->event(i);
        if (event.enabled()  &&  !event.expired())
            result += event;
    }
    return result;
}

782 783 784
/******************************************************************************
* Display an error message corresponding to a specified alarm update error code.
*/
785
void displayKOrgUpdateError(QWidget* parent, UpdateError code, UpdateResult korgError, int nAlarms)
786
{
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804
    QString errmsg;
    switch (code)
    {
        case ERR_ADD:
        case ERR_REACTIVATE:
            errmsg = (nAlarms > 1) ? i18nc("@info", "Unable to show alarms in KOrganizer")
                                   : i18nc("@info", "Unable to show alarm in KOrganizer");
            break;
        case ERR_MODIFY:
            errmsg = i18nc("@info", "Unable to update alarm in KOrganizer");
            break;
        case ERR_DELETE:
            errmsg = (nAlarms > 1) ? i18nc("@info", "Unable to delete alarms from KOrganizer")
                                   : i18nc("@info", "Unable to delete alarm from KOrganizer");
            break;
        case ERR_TEMPLATE:
            return;
    }
805
    bool showDetail = !korgError.message.isEmpty();
806
    QString msg;