messagewin.cpp 94.3 KB
Newer Older
1
2
3
/*
 *  messagewin.cpp  -  displays an alarm message
 *  Program:  kalarm
David Jarvie's avatar
David Jarvie committed
4
 *  Copyright © 2001-2014 by David Jarvie <djarvie@kde.org>
5
 *
David Jarvie's avatar
David Jarvie committed
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.
19
20
 */

Laurent Montel's avatar
Laurent Montel committed
21
//QT5 reactivate after porting #include "config-kdepim.h"
22
#include "kalarm.h"
Laurent Montel's avatar
Laurent Montel committed
23
24
#include "messagewin_p.h"
#include "messagewin.h"
25

26
#include "alarmcalendar.h"
27
#include "autoqpointer.h"
28
#include "collectionmodel.h"
29
30
31
32
33
34
#include "deferdlg.h"
#include "desktop.h"
#include "editdlg.h"
#include "functions.h"
#include "kalarmapp.h"
#include "mainwindow.h"
35
#include "messagebox.h"
36
#include "preferences.h"
37
#include "pushbutton.h"
38
39
#include "shellprocess.h"
#include "synchtimer.h"
40

Laurent Montel's avatar
Laurent Montel committed
41
#include <kpimtextedit/texttospeech.h>
42
43
44
//QT5 reactivate after porting (activated by config-kdepim.h include in texttospeech.h)
#undef KDEPIM_HAVE_X11

Laurent Montel's avatar
Laurent Montel committed
45
#include <K4AboutData>
Laurent Montel's avatar
Laurent Montel committed
46
47
#include <KLocale>
#include <kstandardguiitem.h>
Laurent Montel's avatar
Laurent Montel committed
48
#include <KLocalizedString>
David Jarvie's avatar
David Jarvie committed
49
#include <kconfig.h>
David Jarvie's avatar
David Jarvie committed
50
#include <kiconloader.h>
51
#include <ksystemtimezone.h>
52
#include <ktextedit.h>
Luboš Luňák's avatar
Luboš Luňák committed
53
#include <kwindowsystem.h>
54
55
#include <KIO/StoredTransferJob>
#include <KJobWidgets>
56
#include <knotification.h>
57
#include <ksqueezedtextlabel.h>
David Jarvie's avatar
David Jarvie committed
58
59
60
#include <phonon/mediaobject.h>
#include <phonon/audiooutput.h>
#include <phonon/volumefadereffect.h>
Laurent Montel's avatar
Laurent Montel committed
61
#if KDEPIM_HAVE_X11
62
#include <netwm.h>
Laurent Montel's avatar
Laurent Montel committed
63
#include <qx11info_x11.h>
64
#endif
65

David Jarvie's avatar
David Jarvie committed
66
67
#include <qtextbrowser.h>
#include <QPushButton>
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <QScrollBar>
#include <QtDBus/QtDBus>
#include <QFile>
#include <QFileInfo>
#include <QCheckBox>
#include <QLabel>
#include <QPalette>
#include <QTimer>
#include <QPixmap>
#include <QByteArray>
#include <QFrame>
#include <QGridLayout>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QResizeEvent>
#include <QCloseEvent>
#include <QDesktopWidget>
#include <QMutexLocker>
86
#include <QMimeDatabase>
Daniel Vrátil's avatar
Daniel Vrátil committed
87
#include <QUrl>
Laurent Montel's avatar
Laurent Montel committed
88
#include "kalarm_debug.h"
89
90
91
92

#include <stdlib.h>
#include <string.h>

93
using namespace KCalCore;
94
using namespace KAlarmCal;
David Jarvie's avatar
David Jarvie committed
95

Laurent Montel's avatar
Laurent Montel committed
96
#if KDEPIM_HAVE_X11
97
98
99
100
101
enum FullScreenType { NoFullScreen = 0, FullScreen = 1, FullScreenActive = 2 };
static FullScreenType haveFullScreenWindow(int screen);
static FullScreenType findFullScreenWindows(const QVector<QRect>& screenRects, QVector<FullScreenType>& screenTypes);
#endif

102
#include "kmailinterface.h"
Laurent Montel's avatar
Laurent Montel committed
103
104
105
static const QLatin1String KMAIL_DBUS_SERVICE("org.kde.kmail");
static const QLatin1String KMAIL_DBUS_PATH("/KMail");
 
106
107
108
109
// The delay for enabling message window buttons if a zero delay is
// configured, i.e. the windows are placed far from the cursor.
static const int proximityButtonDelay = 1000;    // (milliseconds)
static const int proximityMultiple = 10;         // multiple of button height distance from cursor for proximity
110

David Jarvie's avatar
David Jarvie committed
111
// A text label widget which can be scrolled and copied with the mouse
112
class MessageText : public KTextEdit
David Jarvie's avatar
David Jarvie committed
113
{
114
    public:
Laurent Montel's avatar
Laurent Montel committed
115
        MessageText(QWidget* parent = Q_NULLPTR)
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
            : KTextEdit(parent),
              mNewLine(false)
        {
            setReadOnly(true);
            setFrameStyle(NoFrame);
            setLineWrapMode(NoWrap);
        }
        int scrollBarHeight() const     { return horizontalScrollBar()->height(); }
        int scrollBarWidth() const      { return verticalScrollBar()->width(); }
        void setBackgroundColour(const QColor& c)
        {
            QPalette pal = viewport()->palette();
            pal.setColor(viewport()->backgroundRole(), c);
            viewport()->setPalette(pal);
        }
Laurent Montel's avatar
Laurent Montel committed
131
        QSize sizeHint() const Q_DECL_OVERRIDE
132
        {
David Jarvie's avatar
David Jarvie committed
133
            const QSizeF docsize = document()->size();
134
135
136
137
138
139
140
            return QSize(static_cast<int>(docsize.width() + 0.99) + verticalScrollBar()->width(),
                         static_cast<int>(docsize.height() + 0.99) + horizontalScrollBar()->height());
        }
        bool newLine() const       { return mNewLine; }
        void setNewLine(bool nl)   { mNewLine = nl; }
    private:
        bool mNewLine;
David Jarvie's avatar
David Jarvie committed
141
142
143
};


144
// Basic flags for the window
145
146
static const Qt::WindowFlags          WFLAGS       = Qt::WindowStaysOnTopHint;
static const Qt::WindowFlags          WFLAGS2      = Qt::WindowContextHelpButtonHint;
David Jarvie's avatar
David Jarvie committed
147
static const Qt::WidgetAttribute WidgetFlags  = Qt::WA_DeleteOnClose;
148

149
150
// Error message bit masks
enum {
151
152
    ErrMsg_Speak     = 0x01,
    ErrMsg_AudioFile = 0x02
153
154
};

David Jarvie's avatar
David Jarvie committed
155

David Jarvie's avatar
David Jarvie committed
156
QList<MessageWin*> MessageWin::mWindowList;
157
QMap<EventId, unsigned> MessageWin::mErrorMessages;
158
bool                    MessageWin::mRedisplayed = false;
159
160
161
162
// There can only be one audio thread at a time: trying to play multiple
// sound files simultaneously would result in a cacophony, and besides
// that, Phonon currently crashes...
QPointer<AudioThread> MessageWin::mAudioThread;
Laurent Montel's avatar
Laurent Montel committed
163
MessageWin*           AudioThread::mAudioOwner = Q_NULLPTR;
164

165
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
166
167
168
169
* Construct the message window for the specified alarm.
* Other alarms in the supplied event may have been updated by the caller, so
* the whole event needs to be stored for updating the calendar file when it is
* displayed.
170
*/
171
MessageWin::MessageWin(const KAEvent* event, const KAAlarm& alarm, int flags)
Laurent Montel's avatar
Laurent Montel committed
172
    : MainWindowBase(Q_NULLPTR, static_cast<Qt::WindowFlags>(WFLAGS | WFLAGS2 | ((flags & ALWAYS_HIDE) || getWorkAreaAndModal() ? Qt::WindowType(0) : Qt::X11BypassWindowManagerHint))),
173
174
175
176
177
      mMessage(event->cleanText()),
      mFont(event->font()),
      mBgColour(event->bgColour()),
      mFgColour(event->fgColour()),
      mEventItemId(event->itemId()),
178
      mEventId(*event),
179
180
181
182
183
184
      mAudioFile(event->audioFile()),
      mVolume(event->soundVolume()),
      mFadeVolume(event->fadeVolume()),
      mFadeSeconds(qMin(event->fadeSeconds(), 86400)),
      mDefaultDeferMinutes(event->deferDefaultMinutes()),
      mAlarmType(alarm.type()),
185
      mAction(event->actionSubType()),
186
187
188
      mKMailSerialNumber(event->kmailSerialNumber()),
      mCommandError(event->commandError()),
      mRestoreHeight(0),
189
      mAudioRepeatPause(event->repeatSoundPause()),
190
191
192
193
      mConfirmAck(event->confirmAck()),
      mNoDefer(true),
      mInvalid(false),
      mEvent(*event),
194
      mOriginalEvent(*event),
195
      mCollection(AlarmCalendar::resources()->collectionForEvent(mEventItemId)),
Laurent Montel's avatar
Laurent Montel committed
196
197
198
199
200
201
202
203
204
205
      mTimeLabel(Q_NULLPTR),
      mRemainingText(Q_NULLPTR),
      mEditButton(Q_NULLPTR),
      mDeferButton(Q_NULLPTR),
      mSilenceButton(Q_NULLPTR),
      mKMailButton(Q_NULLPTR),
      mCommandText(Q_NULLPTR),
      mDontShowAgainCheck(Q_NULLPTR),
      mEditDlg(Q_NULLPTR),
      mDeferDlg(Q_NULLPTR),
206
207
208
209
210
211
212
213
214
215
216
217
218
      mAlwaysHide(flags & ALWAYS_HIDE),
      mErrorWindow(false),
      mInitialised(false),
      mNoPostAction(alarm.type() & KAAlarm::REMINDER_ALARM),
      mRecreating(false),
      mBeep(event->beep()),
      mSpeak(event->speak()),
      mRescheduleEvent(!(flags & NO_RESCHEDULE)),
      mShown(false),
      mPositioning(false),
      mNoCloseConfirm(false),
      mDisableDeferral(false)
{
Laurent Montel's avatar
Laurent Montel committed
219
    qCDebug(KALARM_LOG) << (void*)this << "event" << mEventId;
220
221
    setAttribute(static_cast<Qt::WidgetAttribute>(WidgetFlags));
    setWindowModality(Qt::WindowModal);
David Jarvie's avatar
David Jarvie committed
222
    setObjectName(QStringLiteral("MessageWin"));    // used by LikeBack
223
224
225
226
227
228
229
230
231
232
233
234
235
    if (alarm.type() & KAAlarm::REMINDER_ALARM)
    {
        if (event->reminderMinutes() < 0)
        {
            event->previousOccurrence(alarm.dateTime(false).effectiveKDateTime(), mDateTime, false);
            if (!mDateTime.isValid()  &&  event->repeatAtLogin())
                mDateTime = alarm.dateTime().addSecs(event->reminderMinutes() * 60);
        }
        else
            mDateTime = event->mainDateTime(true);
    }
    else
        mDateTime = alarm.dateTime(true);
236
237
    if (!(flags & (NO_INIT_VIEW | ALWAYS_HIDE)))
    {
David Jarvie's avatar
David Jarvie committed
238
        const bool readonly = AlarmCalendar::resources()->eventReadOnly(mEventItemId);
239
        mShowEdit = !mEventId.isEmpty()  &&  !readonly;
240
241
242
243
244
        mNoDefer  = readonly || (flags & NO_DEFER) || alarm.repeatAtLogin();
        initView();
    }
    // Set to save settings automatically, but don't save window size.
    // File alarm window size is saved elsewhere.
David Jarvie's avatar
David Jarvie committed
245
    setAutoSaveSettings(QStringLiteral("MessageWin"), false);
246
247
    mWindowList.append(this);
    if (event->autoClose())
248
        mCloseTime = alarm.dateTime().effectiveKDateTime().toUtc().dateTime().addSecs(event->lateCancel() * 60);
249
250
251
252
253
    if (mAlwaysHide)
    {
        hide();
        displayComplete();    // play audio, etc.
    }
David Jarvie's avatar
David Jarvie committed
254
255
}

256
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
257
258
259
* Display an error message window.
* If 'dontShowAgain' is non-null, a "Don't show again" option is displayed. Note
* that the option is specific to 'event'.
260
261
262
263
*/
void MessageWin::showError(const KAEvent& event, const DateTime& alarmDateTime,
                           const QStringList& errmsgs, const QString& dontShowAgain)
{
264
265
    if (!dontShowAgain.isEmpty()
    &&  KAlarm::dontShowErrors(EventId(event), dontShowAgain))
266
        return;
267

268
269
270
    // Don't pile up duplicate error messages for the same alarm
    for (int i = 0, end = mWindowList.count();  i < end;  ++i)
    {
David Jarvie's avatar
David Jarvie committed
271
        const MessageWin* w = mWindowList[i];
272
273
        if (w->mErrorWindow  &&  w->mEventId == EventId(event)
        &&  w->mErrorMsgs == errmsgs  &&  w->mDontShowAgain == dontShowAgain)
274
275
            return;
    }
276

277
    (new MessageWin(&event, alarmDateTime, errmsgs, dontShowAgain))->show();
278
279
}

David Jarvie's avatar
David Jarvie committed
280
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
281
282
283
* Construct the message window for a specified error message.
* If 'dontShowAgain' is non-null, a "Don't show again" option is displayed. Note
* that the option is specific to 'event'.
David Jarvie's avatar
David Jarvie committed
284
*/
285
MessageWin::MessageWin(const KAEvent* event, const DateTime& alarmDateTime,
286
                       const QStringList& errmsgs, const QString& dontShowAgain)
Laurent Montel's avatar
Laurent Montel committed
287
    : MainWindowBase(Q_NULLPTR, WFLAGS | WFLAGS2),
288
289
290
      mMessage(event->cleanText()),
      mDateTime(alarmDateTime),
      mEventItemId(event->itemId()),
291
      mEventId(*event),
292
      mAlarmType(KAAlarm::MAIN_ALARM),
293
      mAction(event->actionSubType()),
294
295
296
297
298
299
300
301
302
303
      mKMailSerialNumber(0),
      mCommandError(KAEvent::CMD_NO_ERROR),
      mErrorMsgs(errmsgs),
      mDontShowAgain(dontShowAgain),
      mRestoreHeight(0),
      mConfirmAck(false),
      mShowEdit(false),
      mNoDefer(true),
      mInvalid(false),
      mEvent(*event),
304
      mOriginalEvent(*event),
Laurent Montel's avatar
Laurent Montel committed
305
306
307
308
309
310
311
312
313
314
      mTimeLabel(Q_NULLPTR),
      mRemainingText(Q_NULLPTR),
      mEditButton(Q_NULLPTR),
      mDeferButton(Q_NULLPTR),
      mSilenceButton(Q_NULLPTR),
      mKMailButton(Q_NULLPTR),
      mCommandText(Q_NULLPTR),
      mDontShowAgainCheck(Q_NULLPTR),
      mEditDlg(Q_NULLPTR),
      mDeferDlg(Q_NULLPTR),
315
316
317
318
319
320
321
322
323
324
325
      mAlwaysHide(false),
      mErrorWindow(true),
      mInitialised(false),
      mNoPostAction(true),
      mRecreating(false),
      mRescheduleEvent(false),
      mShown(false),
      mPositioning(false),
      mNoCloseConfirm(false),
      mDisableDeferral(false)
{
Laurent Montel's avatar
Laurent Montel committed
326
    qCDebug(KALARM_LOG) << "errmsg";
327
328
    setAttribute(static_cast<Qt::WidgetAttribute>(WidgetFlags));
    setWindowModality(Qt::WindowModal);
David Jarvie's avatar
David Jarvie committed
329
    setObjectName(QStringLiteral("ErrorWin"));    // used by LikeBack
330
331
332
    getWorkAreaAndModal();
    initView();
    mWindowList.append(this);
David Jarvie's avatar
David Jarvie committed
333
334
}

David Jarvie's avatar
David Jarvie committed
335
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
336
337
* Construct the message window for restoration by session management.
* The window is initialised by readProperties().
David Jarvie's avatar
David Jarvie committed
338
339
*/
MessageWin::MessageWin()
Laurent Montel's avatar
Laurent Montel committed
340
341
342
343
344
345
346
347
348
349
350
    : MainWindowBase(Q_NULLPTR, WFLAGS),
      mTimeLabel(Q_NULLPTR),
      mRemainingText(Q_NULLPTR),
      mEditButton(Q_NULLPTR),
      mDeferButton(Q_NULLPTR),
      mSilenceButton(Q_NULLPTR),
      mKMailButton(Q_NULLPTR),
      mCommandText(Q_NULLPTR),
      mDontShowAgainCheck(Q_NULLPTR),
      mEditDlg(Q_NULLPTR),
      mDeferDlg(Q_NULLPTR),
351
352
353
354
355
356
357
358
359
360
      mAlwaysHide(false),
      mErrorWindow(false),
      mInitialised(false),
      mRecreating(false),
      mRescheduleEvent(false),
      mShown(false),
      mPositioning(false),
      mNoCloseConfirm(false),
      mDisableDeferral(false)
{
Laurent Montel's avatar
Laurent Montel committed
361
    qCDebug(KALARM_LOG) << (void*)this << "restore";
362
363
    setAttribute(WidgetFlags);
    setWindowModality(Qt::WindowModal);
David Jarvie's avatar
David Jarvie committed
364
    setObjectName(QStringLiteral("RestoredMsgWin"));    // used by LikeBack
365
366
    getWorkAreaAndModal();
    mWindowList.append(this);
David Jarvie's avatar
David Jarvie committed
367
}
368

369
370
371
/******************************************************************************
* Destructor. Perform any post-alarm actions before tidying up.
*/
David Jarvie's avatar
David Jarvie committed
372
373
MessageWin::~MessageWin()
{
Laurent Montel's avatar
Laurent Montel committed
374
    qCDebug(KALARM_LOG) << (void*)this << mEventId;
375
    if (AudioThread::mAudioOwner == this  &&  !mAudioThread.isNull())
376
        mAudioThread->quit();
377
    mErrorMessages.remove(mEventId);
378
379
380
381
382
    mWindowList.removeAll(this);
    if (!mRecreating)
    {
        if (!mNoPostAction  &&  !mEvent.postAction().isEmpty())
            theApp()->alarmCompleted(mEvent);
383
384
        if (!instanceCount(true))
            theApp()->quitIf();   // no visible windows remain - check whether to quit
385
    }
David Jarvie's avatar
David Jarvie committed
386
}
387

David Jarvie's avatar
David Jarvie committed
388
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
389
* Construct the message window.
David Jarvie's avatar
David Jarvie committed
390
*/
391
void MessageWin::initView()
David Jarvie's avatar
David Jarvie committed
392
{
David Jarvie's avatar
David Jarvie committed
393
394
    const bool reminder = (!mErrorWindow  &&  (mAlarmType & KAAlarm::REMINDER_ALARM));
    const int leading = fontMetrics().leading();
395
396
397
398
    setCaption((mAlarmType & KAAlarm::REMINDER_ALARM) ? i18nc("@title:window", "Reminder") : i18nc("@title:window", "Message"));
    QWidget* topWidget = new QWidget(this);
    setCentralWidget(topWidget);
    QVBoxLayout* topLayout = new QVBoxLayout(topWidget);
399
400
    topLayout->setMargin(style()->pixelMetric(QStyle::PM_DefaultChildMargin));
    topLayout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
401
402
403
404

    QPalette labelPalette = palette();
    labelPalette.setColor(backgroundRole(), labelPalette.color(QPalette::Window));

405
    // Show the alarm date/time, together with a reminder text where appropriate.
406
407
408
409
410
411
412
413
414
415
416
417
418
419
    // Alarm date/time: display time zone if not local time zone.
    mTimeLabel = new QLabel(topWidget);
    mTimeLabel->setText(dateTimeToDisplay());
    mTimeLabel->setFrameStyle(QFrame::StyledPanel);
    mTimeLabel->setPalette(labelPalette);
    mTimeLabel->setAutoFillBackground(true);
    topLayout->addWidget(mTimeLabel, 0, Qt::AlignHCenter);
    mTimeLabel->setWhatsThis(i18nc("@info:whatsthis", "The scheduled date/time for the message (as opposed to the actual time of display)."));

    if (mDateTime.isValid())
    {
        // Reminder
        if (reminder)
        {
David Jarvie's avatar
David Jarvie committed
420
421
422
            // Create a label "time\nReminder" by inserting the time at the
            // start of the translated string, allowing for possible HTML tags
            // enclosing "Reminder".
423
            QString s = i18nc("@info", "Reminder");
David Jarvie's avatar
David Jarvie committed
424
            QRegExp re(QStringLiteral("^(<[^>]+>)*"));
425
            re.indexIn(s);
Laurent Montel's avatar
Laurent Montel committed
426
            s.insert(re.matchedLength(), mTimeLabel->text() + QLatin1String("<br/>"));
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
            mTimeLabel->setText(s);
            mTimeLabel->setAlignment(Qt::AlignHCenter);
        }
    }
    else
        mTimeLabel->hide();

    if (!mErrorWindow)
    {
        // It's a normal alarm message window
        switch (mAction)
        {
            case KAEvent::FILE:
            {
                // Display the file name
                KSqueezedTextLabel* label = new KSqueezedTextLabel(mMessage, topWidget);
                label->setFrameStyle(QFrame::StyledPanel);
                label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
                label->setPalette(labelPalette);
                label->setAutoFillBackground(true);
                label->setWhatsThis(i18nc("@info:whatsthis", "The file whose contents are displayed below"));
                topLayout->addWidget(label, 0, Qt::AlignHCenter);

                // Display contents of file
Daniel Vrátil's avatar
Daniel Vrátil committed
451
                const QUrl url = QUrl::fromUserInput(mMessage, QString(), QUrl::AssumeLocalFile);
452

Daniel Vrátil's avatar
Daniel Vrátil committed
453
                auto statJob =  KIO::stat(url, KIO::StatJob::SourceSide, 0, KIO::HideProgressInfo);
454
455
456
457
458
                const bool exists = statJob->exec();
                const bool isDir = statJob->statResult().isDir();

                bool opened = false;
                if (exists && !isDir) {
Daniel Vrátil's avatar
Daniel Vrátil committed
459
                    auto job = KIO::storedGet(url);
460
461
                    KJobWidgets::setWindow(job, MainWindow::mainMainWindow());
                    if (job->exec()) {
462
                        opened = true;
463
464
465
466
467
                        const QByteArray data = job->data();
                        QTemporaryFile tmpFile;
                        tmpFile.write(data);
                        tmpFile.seek(0);

Laurent Montel's avatar
Laurent Montel committed
468
                        QTextBrowser* view = new QTextBrowser(topWidget);
469
470
471
472
473
474
475
                        view->setFrameStyle(QFrame::NoFrame);
                        view->setWordWrapMode(QTextOption::NoWrap);
                        QPalette pal = view->viewport()->palette();
                        pal.setColor(view->viewport()->backgroundRole(), mBgColour);
                        view->viewport()->setPalette(pal);
                        view->setTextColor(mFgColour);
                        view->setCurrentFont(mFont);
476
477
478
479
                        QMimeDatabase db;
                        QMimeType mime = db.mimeTypeForUrl(url);
                        if (mime.name() == QLatin1String("application/octet-stream"))
                            mime = db.mimeTypeForData(&tmpFile);
480
481
482
                        switch (KAlarm::fileType(mime))
                        {
                            case KAlarm::Image:
483
                                view->setHtml(QLatin1String("<img source=\"") + tmpFile.fileName() + QLatin1String("\">"));
484
485
                                break;
                            case KAlarm::TextFormatted:
486
                                view->QTextBrowser::setSource(QUrl::fromLocalFile(tmpFile.fileName()));   //krazy:exclude=qclasses
487
488
489
                                break;
                            default:
                            {
490
                                view->setPlainText(QString::fromUtf8(data));
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
                                break;
                            }
                        }
                        view->setMinimumSize(view->sizeHint());
                        topLayout->addWidget(view);

                        // Set the default size to 20 lines square.
                        // Note that after the first file has been displayed, this size
                        // is overridden by the user-set default stored in the config file.
                        // So there is no need to calculate an accurate size.
                        int h = 20*view->fontMetrics().lineSpacing() + 2*view->frameWidth();
                        view->resize(QSize(h, h).expandedTo(view->sizeHint()));
                        view->setWhatsThis(i18nc("@info:whatsthis", "The contents of the file to be displayed"));
                    }
                }
506
507
508

                if (!exists || isDir || !opened) {
                    mErrorMsgs += isDir ? i18nc("@info", "File is a folder") : exists ? i18nc("@info", "Failed to open file") : i18nc("@info", "File not found");
509
510
511
512
513
514
515
516
517
518
519
520
521
                }
                break;
            }
            case KAEvent::MESSAGE:
            {
                // Message label
                // Using MessageText instead of QLabel allows scrolling and mouse copying
                MessageText* text = new MessageText(topWidget);
                text->setAutoFillBackground(true);
                text->setBackgroundColour(mBgColour);
                text->setTextColor(mFgColour);
                text->setCurrentFont(mFont);
                text->insertPlainText(mMessage);
David Jarvie's avatar
David Jarvie committed
522
523
524
                const int lineSpacing = text->fontMetrics().lineSpacing();
                const QSize s = text->sizeHint();
                const int h = s.height();
525
526
527
528
                text->setMaximumHeight(h + text->scrollBarHeight());
                text->setMinimumHeight(qMin(h, lineSpacing*4));
                text->setMaximumWidth(s.width() + text->scrollBarWidth());
                text->setWhatsThis(i18nc("@info:whatsthis", "The alarm message"));
David Jarvie's avatar
David Jarvie committed
529
                const int vspace = lineSpacing/2;
530
                const int hspace = lineSpacing - style()->pixelMetric(QStyle::PM_DefaultChildMargin);
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
                topLayout->addSpacing(vspace);
                topLayout->addStretch();
                // Don't include any horizontal margins if message is 2/3 screen width
                if (text->sizeHint().width() >= KAlarm::desktopWorkArea(mScreenNumber).width()*2/3)
                    topLayout->addWidget(text, 1, Qt::AlignHCenter);
                else
                {
                    QHBoxLayout* layout = new QHBoxLayout();
                    layout->addSpacing(hspace);
                    layout->addWidget(text, 1, Qt::AlignHCenter);
                    layout->addSpacing(hspace);
                    topLayout->addLayout(layout);
                }
                if (!reminder)
                    topLayout->addStretch();
                break;
            }
            case KAEvent::COMMAND:
            {
                mCommandText = new MessageText(topWidget);
                mCommandText->setBackgroundColour(mBgColour);
                mCommandText->setTextColor(mFgColour);
                mCommandText->setCurrentFont(mFont);
                topLayout->addWidget(mCommandText);
                mCommandText->setWhatsThis(i18nc("@info:whatsthis", "The output of the alarm's command"));
                theApp()->execCommandAlarm(mEvent, mEvent.alarm(mAlarmType), this, SLOT(readProcessOutput(ShellProcess*)));
                break;
            }
            case KAEvent::EMAIL:
            default:
                break;
        }

564
        if (reminder  &&  mEvent.reminderMinutes() > 0)
565
        {
566
            // Advance reminder: show remaining time until the actual alarm
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
            mRemainingText = new QLabel(topWidget);
            mRemainingText->setFrameStyle(QFrame::Box | QFrame::Raised);
            mRemainingText->setMargin(leading);
            mRemainingText->setPalette(labelPalette);
            mRemainingText->setAutoFillBackground(true);
            if (mDateTime.isDateOnly()  ||  KDateTime::currentLocalDate().daysTo(mDateTime.date()) > 0)
            {
                setRemainingTextDay();
                MidnightTimer::connect(this, SLOT(setRemainingTextDay()));    // update every day
            }
            else
            {
                setRemainingTextMinute();
                MinuteTimer::connect(this, SLOT(setRemainingTextMinute()));   // update every minute
            }
            topLayout->addWidget(mRemainingText, 0, Qt::AlignHCenter);
583
            topLayout->addSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
            topLayout->addStretch();
        }
    }
    else
    {
        // It's an error message
        switch (mAction)
        {
            case KAEvent::EMAIL:
            {
                // Display the email addresses and subject.
                QFrame* frame = new QFrame(topWidget);
                frame->setFrameStyle(QFrame::Box | QFrame::Raised);
                frame->setWhatsThis(i18nc("@info:whatsthis", "The email to send"));
                topLayout->addWidget(frame, 0, Qt::AlignHCenter);
                QGridLayout* grid = new QGridLayout(frame);
600
601
                grid->setMargin(style()->pixelMetric(QStyle::PM_DefaultChildMargin));
                grid->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
602
603
604
605

                QLabel* label = new QLabel(i18nc("@info Email addressee", "To:"), frame);
                label->setFixedSize(label->sizeHint());
                grid->addWidget(label, 0, 0, Qt::AlignLeft);
David Jarvie's avatar
David Jarvie committed
606
                label = new QLabel(mEvent.emailAddresses(QStringLiteral("\n")), frame);
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
                label->setFixedSize(label->sizeHint());
                grid->addWidget(label, 0, 1, Qt::AlignLeft);

                label = new QLabel(i18nc("@info Email subject", "Subject:"), frame);
                label->setFixedSize(label->sizeHint());
                grid->addWidget(label, 1, 0, Qt::AlignLeft);
                label = new QLabel(mEvent.emailSubject(), frame);
                label->setFixedSize(label->sizeHint());
                grid->addWidget(label, 1, 1, Qt::AlignLeft);
                break;
            }
            case KAEvent::COMMAND:
            case KAEvent::FILE:
            case KAEvent::MESSAGE:
            default:
                // Just display the error message strings
                break;
        }
    }

    if (!mErrorMsgs.count())
    {
        topWidget->setAutoFillBackground(true);
        QPalette palette = topWidget->palette();
        palette.setColor(topWidget->backgroundRole(), mBgColour);
        topWidget->setPalette(palette);
    }
    else
    {
        setCaption(i18nc("@title:window", "Error"));
        QHBoxLayout* layout = new QHBoxLayout();
638
        layout->setMargin(2 * style()->pixelMetric(QStyle::PM_DefaultChildMargin));
639
640
        layout->addStretch();
        topLayout->addLayout(layout);
641
        QLabel* label = new QLabel(topWidget);
Laurent Montel's avatar
Laurent Montel committed
642
        label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-error")).pixmap(IconSize(KIconLoader::Desktop), IconSize(KIconLoader::Desktop)));
643
644
645
646
        label->setFixedSize(label->sizeHint());
        layout->addWidget(label, 0, Qt::AlignRight);
        QVBoxLayout* vlayout = new QVBoxLayout();
        layout->addLayout(vlayout);
David Jarvie's avatar
David Jarvie committed
647
        for (QStringList::ConstIterator it = mErrorMsgs.constBegin();  it != mErrorMsgs.constEnd();  ++it)
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
        {
            label = new QLabel(*it, topWidget);
            label->setFixedSize(label->sizeHint());
            vlayout->addWidget(label, 0, Qt::AlignLeft);
        }
        layout->addStretch();
        if (!mDontShowAgain.isEmpty())
        {
            mDontShowAgainCheck = new QCheckBox(i18nc("@option:check", "Do not display this error message again for this alarm"), topWidget);
            mDontShowAgainCheck->setFixedSize(mDontShowAgainCheck->sizeHint());
            topLayout->addWidget(mDontShowAgainCheck, 0, Qt::AlignLeft);
        }
    }

    QGridLayout* grid = new QGridLayout();
    grid->setColumnStretch(0, 1);     // keep the buttons right-adjusted in the window
    topLayout->addLayout(grid);
    int gridIndex = 1;

    // Close button
    mOkButton = new PushButton(KStandardGuiItem::close(), topWidget);
    // Prevent accidental acknowledgement of the message if the user is typing when the window appears
    mOkButton->clearFocus();
    mOkButton->setFocusPolicy(Qt::ClickFocus);    // don't allow keyboard selection
    mOkButton->setFixedSize(mOkButton->sizeHint());
673
    connect(mOkButton, &QAbstractButton::clicked, this, &MessageWin::slotOk);
674
675
676
677
678
679
680
681
682
    grid->addWidget(mOkButton, 0, gridIndex++, Qt::AlignHCenter);
    mOkButton->setWhatsThis(i18nc("@info:whatsthis", "Acknowledge the alarm"));

    if (mShowEdit)
    {
        // Edit button
        mEditButton = new PushButton(i18nc("@action:button", "&Edit..."), topWidget);
        mEditButton->setFocusPolicy(Qt::ClickFocus);    // don't allow keyboard selection
        mEditButton->setFixedSize(mEditButton->sizeHint());
683
        connect(mEditButton, &QAbstractButton::clicked, this, &MessageWin::slotEdit);
684
685
686
687
688
689
690
691
        grid->addWidget(mEditButton, 0, gridIndex++, Qt::AlignHCenter);
        mEditButton->setWhatsThis(i18nc("@info:whatsthis", "Edit the alarm."));
    }

    // Defer button
    mDeferButton = new PushButton(i18nc("@action:button", "&Defer..."), topWidget);
    mDeferButton->setFocusPolicy(Qt::ClickFocus);    // don't allow keyboard selection
    mDeferButton->setFixedSize(mDeferButton->sizeHint());
692
    connect(mDeferButton, &QAbstractButton::clicked, this, &MessageWin::slotDefer);
693
    grid->addWidget(mDeferButton, 0, gridIndex++, Qt::AlignHCenter);
Laurent Montel's avatar
Laurent Montel committed
694
    mDeferButton->setWhatsThis(xi18nc("@info:whatsthis", "<para>Defer the alarm until later.</para>"
695
696
697
698
699
700
701
702
703
704
                                    "<para>You will be prompted to specify when the alarm should be redisplayed.</para>"));

    if (mNoDefer)
        mDeferButton->hide();
    else
        setDeferralLimit(mEvent);    // ensure that button is disabled when alarm can't be deferred any more

    if (!mAudioFile.isEmpty()  &&  (mVolume || mFadeVolume > 0))
    {
        // Silence button to stop sound repetition
David Jarvie's avatar
David Jarvie committed
705
        const QPixmap pixmap = MainBarIcon(QStringLiteral("media-playback-stop"));
706
        mSilenceButton = new PushButton(topWidget);
Laurent Montel's avatar
Laurent Montel committed
707
        mSilenceButton->setIcon(pixmap);
708
709
710
711
712
713
714
715
716
717
718
        grid->addWidget(mSilenceButton, 0, gridIndex++, Qt::AlignHCenter);
        mSilenceButton->setToolTip(i18nc("@info:tooltip", "Stop sound"));
        mSilenceButton->setWhatsThis(i18nc("@info:whatsthis", "Stop playing the sound"));
        // To avoid getting in a mess, disable the button until sound playing has been set up
        mSilenceButton->setEnabled(false);
    }

    KIconLoader iconLoader;
    if (mKMailSerialNumber)
    {
        // KMail button
David Jarvie's avatar
David Jarvie committed
719
        const QPixmap pixmap = iconLoader.loadIcon(QStringLiteral("internet-mail"), KIconLoader::MainToolbar);
720
        mKMailButton = new PushButton(topWidget);
Laurent Montel's avatar
Laurent Montel committed
721
        mKMailButton->setIcon(pixmap);
722
        connect(mKMailButton, &QAbstractButton::clicked, this, &MessageWin::slotShowKMailMessage);
723
        grid->addWidget(mKMailButton, 0, gridIndex++, Qt::AlignHCenter);
Laurent Montel's avatar
Laurent Montel committed
724
725
        mKMailButton->setToolTip(xi18nc("@info:tooltip Locate this email in KMail", "Locate in <application>KMail</application>"));
        mKMailButton->setWhatsThis(xi18nc("@info:whatsthis", "Locate and highlight this email in <application>KMail</application>"));
726
727
728
    }

    // KAlarm button
Laurent Montel's avatar
Laurent Montel committed
729
    const QPixmap pixmap = iconLoader.loadIcon(KComponentData::mainComponent().aboutData()->appName(), KIconLoader::MainToolbar);
730
    mKAlarmButton = new PushButton(topWidget);
Laurent Montel's avatar
Laurent Montel committed
731
    mKAlarmButton->setIcon(pixmap);
732
    connect(mKAlarmButton, &QAbstractButton::clicked, this, &MessageWin::displayMainWindow);
733
    grid->addWidget(mKAlarmButton, 0, gridIndex++, Qt::AlignHCenter);
Laurent Montel's avatar
Laurent Montel committed
734
735
    mKAlarmButton->setToolTip(xi18nc("@info:tooltip", "Activate <application>KAlarm</application>"));
    mKAlarmButton->setWhatsThis(xi18nc("@info:whatsthis", "Activate <application>KAlarm</application>"));
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759

    int butsize = mKAlarmButton->sizeHint().height();
    if (mSilenceButton)
        butsize = qMax(butsize, mSilenceButton->sizeHint().height());
    if (mKMailButton)
        butsize = qMax(butsize, mKMailButton->sizeHint().height());
    mKAlarmButton->setFixedSize(butsize, butsize);
    if (mSilenceButton)
        mSilenceButton->setFixedSize(butsize, butsize);
    if (mKMailButton)
        mKMailButton->setFixedSize(butsize, butsize);

    // Disable all buttons initially, to prevent accidental clicking on if they happen to be
    // under the mouse just as the window appears.
    mOkButton->setEnabled(false);
    if (mDeferButton->isVisible())
        mDeferButton->setEnabled(false);
    if (mEditButton)
        mEditButton->setEnabled(false);
    if (mKMailButton)
        mKMailButton->setEnabled(false);
    mKAlarmButton->setEnabled(false);

    topLayout->activate();
760
761
    setMinimumSize(QSize(grid->sizeHint().width() + 2 * style()->pixelMetric(QStyle::PM_DefaultChildMargin),
                         sizeHint().height()));
David Jarvie's avatar
David Jarvie committed
762
763
    const bool modal = !(windowFlags() & Qt::X11BypassWindowManagerHint);
    const unsigned long wstate = (modal ? NET::Modal : 0) | NET::Sticky | NET::StaysOnTop;
764
    WId winid = winId();
Laurent Montel's avatar
Laurent Montel committed
765
    //QT5 KWindowSystem::setState(winid, wstate);
766
767
768
    KWindowSystem::setOnAllDesktops(winid, true);

    mInitialised = true;   // the window's widgets have been created
769
770
}

771
772
773
774
775
/******************************************************************************
* Return the number of message windows, optionally excluding always-hidden ones.
*/
int MessageWin::instanceCount(bool excludeAlwaysHidden)
{
776
777
778
779
780
781
782
783
784
785
    int count = mWindowList.count();
    if (excludeAlwaysHidden)
    {
        foreach (MessageWin* win, mWindowList)
        {
            if (win->mAlwaysHide)
                --count;
        }
    }
    return count;
786
787
}

788
789
bool MessageWin::hasDefer() const
{
790
    return mDeferButton && mDeferButton->isVisible();
791
792
793
794
795
796
797
}

/******************************************************************************
* Show the Defer button when it was previously hidden.
*/
void MessageWin::showDefer()
{
798
799
800
801
802
803
804
    if (mDeferButton)
    {
        mNoDefer = false;
        mDeferButton->show();
        setDeferralLimit(mEvent);    // ensure that button is disabled when alarm can't be deferred any more
        resize(sizeHint());
    }
805
806
807
808
809
810
811
}

/******************************************************************************
* Convert a reminder window into a normal alarm window.
*/
void MessageWin::cancelReminder(const KAEvent& event, const KAAlarm& alarm)
{
812
813
814
815
816
817
    if (!mInitialised)
        return;
    mDateTime = alarm.dateTime(true);
    mNoPostAction = false;
    mAlarmType = alarm.type();
    if (event.autoClose())
818
        mCloseTime = alarm.dateTime().effectiveKDateTime().toUtc().dateTime().addSecs(event.lateCancel() * 60);
819
820
821
822
823
824
825
826
827
828
    setCaption(i18nc("@title:window", "Message"));
    mTimeLabel->setText(dateTimeToDisplay());
    if (mRemainingText)
        mRemainingText->hide();
    MidnightTimer::disconnect(this, SLOT(setRemainingTextDay()));
    MinuteTimer::disconnect(this, SLOT(setRemainingTextMinute()));
    setMinimumHeight(0);
    centralWidget()->layout()->activate();
    setMinimumHeight(sizeHint().height());
    resize(sizeHint());
829
830
831
832
833
834
835
836
}

/******************************************************************************
* Show the alarm's trigger time.
* This is assumed to have previously been hidden.
*/
void MessageWin::showDateTime(const KAEvent& event, const KAAlarm& alarm)
{
837
838
839
840
841
842
843
844
    if (!mTimeLabel)
        return;
    mDateTime = (alarm.type() & KAAlarm::REMINDER_ALARM) ? event.mainDateTime(true) : alarm.dateTime(true);
    if (mDateTime.isValid())
    {
        mTimeLabel->setText(dateTimeToDisplay());
        mTimeLabel->show();
    }
845
846
847
848
849
850
851
}

/******************************************************************************
* Get the trigger time to display.
*/
QString MessageWin::dateTimeToDisplay()
{
852
853
854
855
    QString tm;
    if (mDateTime.isValid())
    {
        if (mDateTime.isDateOnly())
856
            tm = KLocale::global()->formatDate(mDateTime.date(), KLocale::ShortDate);
857
858
859
860
861
862
863
864
865
866
        else
        {
            bool showZone = false;
            if (mDateTime.timeType() == KDateTime::UTC
            ||  (mDateTime.timeType() == KDateTime::TimeZone && !mDateTime.isLocalZone()))
            {
                // Display time zone abbreviation if it's different from the local
                // zone. Note that the iCalendar time zone might represent the local
                // time zone in a slightly different way from the system time zone,
                // so the zone comparison above might not produce the desired result.
867
                const QString tz = mDateTime.kDateTime().toString(QStringLiteral("%Z"));
868
869
                KDateTime local = mDateTime.kDateTime();
                local.setTimeSpec(KDateTime::Spec::LocalZone());
870
                showZone = (local.toString(QStringLiteral("%Z")) != tz);
871
            }
872
            tm = KLocale::global()->formatDateTime(mDateTime.kDateTime(), KLocale::ShortDate, KLocale::DateTimeFormatOptions(showZone ? KLocale::TimeZone : 0));
873
874
875
        }
    }
    return tm;
876
877
}

878
879
880
881
882
883
/******************************************************************************
* Set the remaining time text in a reminder window.
* Called at the start of every day (at the user-defined start-of-day time).
*/
void MessageWin::setRemainingTextDay()
{
884
    QString text;
David Jarvie's avatar
David Jarvie committed
885
    const int days = KDateTime::currentLocalDate().daysTo(mDateTime.date());
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
    if (days <= 0  &&  !mDateTime.isDateOnly())
    {
        // The alarm is due today, so start refreshing every minute
        MidnightTimer::disconnect(this, SLOT(setRemainingTextDay()));
        setRemainingTextMinute();
        MinuteTimer::connect(this, SLOT(setRemainingTextMinute()));   // update every minute
    }
    else
    {
        if (days <= 0)
            text = i18nc("@info", "Today");
        else if (days % 7)
            text = i18ncp("@info", "Tomorrow", "in %1 days' time", days);
        else
            text = i18ncp("@info", "in 1 week's time", "in %1 weeks' time", days/7);
    }
    mRemainingText->setText(text);
903
904
905
906
907
908
909
910
}

/******************************************************************************
* Set the remaining time text in a reminder window.
* Called on every minute boundary.
*/
void MessageWin::setRemainingTextMinute()
{
911
    QString text;
David Jarvie's avatar
David Jarvie committed
912
    const int mins = (KDateTime::currentUtcDateTime().secsTo(mDateTime.effectiveKDateTime()) + 59) / 60;
913
914
915
916
917
918
919
920
921
922
    if (mins < 60)
        text = i18ncp("@info", "in 1 minute's time", "in %1 minutes' time", (mins > 0 ? mins : 0));
    else if (mins % 60 == 0)
        text = i18ncp("@info", "in 1 hour's time", "in %1 hours' time", mins/60);
    else
    {
        QString hourText = i18ncp("@item:intext inserted into 'in ... %1 minute's time' below", "1 hour", "%1 hours", mins/60);
        text = i18ncp("@info '%2' is the previous message '1 hour'/'%1 hours'", "in %2 1 minute's time", "in %2 %1 minutes' time", mins%60, hourText);
    }
    mRemainingText->setText(text);
923
924
}

925
926
/******************************************************************************
* Called when output is available from the command which is providing the text
927
* for this window. Add the output and resize the window to show it.
928
929
930
*/
void MessageWin::readProcessOutput(ShellProcess* proc)
{
David Jarvie's avatar
David Jarvie committed
931
    const QByteArray data = proc->readAll();
932
933
934
935
936
    if (!data.isEmpty())
    {
        // Strip any trailing newline, to avoid showing trailing blank line
        // in message window.
        if (mCommandText->newLine())
David Jarvie's avatar
David Jarvie committed
937
            mCommandText->append(QStringLiteral("\n"));
David Jarvie's avatar
David Jarvie committed
938
        const int nl = data.endsWith('\n') ? 1 : 0;
939
940
941
942
        mCommandText->setNewLine(nl);
        mCommandText->insertPlainText(QString::fromLocal8Bit(data.data(), data.length() - nl));
        resize(sizeHint());
    }
943
944
}

945
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
946
947
* Save settings to the session managed config file, for restoration
* when the program is restored.
948
*/
David Jarvie's avatar
David Jarvie committed
949
void MessageWin::saveProperties(KConfigGroup& config)
David Jarvie's avatar
David Jarvie committed
950
{
951
952
    if (mShown  &&  !mErrorWindow  &&  !mAlwaysHide)
    {
953
        config.writeEntry("EventID", mEventId.eventId());
954
955
956
        config.writeEntry("EventItemID", mEventItemId);
        config.writeEntry("AlarmType", static_cast<int>(mAlarmType));
        if (mAlarmType == KAAlarm::INVALID_ALARM)
Laurent Montel's avatar
Laurent Montel committed
957
            qCCritical(KALARM_LOG) << "Invalid alarm: id=" << mEventId << ", alarm count=" << mEvent.alarmCount();
958
959
960
961
962
963
964
965
        config.writeEntry("Message", mMessage);
        config.writeEntry("Type", static_cast<int>(mAction));
        config.writeEntry("Font", mFont);
        config.writeEntry("BgColour", mBgColour);
        config.writeEntry("FgColour", mFgColour);
        config.writeEntry("ConfirmAck", mConfirmAck);
        if (mDateTime.isValid())
        {
966
//TODO: Write KDateTime when it becomes possible
967
968
969
970
            config.writeEntry("Time", mDateTime.effectiveDateTime());
            config.writeEntry("DateOnly", mDateTime.isDateOnly());
            QString zone;
            if (mDateTime.isUtc())
David Jarvie's avatar
David Jarvie committed
971
                zone = QStringLiteral("UTC");
972
973
            else
            {
David Jarvie's avatar
David Jarvie committed
974
                const KTimeZone tz = mDateTime.timeZone();
975
976
977
978
979
980
981
                if (tz.isValid())
                    zone = tz.name();
            }
            config.writeEntry("TimeZone", zone);
        }
        if (mCloseTime.isValid())
            config.writeEntry("Expiry", mCloseTime);
982
        if (mAudioRepeatPause >= 0  &&  mSilenceButton  &&  mSilenceButton->isEnabled())
983
984
985
986
        {
            // Only need to restart sound file playing if it's being repeated
            config.writePathEntry("AudioFile", mAudioFile);
            config.writeEntry("Volume", static_cast<int>(mVolume * 100));
987
            config.writeEntry("AudioPause", mAudioRepeatPause);
988
989
990
991
992
993
994
995
996
997
998
999
        }
        config.writeEntry("Speak", mSpeak);
        config.writeEntry("Height", height());
        config.writeEntry("DeferMins", mDefaultDeferMinutes);
        config.writeEntry("NoDefer", mNoDefer);
        config.writeEntry("NoPostAction", mNoPostAction);
        config.writeEntry("KMailSerial", static_cast<qulonglong>(mKMailSerialNumber));
        config.writeEntry("CmdErr", static_cast<int>(mCommandError));
        config.writeEntry("DontShowAgain", mDontShowAgain);
    }
    else
        config.writeEntry("Invalid", true);
David Jarvie's avatar
David Jarvie committed
1000
1001
1002
1003
}

/******************************************************************************
* Read settings from the session managed config file.
David Jarvie's avatar
David Jarvie committed
1004
1005
* This function is automatically called whenever the app is being restored.
* Read in whatever was saved in saveProperties().
David Jarvie's avatar
David Jarvie committed
1006
*/
Stephan Kulow's avatar
Stephan Kulow committed
1007
void MessageWin::readProperties(const KConfigGroup& config)
1008
{
1009
    mInvalid             = config.readEntry("Invalid", false);
1010
    QString eventId      = config.readEntry("EventID");
1011
1012
1013
1014
1015
    mEventItemId         = config.readEntry("EventItemID", Akonadi::Item::Id(-1));
    mAlarmType           = static_cast<KAAlarm::Type>(config.readEntry("AlarmType", 0));
    if (mAlarmType == KAAlarm::INVALID_ALARM)
    {
        mInvalid = true;
Laurent Montel's avatar
Laurent Montel committed
1016
        qCCritical(KALARM_LOG) << "Invalid alarm: id=" << eventId;
1017
1018
    }
    mMessage             = config.readEntry("Message");
1019
    mAction              = static_cast<KAEvent::SubAction>(config.readEntry("Type", 0));
1020
1021
1022
1023
1024
1025
    mFont                = config.readEntry("Font", QFont());
    mBgColour            = config.readEntry("BgColour", QColor(Qt::white));
    mFgColour            = config.readEntry("FgColour", QColor(Qt::black));
    mConfirmAck          = config.readEntry("ConfirmAck", false);
    QDateTime invalidDateTime;
    QDateTime dt         = config.readEntry("Time", invalidDateTime);
David Jarvie's avatar
David Jarvie committed
1026
    const QString zone   = config.readEntry("TimeZone");
1027
1028
    if (zone.isEmpty())
        mDateTime = KDateTime(dt, KDateTime::ClockTime);
David Jarvie's avatar
David Jarvie committed
1029
    else if (zone == QStringLiteral("UTC"))
1030
1031
1032
1033
1034
1035
1036
1037
1038
    {
        dt.setTimeSpec(Qt::UTC);
        mDateTime = KDateTime(dt, KDateTime::UTC);
    }
    else
    {
        KTimeZone tz = KSystemTimeZones::zone(zone);
        mDateTime = KDateTime(dt, (tz.isValid() ? tz : KSystemTimeZones::local()));
    }
David Jarvie's avatar
David Jarvie committed
1039
    const bool dateOnly  = config.readEntry("DateOnly", false);
1040
1041
1042
    if (dateOnly)
        mDateTime.setDateOnly(true);
    mCloseTime           = config.readEntry("Expiry", invalidDateTime);
1043
    mCloseTime.setTimeSpec(Qt::UTC);
1044
1045
1046
1047
    mAudioFile           = config.readPathEntry("AudioFile", QString());
    mVolume              = static_cast<float>(config.readEntry("Volume", 0)) / 100;
    mFadeVolume          = -1;
    mFadeSeconds         = 0;
1048
1049
    if (!mAudioFile.isEmpty())   // audio file URL was only saved if it repeats
        mAudioRepeatPause = config.readEntry("AudioPause", 0);
David Jarvie's avatar
David Jarvie committed
1050
    mBeep                = false;   // don't beep after restart (similar to not playing non-repeated sound file)
1051
1052
1053
1054
1055
1056
1057
1058
1059
    mSpeak               = config.readEntry("Speak", false);
    mRestoreHeight       = config.readEntry("Height", 0);
    mDefaultDeferMinutes = config.readEntry("DeferMins", 0);
    mNoDefer             = config.readEntry("NoDefer", false);
    mNoPostAction        = config.readEntry("NoPostAction", true);
    mKMailSerialNumber   = static_cast<unsigned long>(config.readEntry("KMailSerial", QVariant(QVariant::ULongLong)).toULongLong());
    mCommandError        = KAEvent::CmdErrType(config.readEntry("CmdErr", static_cast<int>(KAEvent::CMD_NO_ERROR)));
    mDontShowAgain       = config.readEntry("DontShowAgain", QString());
    mShowEdit            = false;
1060
    // Temporarily initialise mCollection and mEventId - they will be set by redisplayAlarm()
1061
    mCollection          = Akonadi::Collection();
1062
    mEventId             = EventId(mCollection.id(), eventId);
Laurent Montel's avatar
Laurent Montel committed
1063
    qCDebug(KALARM_LOG) << eventId;
1064
1065
1066
    if (mAlarmType != KAAlarm::INVALID_ALARM)
    {
        // Recreate the event from the calendar file (if possible)
1067
1068
1069
        if (eventId.isEmpty())
            initView();
        else
1070
        {
1071
1072
            // Close any other window for this alarm which has already been restored by redisplayAlarms()
            if (!AkonadiModel::instance()->isCollectionTreeFetched())
1073
            {
1074
1075
                connect(AkonadiModel::instance(), &Akonadi::EntityTreeModel::collectionTreeFetched,
                                                  this, &MessageWin::showRestoredAlarm);
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
                return;
            }
            redisplayAlarm();
        }
    }
}

/******************************************************************************
* Fetch the restored alarm from the calendar and redisplay it in this window.
*/
void MessageWin::showRestoredAlarm()
{
Laurent Montel's avatar
Laurent Montel committed
1088
    qCDebug(KALARM_LOG) << mEventId;
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
    redisplayAlarm();
    show();
}

/******************************************************************************
* Fetch the restored alarm from the calendar and redisplay it in this window.
*/
void MessageWin::redisplayAlarm()
{
    mCollection = AkonadiModel::instance()->collectionForItem(mEventItemId);
    mEventId.setCollectionId(mCollection.id());
Laurent Montel's avatar
Laurent Montel committed
1100
    qCDebug(KALARM_LOG) << mEventId;
1101
1102
1103
    // Delete any already existing window for the same event
    MessageWin* duplicate = findEvent(mEventId, this);
    if (duplicate)
Laurent Montel's avatar
Laurent Montel committed
1104
        qCDebug(KALARM_LOG) << "Deleting duplicate window:" << mEventId;
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
    delete duplicate;

    KAEvent* event = AlarmCalendar::resources()->event(mEventId);
    if (event)
    {
        mEvent = *event;
        mShowEdit = true;
    }
    else
    {
        // It's not in the active calendar, so try the displaying or archive calendars
        retrieveEvent(mEvent, mCollection, mShowEdit, mNoDefer);
        mNoDefer = !mNoDefer;
1118
    }
1119
    initView();
1120
1121
}

1122
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
1123
1124
1125
1126
* Redisplay alarms which were being shown when the program last exited.
* Normally, these alarms will have been displayed by session restoration, but
* if the program crashed or was killed, we can redisplay them here so that
* they won't be lost.
1127
1128
1129
*/
void MessageWin::redisplayAlarms()
{
1130
1131
    if (mRedisplayed)
        return;
Laurent Montel's avatar
Laurent Montel committed
1132
    qCDebug(KALARM_LOG);
1133
    mRedisplayed = true;
1134
1135
1136
1137
1138
    AlarmCalendar* cal = AlarmCalendar::displayCalendar();
    if (cal->isOpen())
    {
        KAEvent event;
        Akonadi::Collection collection;
David Jarvie's avatar
David Jarvie committed
1139
        const Event::List events = cal->kcalEvents();
1140
1141
1142
1143
        for (int i = 0, end = events.count();  i < end;  ++i)
        {
            bool showDefer, showEdit;
            reinstateFromDisplaying(events[i], event, collection, showEdit, showDefer);
1144
1145
1146
1147
1148
            Akonadi::Item::Id id = AkonadiModel::instance()->findItemId(event);
            if (id >= 0)
                event.setItemId(id);
            const EventId eventId(event);
            if (findEvent(eventId))
Laurent Montel's avatar
Laurent Montel committed
1149
                qCDebug(KALARM_LOG) << "Message window already exists:" << eventId;
1150
            else
1151
1152
            {
                // This event should be displayed, but currently isn't being
David Jarvie's avatar
David Jarvie committed
1153
                const KAAlarm alarm = event.convertDisplayingAlarm();
1154
1155
                if (alarm.type() == KAAlarm::INVALID_ALARM)
                {
Laurent Montel's avatar
Laurent Montel committed
1156
                    qCCritical(KALARM_LOG) << "Invalid alarm: id=" << eventId;
1157
1158
                    continue;
                }
Laurent Montel's avatar
Laurent Montel committed
1159
                qCDebug(KALARM_LOG) << eventId;
David Jarvie's avatar
David Jarvie committed
1160
1161
                const bool login = alarm.repeatAtLogin();
                const int flags = NO_RESCHEDULE | (login ? NO_DEFER : 0) | NO_INIT_VIEW;
1162
1163
                MessageWin* win = new MessageWin(&event, alarm, flags);
                win->mCollection = collection;
David Jarvie's avatar
David Jarvie committed
1164
                const bool rw = CollectionControlModel::isWritableEnabled(collection, event.category()) > 0;
1165
1166
1167
1168
1169
1170
1171
                win->mShowEdit = rw ? showEdit : false;
                win->mNoDefer  = (rw && !login) ? !showDefer : true;
                win->initView();
                win->show();
            }
        }
    }
1172
1173
1174
}

/******************************************************************************
David Jarvie's avatar
David Jarvie committed
1175
1176
* Retrieves the event with the current ID from the displaying calendar file,
* or if not found there, from the archive calendar.
1177
*/
1178
bool MessageWin::retrieveEvent(KAEvent& event, Akonadi::Collection& resource, bool& showEdit, bool& showDefer)
1179
{
David Jarvie's avatar
David Jarvie committed
1180
    const Event::Ptr kcalEvent = AlarmCalendar::displayCalendar()->kcalEvent(CalEvent::uid(mEventId.eventId(), CalEvent::DISPLAYING));
1181
1182
1183
1184
    if (!reinstateFromDisplaying(kcalEvent, event, resource, showEdit, showDefer))
    {
        // The event isn't in the displaying calendar.
        // Try to retrieve it from the archive calendar.
Laurent Montel's avatar
Laurent Montel committed
1185
        KAEvent* ev = Q_NULLPTR;
1186
1187
1188
        Akonadi::Collection archiveCol = CollectionControlModel::getStandard(CalEvent::ARCHIVED);
        if (archiveCol.isValid())
            ev = AlarmCalendar::resources()->event(EventId(archiveCol.id(), CalEvent::uid(mEventId.eventId(), CalEvent::ARCHIVED)));