2 * messagewin.cpp - displays an alarm message
4 * Copyright © 2001-2013 by David Jarvie <djarvie@kde.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 #include "messagewin_p.moc"
23 #include "messagewin.moc"
25 #include "alarmcalendar.h"
26 #include "autoqpointer.h"
28 #include "collectionmodel.h"
33 #include "functions.h"
34 #include "kalarmapp.h"
35 #include "mainwindow.h"
36 #include "messagebox.h"
37 #include "preferences.h"
38 #include "pushbutton.h"
39 #include "shellprocess.h"
40 #include "synchtimer.h"
42 #include "kspeechinterface.h"
44 #include <kstandarddirs.h>
46 #include <kstandardguiitem.h>
47 #include <kaboutdata.h>
50 #include <kiconloader.h>
52 #include <ktextbrowser.h>
53 #include <ksystemtimezone.h>
54 #include <kglobalsettings.h>
55 #include <kmimetype.h>
56 #include <ktextedit.h>
57 #include <kwindowsystem.h>
58 #include <kio/netaccess.h>
59 #include <knotification.h>
60 #include <kpushbutton.h>
61 #include <ksqueezedtextlabel.h>
62 #include <phonon/mediaobject.h>
63 #include <phonon/audiooutput.h>
64 #include <phonon/volumefadereffect.h>
66 #include <ktoolinvocation.h>
73 #include <QtDBus/QtDBus>
83 #include <QGridLayout>
84 #include <QVBoxLayout>
85 #include <QHBoxLayout>
86 #include <QResizeEvent>
87 #include <QCloseEvent>
88 #include <QDesktopWidget>
89 #include <QMutexLocker>
95 using namespace KCalCore
;
99 using namespace KAlarmCal
;
102 enum FullScreenType
{ NoFullScreen
= 0, FullScreen
= 1, FullScreenActive
= 2 };
103 static FullScreenType
haveFullScreenWindow(int screen
);
104 static FullScreenType
findFullScreenWindows(const QVector
<QRect
>& screenRects
, QVector
<FullScreenType
>& screenTypes
);
107 #ifdef KMAIL_SUPPORTED
108 #include "kmailinterface.h"
109 static const char* KMAIL_DBUS_SERVICE
= "org.kde.kmail";
110 static const char* KMAIL_DBUS_PATH
= "/KMail";
113 // The delay for enabling message window buttons if a zero delay is
114 // configured, i.e. the windows are placed far from the cursor.
115 static const int proximityButtonDelay
= 1000; // (milliseconds)
116 static const int proximityMultiple
= 10; // multiple of button height distance from cursor for proximity
118 // A text label widget which can be scrolled and copied with the mouse
119 class MessageText
: public KTextEdit
122 MessageText(QWidget
* parent
= 0)
127 setFrameStyle(NoFrame
);
128 setLineWrapMode(NoWrap
);
130 int scrollBarHeight() const { return horizontalScrollBar()->height(); }
131 int scrollBarWidth() const { return verticalScrollBar()->width(); }
132 void setBackgroundColour(const QColor
& c
)
134 QPalette pal
= viewport()->palette();
135 pal
.setColor(viewport()->backgroundRole(), c
);
136 viewport()->setPalette(pal
);
138 virtual QSize
sizeHint() const
140 QSizeF docsize
= document()->size();
141 return QSize(static_cast<int>(docsize
.width() + 0.99) + verticalScrollBar()->width(),
142 static_cast<int>(docsize
.height() + 0.99) + horizontalScrollBar()->height());
144 bool newLine() const { return mNewLine
; }
145 void setNewLine(bool nl
) { mNewLine
= nl
; }
151 // Basic flags for the window
152 static const Qt::WindowFlags WFLAGS
= Qt::WindowStaysOnTopHint
;
153 static const Qt::WindowFlags WFLAGS2
= Qt::WindowContextHelpButtonHint
;
154 static const Qt::WidgetAttribute WidgetFlags
= Qt::WA_DeleteOnClose
;
156 // Error message bit masks
159 ErrMsg_AudioFile
= 0x02
163 QList
<MessageWin
*> MessageWin::mWindowList
;
165 QMap
<EventId
, unsigned> MessageWin::mErrorMessages
;
167 QMap
<QString
, unsigned> MessageWin::mErrorMessages
;
169 // There can only be one audio thread at a time: trying to play multiple
170 // sound files simultaneously would result in a cacophony, and besides
171 // that, Phonon currently crashes...
172 QPointer
<AudioThread
> MessageWin::mAudioThread
;
173 MessageWin
* AudioThread::mAudioOwner
= 0;
175 /******************************************************************************
176 * Construct the message window for the specified alarm.
177 * Other alarms in the supplied event may have been updated by the caller, so
178 * the whole event needs to be stored for updating the calendar file when it is
181 MessageWin::MessageWin(const KAEvent
* event
, const KAAlarm
& alarm
, int flags
)
182 : MainWindowBase(0, static_cast<Qt::WindowFlags
>(WFLAGS
| WFLAGS2
| ((flags
& ALWAYS_HIDE
) || getWorkAreaAndModal() ? Qt::WindowType(0) : Qt::X11BypassWindowManagerHint
))),
183 mMessage(event
->cleanText()),
184 mFont(event
->font()),
185 mBgColour(event
->bgColour()),
186 mFgColour(event
->fgColour()),
188 mEventItemId(event
->itemId()),
191 mEventId(event
->id()),
193 mAudioFile(event
->audioFile()),
194 mVolume(event
->soundVolume()),
195 mFadeVolume(event
->fadeVolume()),
196 mFadeSeconds(qMin(event
->fadeSeconds(), 86400)),
197 mDefaultDeferMinutes(event
->deferDefaultMinutes()),
198 mAlarmType(alarm
.type()),
199 mAction(event
->actionSubType()),
200 #ifdef KMAIL_SUPPORTED
201 mKMailSerialNumber(event
->kmailSerialNumber()),
203 mKMailSerialNumber(0),
205 mCommandError(event
->commandError()),
207 mAudioRepeatPause(event
->repeatSoundPause()),
208 mConfirmAck(event
->confirmAck()),
212 mOriginalEvent(*event
),
214 mCollection(AlarmCalendar::resources()->collectionForEvent(mEventItemId
)),
216 mResource(AlarmCalendar::resources()->resourceForEvent(mEventId
)),
225 mDontShowAgainCheck(0),
228 mAlwaysHide(flags
& ALWAYS_HIDE
),
231 mNoPostAction(alarm
.type() & KAAlarm::REMINDER_ALARM
),
233 mBeep(event
->beep()),
234 mSpeak(event
->speak()),
235 mRescheduleEvent(!(flags
& NO_RESCHEDULE
)),
238 mNoCloseConfirm(false),
239 mDisableDeferral(false)
242 setAttribute(static_cast<Qt::WidgetAttribute
>(WidgetFlags
));
243 setWindowModality(Qt::WindowModal
);
244 setObjectName("MessageWin"); // used by LikeBack
245 if (alarm
.type() & KAAlarm::REMINDER_ALARM
)
247 if (event
->reminderMinutes() < 0)
249 event
->previousOccurrence(alarm
.dateTime(false).effectiveKDateTime(), mDateTime
, false);
250 if (!mDateTime
.isValid() && event
->repeatAtLogin())
251 mDateTime
= alarm
.dateTime().addSecs(event
->reminderMinutes() * 60);
254 mDateTime
= event
->mainDateTime(true);
257 mDateTime
= alarm
.dateTime(true);
258 if (!(flags
& (NO_INIT_VIEW
| ALWAYS_HIDE
)))
261 bool readonly
= AlarmCalendar::resources()->eventReadOnly(mEventItemId
);
263 bool readonly
= AlarmCalendar::resources()->eventReadOnly(mEventId
);
265 mShowEdit
= !mEventId
.isEmpty() && !readonly
;
266 mNoDefer
= readonly
|| (flags
& NO_DEFER
) || alarm
.repeatAtLogin();
269 // Set to save settings automatically, but don't save window size.
270 // File alarm window size is saved elsewhere.
271 setAutoSaveSettings(QLatin1String("MessageWin"), false);
272 mWindowList
.append(this);
273 if (event
->autoClose())
274 mCloseTime
= alarm
.dateTime().effectiveKDateTime().toUtc().dateTime().addSecs(event
->lateCancel() * 60);
278 displayComplete(); // play audio, etc.
282 /******************************************************************************
283 * Display an error message window.
284 * If 'dontShowAgain' is non-null, a "Don't show again" option is displayed. Note
285 * that the option is specific to 'event'.
287 void MessageWin::showError(const KAEvent
& event
, const DateTime
& alarmDateTime
,
288 const QStringList
& errmsgs
, const QString
& dontShowAgain
)
291 if (!dontShowAgain
.isEmpty()
292 && KAlarm::dontShowErrors(EventId(event
), dontShowAgain
))
294 if (!dontShowAgain
.isEmpty()
295 && KAlarm::dontShowErrors(event
.id(), dontShowAgain
))
299 // Don't pile up duplicate error messages for the same alarm
300 for (int i
= 0, end
= mWindowList
.count(); i
< end
; ++i
)
302 MessageWin
* w
= mWindowList
[i
];
304 if (w
->mErrorWindow
&& w
->mEventId
== EventId(event
)
305 && w
->mErrorMsgs
== errmsgs
&& w
->mDontShowAgain
== dontShowAgain
)
307 if (w
->mErrorWindow
&& w
->mEventId
== event
.id()
308 && w
->mErrorMsgs
== errmsgs
&& w
->mDontShowAgain
== dontShowAgain
)
313 (new MessageWin(&event
, alarmDateTime
, errmsgs
, dontShowAgain
))->show();
316 /******************************************************************************
317 * Construct the message window for a specified error message.
318 * If 'dontShowAgain' is non-null, a "Don't show again" option is displayed. Note
319 * that the option is specific to 'event'.
321 MessageWin::MessageWin(const KAEvent
* event
, const DateTime
& alarmDateTime
,
322 const QStringList
& errmsgs
, const QString
& dontShowAgain
)
323 : MainWindowBase(0, WFLAGS
| WFLAGS2
),
324 mMessage(event
->cleanText()),
325 mDateTime(alarmDateTime
),
327 mEventItemId(event
->itemId()),
330 mEventId(event
->id()),
332 mAlarmType(KAAlarm::MAIN_ALARM
),
333 mAction(event
->actionSubType()),
334 mKMailSerialNumber(0),
335 mCommandError(KAEvent::CMD_NO_ERROR
),
337 mDontShowAgain(dontShowAgain
),
344 mOriginalEvent(*event
),
355 mDontShowAgainCheck(0),
363 mRescheduleEvent(false),
366 mNoCloseConfirm(false),
367 mDisableDeferral(false)
369 kDebug() << "errmsg";
370 setAttribute(static_cast<Qt::WidgetAttribute
>(WidgetFlags
));
371 setWindowModality(Qt::WindowModal
);
372 setObjectName("ErrorWin"); // used by LikeBack
373 getWorkAreaAndModal();
375 mWindowList
.append(this);
378 /******************************************************************************
379 * Construct the message window for restoration by session management.
380 * The window is initialised by readProperties().
382 MessageWin::MessageWin()
383 : MainWindowBase(0, WFLAGS
),
391 mDontShowAgainCheck(0),
398 mRescheduleEvent(false),
401 mNoCloseConfirm(false),
402 mDisableDeferral(false)
404 kDebug() << "restore";
405 setAttribute(WidgetFlags
);
406 setWindowModality(Qt::WindowModal
);
407 setObjectName("RestoredMsgWin"); // used by LikeBack
408 getWorkAreaAndModal();
409 mWindowList
.append(this);
412 /******************************************************************************
413 * Destructor. Perform any post-alarm actions before tidying up.
415 MessageWin::~MessageWin()
417 kDebug() << mEventId
;
418 if (AudioThread::mAudioOwner
== this && !mAudioThread
.isNull())
419 mAudioThread
->quit();
420 mErrorMessages
.remove(mEventId
);
421 mWindowList
.removeAll(this);
424 if (!mNoPostAction
&& !mEvent
.postAction().isEmpty())
425 theApp()->alarmCompleted(mEvent
);
426 if (!instanceCount(true))
427 theApp()->quitIf(); // no visible windows remain - check whether to quit
431 /******************************************************************************
432 * Construct the message window.
434 void MessageWin::initView()
436 bool reminder
= (!mErrorWindow
&& (mAlarmType
& KAAlarm::REMINDER_ALARM
));
437 int leading
= fontMetrics().leading();
438 setCaption((mAlarmType
& KAAlarm::REMINDER_ALARM
) ? i18nc("@title:window", "Reminder") : i18nc("@title:window", "Message"));
439 QWidget
* topWidget
= new QWidget(this);
440 setCentralWidget(topWidget
);
441 QVBoxLayout
* topLayout
= new QVBoxLayout(topWidget
);
442 topLayout
->setMargin(KDialog::marginHint());
443 topLayout
->setSpacing(KDialog::spacingHint());
445 QPalette labelPalette
= palette();
446 labelPalette
.setColor(backgroundRole(), labelPalette
.color(QPalette::Window
));
448 // Show the alarm date/time, together with a reminder text where appropriate.
449 // Alarm date/time: display time zone if not local time zone.
450 mTimeLabel
= new QLabel(topWidget
);
451 mTimeLabel
->setText(dateTimeToDisplay());
452 mTimeLabel
->setFrameStyle(QFrame::StyledPanel
);
453 mTimeLabel
->setPalette(labelPalette
);
454 mTimeLabel
->setAutoFillBackground(true);
455 topLayout
->addWidget(mTimeLabel
, 0, Qt::AlignHCenter
);
456 mTimeLabel
->setWhatsThis(i18nc("@info:whatsthis", "The scheduled date/time for the message (as opposed to the actual time of display)."));
458 if (mDateTime
.isValid())
463 QString s
= i18nc("@info", "Reminder");
464 QRegExp
re("^(<[^>]+>)*");
466 s
.insert(re
.matchedLength(), mTimeLabel
->text() + "<br/>");
467 mTimeLabel
->setText(s
);
468 mTimeLabel
->setAlignment(Qt::AlignHCenter
);
476 // It's a normal alarm message window
481 // Display the file name
482 KSqueezedTextLabel
* label
= new KSqueezedTextLabel(mMessage
, topWidget
);
483 label
->setFrameStyle(QFrame::StyledPanel
);
484 label
->setTextInteractionFlags(Qt::TextSelectableByMouse
| Qt::TextSelectableByKeyboard
);
485 label
->setPalette(labelPalette
);
486 label
->setAutoFillBackground(true);
487 label
->setWhatsThis(i18nc("@info:whatsthis", "The file whose contents are displayed below"));
488 topLayout
->addWidget(label
, 0, Qt::AlignHCenter
);
490 // Display contents of file
495 if (KIO::NetAccess::download(url
, tmpFile
, MainWindow::mainMainWindow()))
497 QFile
qfile(tmpFile
);
498 QFileInfo
info(qfile
);
499 if (!(dir
= info
.isDir()))
502 KTextBrowser
* view
= new KTextBrowser(topWidget
);
503 view
->setFrameStyle(QFrame::NoFrame
);
504 view
->setWordWrapMode(QTextOption::NoWrap
);
505 QPalette pal
= view
->viewport()->palette();
506 pal
.setColor(view
->viewport()->backgroundRole(), mBgColour
);
507 view
->viewport()->setPalette(pal
);
508 view
->setTextColor(mFgColour
);
509 view
->setCurrentFont(mFont
);
510 KMimeType::Ptr mime
= KMimeType::findByUrl(url
);
511 if (mime
->is("application/octet-stream"))
512 mime
= KMimeType::findByFileContent(tmpFile
);
513 switch (KAlarm::fileType(mime
))
516 view
->setHtml("<img source=\"" + tmpFile
+ "\">");
518 case KAlarm::TextFormatted
:
519 view
->QTextBrowser::setSource(tmpFile
); //krazy:exclude=qclasses
523 // Assume a plain text file
524 if (qfile
.open(QIODevice::ReadOnly
))
526 QTextStream
str(&qfile
);
528 view
->setPlainText(str
.readAll());
534 view
->setMinimumSize(view
->sizeHint());
535 topLayout
->addWidget(view
);
537 // Set the default size to 20 lines square.
538 // Note that after the first file has been displayed, this size
539 // is overridden by the user-set default stored in the config file.
540 // So there is no need to calculate an accurate size.
541 int h
= 20*view
->fontMetrics().lineSpacing() + 2*view
->frameWidth();
542 view
->resize(QSize(h
, h
).expandedTo(view
->sizeHint()));
543 view
->setWhatsThis(i18nc("@info:whatsthis", "The contents of the file to be displayed"));
545 KIO::NetAccess::removeTempFile(tmpFile
);
549 // File couldn't be opened
550 bool exists
= KIO::NetAccess::exists(url
, KIO::NetAccess::SourceSide
, MainWindow::mainMainWindow());
551 mErrorMsgs
+= dir
? i18nc("@info", "File is a folder") : exists
? i18nc("@info", "Failed to open file") : i18nc("@info", "File not found");
555 case KAEvent::MESSAGE
:
558 // Using MessageText instead of QLabel allows scrolling and mouse copying
559 MessageText
* text
= new MessageText(topWidget
);
560 text
->setAutoFillBackground(true);
561 text
->setBackgroundColour(mBgColour
);
562 text
->setTextColor(mFgColour
);
563 text
->setCurrentFont(mFont
);
564 text
->insertPlainText(mMessage
);
565 int lineSpacing
= text
->fontMetrics().lineSpacing();
566 QSize s
= text
->sizeHint();
568 text
->setMaximumHeight(h
+ text
->scrollBarHeight());
569 text
->setMinimumHeight(qMin(h
, lineSpacing
*4));
570 text
->setMaximumWidth(s
.width() + text
->scrollBarWidth());
571 text
->setWhatsThis(i18nc("@info:whatsthis", "The alarm message"));
572 int vspace
= lineSpacing
/2;
573 int hspace
= lineSpacing
- KDialog::marginHint();
574 topLayout
->addSpacing(vspace
);
575 topLayout
->addStretch();
576 // Don't include any horizontal margins if message is 2/3 screen width
577 if (text
->sizeHint().width() >= KAlarm::desktopWorkArea(mScreenNumber
).width()*2/3)
578 topLayout
->addWidget(text
, 1, Qt::AlignHCenter
);
581 QHBoxLayout
* layout
= new QHBoxLayout();
582 layout
->addSpacing(hspace
);
583 layout
->addWidget(text
, 1, Qt::AlignHCenter
);
584 layout
->addSpacing(hspace
);
585 topLayout
->addLayout(layout
);
588 topLayout
->addStretch();
591 case KAEvent::COMMAND
:
593 mCommandText
= new MessageText(topWidget
);
594 mCommandText
->setBackgroundColour(mBgColour
);
595 mCommandText
->setTextColor(mFgColour
);
596 mCommandText
->setCurrentFont(mFont
);
597 topLayout
->addWidget(mCommandText
);
598 mCommandText
->setWhatsThis(i18nc("@info:whatsthis", "The output of the alarm's command"));
599 theApp()->execCommandAlarm(mEvent
, mEvent
.alarm(mAlarmType
), this, SLOT(readProcessOutput(ShellProcess
*)));
607 if (reminder
&& mEvent
.reminderMinutes() > 0)
609 // Advance reminder: show remaining time until the actual alarm
610 mRemainingText
= new QLabel(topWidget
);
611 mRemainingText
->setFrameStyle(QFrame::Box
| QFrame::Raised
);
612 mRemainingText
->setMargin(leading
);
613 mRemainingText
->setPalette(labelPalette
);
614 mRemainingText
->setAutoFillBackground(true);
615 if (mDateTime
.isDateOnly() || KDateTime::currentLocalDate().daysTo(mDateTime
.date()) > 0)
617 setRemainingTextDay();
618 MidnightTimer::connect(this, SLOT(setRemainingTextDay())); // update every day
622 setRemainingTextMinute();
623 MinuteTimer::connect(this, SLOT(setRemainingTextMinute())); // update every minute
625 topLayout
->addWidget(mRemainingText
, 0, Qt::AlignHCenter
);
626 topLayout
->addSpacing(KDialog::spacingHint());
627 topLayout
->addStretch();
632 // It's an error message
637 // Display the email addresses and subject.
638 QFrame
* frame
= new QFrame(topWidget
);
639 frame
->setFrameStyle(QFrame::Box
| QFrame::Raised
);
640 frame
->setWhatsThis(i18nc("@info:whatsthis", "The email to send"));
641 topLayout
->addWidget(frame
, 0, Qt::AlignHCenter
);
642 QGridLayout
* grid
= new QGridLayout(frame
);
643 grid
->setMargin(KDialog::marginHint());
644 grid
->setSpacing(KDialog::spacingHint());
646 QLabel
* label
= new QLabel(i18nc("@info Email addressee", "To:"), frame
);
647 label
->setFixedSize(label
->sizeHint());
648 grid
->addWidget(label
, 0, 0, Qt::AlignLeft
);
649 label
= new QLabel(mEvent
.emailAddresses("\n"), frame
);
650 label
->setFixedSize(label
->sizeHint());
651 grid
->addWidget(label
, 0, 1, Qt::AlignLeft
);
653 label
= new QLabel(i18nc("@info Email subject", "Subject:"), frame
);
654 label
->setFixedSize(label
->sizeHint());
655 grid
->addWidget(label
, 1, 0, Qt::AlignLeft
);
656 label
= new QLabel(mEvent
.emailSubject(), frame
);
657 label
->setFixedSize(label
->sizeHint());
658 grid
->addWidget(label
, 1, 1, Qt::AlignLeft
);
661 case KAEvent::COMMAND
:
663 case KAEvent::MESSAGE
:
665 // Just display the error message strings
670 if (!mErrorMsgs
.count())
672 topWidget
->setAutoFillBackground(true);
673 QPalette palette
= topWidget
->palette();
674 palette
.setColor(topWidget
->backgroundRole(), mBgColour
);
675 topWidget
->setPalette(palette
);
679 setCaption(i18nc("@title:window", "Error"));
680 QHBoxLayout
* layout
= new QHBoxLayout();
681 layout
->setMargin(2*KDialog::marginHint());
682 layout
->addStretch();
683 topLayout
->addLayout(layout
);
684 QLabel
* label
= new QLabel(topWidget
);
685 label
->setPixmap(DesktopIcon("dialog-error"));
686 label
->setFixedSize(label
->sizeHint());
687 layout
->addWidget(label
, 0, Qt::AlignRight
);
688 QVBoxLayout
* vlayout
= new QVBoxLayout();
689 layout
->addLayout(vlayout
);
690 for (QStringList::Iterator it
= mErrorMsgs
.begin(); it
!= mErrorMsgs
.end(); ++it
)
692 label
= new QLabel(*it
, topWidget
);
693 label
->setFixedSize(label
->sizeHint());
694 vlayout
->addWidget(label
, 0, Qt::AlignLeft
);
696 layout
->addStretch();
697 if (!mDontShowAgain
.isEmpty())
699 mDontShowAgainCheck
= new QCheckBox(i18nc("@option:check", "Do not display this error message again for this alarm"), topWidget
);
700 mDontShowAgainCheck
->setFixedSize(mDontShowAgainCheck
->sizeHint());
701 topLayout
->addWidget(mDontShowAgainCheck
, 0, Qt::AlignLeft
);
705 QGridLayout
* grid
= new QGridLayout();
706 grid
->setColumnStretch(0, 1); // keep the buttons right-adjusted in the window
707 topLayout
->addLayout(grid
);
711 mOkButton
= new PushButton(KStandardGuiItem::close(), topWidget
);
712 // Prevent accidental acknowledgement of the message if the user is typing when the window appears
713 mOkButton
->clearFocus();
714 mOkButton
->setFocusPolicy(Qt::ClickFocus
); // don't allow keyboard selection
715 mOkButton
->setFixedSize(mOkButton
->sizeHint());
716 connect(mOkButton
, SIGNAL(clicked()), SLOT(slotOk()));
717 grid
->addWidget(mOkButton
, 0, gridIndex
++, Qt::AlignHCenter
);
718 mOkButton
->setWhatsThis(i18nc("@info:whatsthis", "Acknowledge the alarm"));
723 mEditButton
= new PushButton(i18nc("@action:button", "&Edit..."), topWidget
);
724 mEditButton
->setFocusPolicy(Qt::ClickFocus
); // don't allow keyboard selection
725 mEditButton
->setFixedSize(mEditButton
->sizeHint());
726 connect(mEditButton
, SIGNAL(clicked()), SLOT(slotEdit()));
727 grid
->addWidget(mEditButton
, 0, gridIndex
++, Qt::AlignHCenter
);
728 mEditButton
->setWhatsThis(i18nc("@info:whatsthis", "Edit the alarm."));
732 mDeferButton
= new PushButton(i18nc("@action:button", "&Defer..."), topWidget
);
733 mDeferButton
->setFocusPolicy(Qt::ClickFocus
); // don't allow keyboard selection
734 mDeferButton
->setFixedSize(mDeferButton
->sizeHint());
735 connect(mDeferButton
, SIGNAL(clicked()), SLOT(slotDefer()));
736 grid
->addWidget(mDeferButton
, 0, gridIndex
++, Qt::AlignHCenter
);
737 mDeferButton
->setWhatsThis(i18nc("@info:whatsthis", "<para>Defer the alarm until later.</para>"
738 "<para>You will be prompted to specify when the alarm should be redisplayed.</para>"));
741 mDeferButton
->hide();
743 setDeferralLimit(mEvent
); // ensure that button is disabled when alarm can't be deferred any more
745 if (!mAudioFile
.isEmpty() && (mVolume
|| mFadeVolume
> 0))
747 // Silence button to stop sound repetition
748 QPixmap pixmap
= MainBarIcon("media-playback-stop");
749 mSilenceButton
= new PushButton(topWidget
);
750 mSilenceButton
->setIcon(KIcon(pixmap
));
751 grid
->addWidget(mSilenceButton
, 0, gridIndex
++, Qt::AlignHCenter
);
752 mSilenceButton
->setToolTip(i18nc("@info:tooltip", "Stop sound"));
753 mSilenceButton
->setWhatsThis(i18nc("@info:whatsthis", "Stop playing the sound"));
754 // To avoid getting in a mess, disable the button until sound playing has been set up
755 mSilenceButton
->setEnabled(false);
758 KIconLoader iconLoader
;
759 if (mKMailSerialNumber
)
762 QPixmap pixmap
= iconLoader
.loadIcon(QLatin1String("internet-mail"), KIconLoader::MainToolbar
);
763 mKMailButton
= new PushButton(topWidget
);
764 mKMailButton
->setIcon(KIcon(pixmap
));
765 connect(mKMailButton
, SIGNAL(clicked()), SLOT(slotShowKMailMessage()));
766 grid
->addWidget(mKMailButton
, 0, gridIndex
++, Qt::AlignHCenter
);
767 mKMailButton
->setToolTip(i18nc("@info:tooltip Locate this email in KMail", "Locate in <application>KMail</application>"));
768 mKMailButton
->setWhatsThis(i18nc("@info:whatsthis", "Locate and highlight this email in <application>KMail</application>"));
772 QPixmap pixmap
= iconLoader
.loadIcon(KGlobal::mainComponent().aboutData()->appName(), KIconLoader::MainToolbar
);
773 mKAlarmButton
= new PushButton(topWidget
);
774 mKAlarmButton
->setIcon(KIcon(pixmap
));
775 connect(mKAlarmButton
, SIGNAL(clicked()), SLOT(displayMainWindow()));
776 grid
->addWidget(mKAlarmButton
, 0, gridIndex
++, Qt::AlignHCenter
);
777 mKAlarmButton
->setToolTip(i18nc("@info:tooltip", "Activate <application>KAlarm</application>"));
778 mKAlarmButton
->setWhatsThis(i18nc("@info:whatsthis", "Activate <application>KAlarm</application>"));
780 int butsize
= mKAlarmButton
->sizeHint().height();
782 butsize
= qMax(butsize
, mSilenceButton
->sizeHint().height());
784 butsize
= qMax(butsize
, mKMailButton
->sizeHint().height());
785 mKAlarmButton
->setFixedSize(butsize
, butsize
);
787 mSilenceButton
->setFixedSize(butsize
, butsize
);
789 mKMailButton
->setFixedSize(butsize
, butsize
);
791 // Disable all buttons initially, to prevent accidental clicking on if they happen to be
792 // under the mouse just as the window appears.
793 mOkButton
->setEnabled(false);
794 if (mDeferButton
->isVisible())
795 mDeferButton
->setEnabled(false);
797 mEditButton
->setEnabled(false);
799 mKMailButton
->setEnabled(false);
800 mKAlarmButton
->setEnabled(false);
802 topLayout
->activate();
803 setMinimumSize(QSize(grid
->sizeHint().width() + 2*KDialog::marginHint(), sizeHint().height()));
804 bool modal
= !(windowFlags() & Qt::X11BypassWindowManagerHint
);
805 unsigned long wstate
= (modal
? NET::Modal
: 0) | NET::Sticky
| NET::StaysOnTop
;
807 KWindowSystem::setState(winid
, wstate
);
808 KWindowSystem::setOnAllDesktops(winid
, true);
810 mInitialised
= true; // the window's widgets have been created
813 /******************************************************************************
814 * Return the number of message windows, optionally excluding always-hidden ones.
816 int MessageWin::instanceCount(bool excludeAlwaysHidden
)
818 int count
= mWindowList
.count();
819 if (excludeAlwaysHidden
)
821 foreach (MessageWin
* win
, mWindowList
)
823 if (win
->mAlwaysHide
)
830 bool MessageWin::hasDefer() const
832 return mDeferButton
&& mDeferButton
->isVisible();
835 /******************************************************************************
836 * Show the Defer button when it was previously hidden.
838 void MessageWin::showDefer()
843 mDeferButton
->show();
844 setDeferralLimit(mEvent
); // ensure that button is disabled when alarm can't be deferred any more
849 /******************************************************************************
850 * Convert a reminder window into a normal alarm window.
852 void MessageWin::cancelReminder(const KAEvent
& event
, const KAAlarm
& alarm
)
856 mDateTime
= alarm
.dateTime(true);
857 mNoPostAction
= false;
858 mAlarmType
= alarm
.type();
859 if (event
.autoClose())
860 mCloseTime
= alarm
.dateTime().effectiveKDateTime().toUtc().dateTime().addSecs(event
.lateCancel() * 60);
861 setCaption(i18nc("@title:window", "Message"));
862 mTimeLabel
->setText(dateTimeToDisplay());
864 mRemainingText
->hide();
865 MidnightTimer::disconnect(this, SLOT(setRemainingTextDay()));
866 MinuteTimer::disconnect(this, SLOT(setRemainingTextMinute()));
868 centralWidget()->layout()->activate();
869 setMinimumHeight(sizeHint().height());
873 /******************************************************************************
874 * Show the alarm's trigger time.
875 * This is assumed to have previously been hidden.
877 void MessageWin::showDateTime(const KAEvent
& event
, const KAAlarm
& alarm
)
881 mDateTime
= (alarm
.type() & KAAlarm::REMINDER_ALARM
) ? event
.mainDateTime(true) : alarm
.dateTime(true);
882 if (mDateTime
.isValid())
884 mTimeLabel
->setText(dateTimeToDisplay());
889 /******************************************************************************
890 * Get the trigger time to display.
892 QString
MessageWin::dateTimeToDisplay()
895 if (mDateTime
.isValid())
897 if (mDateTime
.isDateOnly())
898 tm
= KGlobal::locale()->formatDate(mDateTime
.date(), KLocale::ShortDate
);
901 bool showZone
= false;
902 if (mDateTime
.timeType() == KDateTime::UTC
903 || (mDateTime
.timeType() == KDateTime::TimeZone
&& !mDateTime
.isLocalZone()))
905 // Display time zone abbreviation if it's different from the local
906 // zone. Note that the iCalendar time zone might represent the local
907 // time zone in a slightly different way from the system time zone,
908 // so the zone comparison above might not produce the desired result.
909 QString tz
= mDateTime
.kDateTime().toString(QString::fromLatin1("%Z"));
910 KDateTime local
= mDateTime
.kDateTime();
911 local
.setTimeSpec(KDateTime::Spec::LocalZone());
912 showZone
= (local
.toString(QString::fromLatin1("%Z")) != tz
);
914 tm
= KGlobal::locale()->formatDateTime(mDateTime
.kDateTime(), KLocale::ShortDate
, KLocale::DateTimeFormatOptions(showZone
? KLocale::TimeZone
: 0));
920 /******************************************************************************
921 * Set the remaining time text in a reminder window.
922 * Called at the start of every day (at the user-defined start-of-day time).
924 void MessageWin::setRemainingTextDay()
927 int days
= KDateTime::currentLocalDate().daysTo(mDateTime
.date());
928 if (days
<= 0 && !mDateTime
.isDateOnly())
930 // The alarm is due today, so start refreshing every minute
931 MidnightTimer::disconnect(this, SLOT(setRemainingTextDay()));
932 setRemainingTextMinute();
933 MinuteTimer::connect(this, SLOT(setRemainingTextMinute())); // update every minute
938 text
= i18nc("@info", "Today");
940 text
= i18ncp("@info", "Tomorrow", "in %1 days' time", days
);
942 text
= i18ncp("@info", "in 1 week's time", "in %1 weeks' time", days
/7);
944 mRemainingText
->setText(text
);
947 /******************************************************************************
948 * Set the remaining time text in a reminder window.
949 * Called on every minute boundary.
951 void MessageWin::setRemainingTextMinute()
954 int mins
= (KDateTime::currentUtcDateTime().secsTo(mDateTime
.effectiveKDateTime()) + 59) / 60;
956 text
= i18ncp("@info", "in 1 minute's time", "in %1 minutes' time", (mins
> 0 ? mins
: 0));
957 else if (mins
% 60 == 0)
958 text
= i18ncp("@info", "in 1 hour's time", "in %1 hours' time", mins
/60);
961 QString hourText
= i18ncp("@item:intext inserted into 'in ... %1 minute's time' below", "1 hour", "%1 hours", mins
/60);
962 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
);
964 mRemainingText
->setText(text
);
967 /******************************************************************************
968 * Called when output is available from the command which is providing the text
969 * for this window. Add the output and resize the window to show it.
971 void MessageWin::readProcessOutput(ShellProcess
* proc
)
973 QByteArray data
= proc
->readAll();
976 // Strip any trailing newline, to avoid showing trailing blank line
977 // in message window.
978 if (mCommandText
->newLine())
979 mCommandText
->append("\n");
980 int nl
= data
.endsWith('\n') ? 1 : 0;
981 mCommandText
->setNewLine(nl
);
982 mCommandText
->insertPlainText(QString::fromLocal8Bit(data
.data(), data
.length() - nl
));
987 /******************************************************************************
988 * Save settings to the session managed config file, for restoration
989 * when the program is restored.
991 void MessageWin::saveProperties(KConfigGroup
& config
)
993 if (mShown
&& !mErrorWindow
&& !mAlwaysHide
)
996 config
.writeEntry("EventID", mEventId
.eventId());
997 config
.writeEntry("EventItemID", mEventItemId
);
999 config
.writeEntry("EventID", mEventId
);
1001 config
.writeEntry("AlarmType", static_cast<int>(mAlarmType
));
1002 if (mAlarmType
== KAAlarm::INVALID_ALARM
)
1003 kError() << "Invalid alarm: id=" << mEventId
<< ", alarm count=" << mEvent
.alarmCount();
1004 config
.writeEntry("Message", mMessage
);
1005 config
.writeEntry("Type", static_cast<int>(mAction
));
1006 config
.writeEntry("Font", mFont
);
1007 config
.writeEntry("BgColour", mBgColour
);
1008 config
.writeEntry("FgColour", mFgColour
);
1009 config
.writeEntry("ConfirmAck", mConfirmAck
);
1010 if (mDateTime
.isValid())
1012 //TODO: Write KDateTime when it becomes possible
1013 config
.writeEntry("Time", mDateTime
.effectiveDateTime());
1014 config
.writeEntry("DateOnly", mDateTime
.isDateOnly());
1016 if (mDateTime
.isUtc())
1017 zone
= QLatin1String("UTC");
1020 KTimeZone tz
= mDateTime
.timeZone();
1024 config
.writeEntry("TimeZone", zone
);
1026 if (mCloseTime
.isValid())
1027 config
.writeEntry("Expiry", mCloseTime
);
1028 if (mAudioRepeatPause
>= 0 && mSilenceButton
&& mSilenceButton
->isEnabled())
1030 // Only need to restart sound file playing if it's being repeated
1031 config
.writePathEntry("AudioFile", mAudioFile
);
1032 config
.writeEntry("Volume", static_cast<int>(mVolume
* 100));
1033 config
.writeEntry("AudioPause", mAudioRepeatPause
);
1035 config
.writeEntry("Speak", mSpeak
);
1036 config
.writeEntry("Height", height());
1037 config
.writeEntry("DeferMins", mDefaultDeferMinutes
);
1038 config
.writeEntry("NoDefer", mNoDefer
);
1039 config
.writeEntry("NoPostAction", mNoPostAction
);
1040 config
.writeEntry("KMailSerial", static_cast<qulonglong
>(mKMailSerialNumber
));
1041 config
.writeEntry("CmdErr", static_cast<int>(mCommandError
));
1042 config
.writeEntry("DontShowAgain", mDontShowAgain
);
1045 config
.writeEntry("Invalid", true);
1048 /******************************************************************************
1049 * Read settings from the session managed config file.
1050 * This function is automatically called whenever the app is being restored.
1051 * Read in whatever was saved in saveProperties().
1053 void MessageWin::readProperties(const KConfigGroup
& config
)
1055 mInvalid
= config
.readEntry("Invalid", false);
1057 mEventItemId
= config
.readEntry("EventItemID", Akonadi::Item::Id(-1));
1058 mCollection
= AkonadiModel::instance()->collectionForItem(mEventItemId
);
1059 mEventId
= EventId(mCollection
.id(), config
.readEntry("EventID"));
1061 mEventId
= config
.readEntry("EventID");
1063 mAlarmType
= static_cast<KAAlarm::Type
>(config
.readEntry("AlarmType", 0));
1064 if (mAlarmType
== KAAlarm::INVALID_ALARM
)
1067 kError() << "Invalid alarm: id=" << mEventId
;
1069 mMessage
= config
.readEntry("Message");
1070 mAction
= static_cast<KAEvent::SubAction
>(config
.readEntry("Type", 0));
1071 mFont
= config
.readEntry("Font", QFont());
1072 mBgColour
= config
.readEntry("BgColour", QColor(Qt::white
));
1073 mFgColour
= config
.readEntry("FgColour", QColor(Qt::black
));
1074 mConfirmAck
= config
.readEntry("ConfirmAck", false);
1075 QDateTime invalidDateTime
;
1076 QDateTime dt
= config
.readEntry("Time", invalidDateTime
);
1077 QString zone
= config
.readEntry("TimeZone");
1079 mDateTime
= KDateTime(dt
, KDateTime::ClockTime
);
1080 else if (zone
== QLatin1String("UTC"))
1082 dt
.setTimeSpec(Qt::UTC
);
1083 mDateTime
= KDateTime(dt
, KDateTime::UTC
);
1087 KTimeZone tz
= KSystemTimeZones::zone(zone
);
1088 mDateTime
= KDateTime(dt
, (tz
.isValid() ? tz
: KSystemTimeZones::local()));
1090 bool dateOnly
= config
.readEntry("DateOnly", false);
1092 mDateTime
.setDateOnly(true);
1093 mCloseTime
= config
.readEntry("Expiry", invalidDateTime
);
1094 mCloseTime
.setTimeSpec(Qt::UTC
);
1095 mAudioFile
= config
.readPathEntry("AudioFile", QString());
1096 mVolume
= static_cast<float>(config
.readEntry("Volume", 0)) / 100;
1099 if (!mAudioFile
.isEmpty()) // audio file URL was only saved if it repeats
1100 mAudioRepeatPause
= config
.readEntry("AudioPause", 0);
1101 mBeep
= false; // don't beep after restart (similar to not playing non-repeated sound file)
1102 mSpeak
= config
.readEntry("Speak", false);
1103 mRestoreHeight
= config
.readEntry("Height", 0);
1104 mDefaultDeferMinutes
= config
.readEntry("DeferMins", 0);
1105 mNoDefer
= config
.readEntry("NoDefer", false);
1106 mNoPostAction
= config
.readEntry("NoPostAction", true);
1107 mKMailSerialNumber
= static_cast<unsigned long>(config
.readEntry("KMailSerial", QVariant(QVariant::ULongLong
)).toULongLong());
1108 mCommandError
= KAEvent::CmdErrType(config
.readEntry("CmdErr", static_cast<int>(KAEvent::CMD_NO_ERROR
)));
1109 mDontShowAgain
= config
.readEntry("DontShowAgain", QString());
1112 mCollection
= Akonadi::Collection();
1116 kDebug() << mEventId
;
1117 if (mAlarmType
!= KAAlarm::INVALID_ALARM
)
1119 // Recreate the event from the calendar file (if possible)
1120 if (!mEventId
.isEmpty())
1122 KAEvent
* event
= AlarmCalendar::resources()->event(mEventId
);
1127 mResource
= AlarmCalendar::resources()->resourceForEvent(mEventId
);
1133 // It's not in the active calendar, so try the displaying or archive calendars
1135 retrieveEvent(mEvent
, mCollection
, mShowEdit
, mNoDefer
);
1137 retrieveEvent(mEvent
, mResource
, mShowEdit
, mNoDefer
);
1139 mNoDefer
= !mNoDefer
;
1146 /******************************************************************************
1147 * Redisplay alarms which were being shown when the program last exited.
1148 * Normally, these alarms will have been displayed by session restoration, but
1149 * if the program crashed or was killed, we can redisplay them here so that
1150 * they won't be lost.
1152 void MessageWin::redisplayAlarms()
1154 AlarmCalendar
* cal
= AlarmCalendar::displayCalendar();
1159 Akonadi::Collection collection
;
1161 AlarmResource
* resource
;
1163 Event::List events
= cal
->kcalEvents();
1164 for (int i
= 0, end
= events
.count(); i
< end
; ++i
)
1166 bool showDefer
, showEdit
;
1168 reinstateFromDisplaying(events
[i
], event
, collection
, showEdit
, showDefer
);
1169 if (!findEvent(EventId(event
)))
1171 reinstateFromDisplaying(events
[i
], event
, resource
, showEdit
, showDefer
);
1172 if (!findEvent(event
.id()))
1175 // This event should be displayed, but currently isn't being
1176 KAAlarm alarm
= event
.convertDisplayingAlarm();
1177 if (alarm
.type() == KAAlarm::INVALID_ALARM
)
1179 kError() << "Invalid alarm: id=" << event
.id();
1182 kDebug() << event
.id();
1183 bool login
= alarm
.repeatAtLogin();
1184 int flags
= NO_RESCHEDULE
| (login
? NO_DEFER
: 0) | NO_INIT_VIEW
;
1185 MessageWin
* win
= new MessageWin(&event
, alarm
, flags
);
1187 win
->mCollection
= collection
;
1188 bool rw
= CollectionControlModel::isWritableEnabled(collection
, event
.category()) > 0;
1190 win
->mResource
= resource
;
1191 bool rw
= resource
&& resource
->writable();
1193 win
->mShowEdit
= rw
? showEdit
: false;
1194 win
->mNoDefer
= (rw
&& !login
) ? !showDefer
: true;
1202 /******************************************************************************
1203 * Retrieves the event with the current ID from the displaying calendar file,
1204 * or if not found there, from the archive calendar.
1207 bool MessageWin::retrieveEvent(KAEvent
& event
, Akonadi::Collection
& resource
, bool& showEdit
, bool& showDefer
)
1209 bool MessageWin::retrieveEvent(KAEvent
& event
, AlarmResource
*& resource
, bool& showEdit
, bool& showDefer
)
1213 Event::Ptr kcalEvent
= AlarmCalendar::displayCalendar()->kcalEvent(CalEvent::uid(mEventId
.eventId(), CalEvent::DISPLAYING
));
1215 const Event
* kcalEvent
= AlarmCalendar::displayCalendar()->kcalEvent(CalEvent::uid(mEventId
, CalEvent::DISPLAYING
));
1217 if (!reinstateFromDisplaying(kcalEvent
, event
, resource
, showEdit
, showDefer
))
1219 // The event isn't in the displaying calendar.
1220 // Try to retrieve it from the archive calendar.
1223 Akonadi::Collection archiveCol
= CollectionControlModel::getStandard(CalEvent::ARCHIVED
);
1224 if (archiveCol
.isValid())
1225 ev
= AlarmCalendar::resources()->event(EventId(archiveCol
.id(), CalEvent::uid(mEventId
.eventId(), CalEvent::ARCHIVED
)));
1227 KAEvent
* ev
= AlarmCalendar::resources()->event(CalEvent::uid(mEventId
, CalEvent::ARCHIVED
));
1232 event
.setArchive(); // ensure that it gets re-archived if it's saved
1233 event
.setCategory(CalEvent::ACTIVE
);
1235 if (mEventId
.eventId() != event
.id())
1236 kError() << "Wrong event ID";
1237 event
.setEventId(mEventId
.eventId());
1238 resource
= Akonadi::Collection();
1240 if (mEventId
!= event
.id())
1241 kError() << "Wrong event ID";
1242 event
.setEventId(mEventId
);
1247 kDebug() << event
.id() << ": success";
1252 /******************************************************************************
1253 * Retrieves the displayed event from the calendar file, or if not found there,
1254 * from the displaying calendar.
1257 bool MessageWin::reinstateFromDisplaying(const Event::Ptr
& kcalEvent
, KAEvent
& event
, Akonadi::Collection
& collection
, bool& showEdit
, bool& showDefer
)
1259 bool MessageWin::reinstateFromDisplaying(const Event
* kcalEvent
, KAEvent
& event
, AlarmResource
*& resource
, bool& showEdit
, bool& showDefer
)
1265 Akonadi::Collection::Id collectionId
;
1266 event
.reinstateFromDisplaying(kcalEvent
, collectionId
, showEdit
, showDefer
);
1267 collection
= AkonadiModel::instance()->collectionById(collectionId
);
1270 event
.reinstateFromDisplaying(kcalEvent
, resourceID
, showEdit
, showDefer
);
1271 resource
= AlarmResources::instance()->resourceWithId(resourceID
);
1272 if (resource
&& !resource
->isOpen())
1275 kDebug() << event
.id() << ": success";
1279 /******************************************************************************
1280 * Called when an alarm is currently being displayed, to store a copy of the
1281 * alarm in the displaying calendar, and to reschedule it for its next repetition.
1282 * If no repetitions remain, cancel it.
1284 void MessageWin::alarmShowing(KAEvent
& event
)
1286 kDebug() << event
.id() << "," << KAAlarm::debugType(mAlarmType
);
1288 const KCal::Event
* kcalEvent
= AlarmCalendar::resources()->kcalEvent(event
.id());
1291 kError() << "Event ID not found:" << event
.id();
1295 KAAlarm alarm
= event
.alarm(mAlarmType
);
1296 if (!alarm
.isValid())
1298 kError() << "Alarm type not found:" << event
.id() << ":" << mAlarmType
;
1303 // Copy the alarm to the displaying calendar in case of a crash, etc.
1306 Akonadi::Collection collection
= AkonadiModel::instance()->collectionForItem(event
.itemId());
1307 dispEvent
.setDisplaying(event
, mAlarmType
, collection
.id(),
1308 mDateTime
.effectiveKDateTime(), mShowEdit
, !mNoDefer
);
1310 KAEvent
* dispEvent
= new KAEvent
;
1311 AlarmResource
* resource
= AlarmResources::instance()->resource(kcalEvent
);
1312 dispEvent
->setDisplaying(event
, mAlarmType
, (resource
? resource
->identifier() : QString()),
1313 mDateTime
.effectiveKDateTime(), mShowEdit
, !mNoDefer
);
1315 AlarmCalendar
* cal
= AlarmCalendar::displayCalendarOpen();
1319 cal
->deleteDisplayEvent(dispEvent
.id()); // in case it already exists
1320 cal
->addEvent(dispEvent
);
1322 cal
->deleteEvent(dispEvent
->id()); // in case it already exists
1323 if (!cal
->addEvent(dispEvent
))
1333 theApp()->rescheduleAlarm(event
, alarm
);
1336 /******************************************************************************
1337 * Spread alarm windows over the screen so that they are all visible, or pile
1338 * them on top of each other again.
1339 * Reply = true if windows are now scattered, false if piled up.
1341 bool MessageWin::spread(bool scatter
)
1343 if (instanceCount(true) <= 1) // ignore always-hidden windows
1346 QRect desk
= KAlarm::desktopWorkArea(); // get the usable area of the desktop
1347 if (scatter
== isSpread(desk
.topLeft()))
1352 // Usually there won't be many windows, so a crude
1353 // scattering algorithm should suffice.
1354 int x
= desk
.left();
1357 for (int errmsgs
= 0; errmsgs
< 2; ++errmsgs
)
1359 // Display alarm messages first, then error messages, since most
1360 // error messages tend to be the same height.
1361 for (int i
= 0, end
= mWindowList
.count(); i
< end
; ++i
)
1363 MessageWin
* w
= mWindowList
[i
];
1364 if ((!errmsgs
&& w
->mErrorWindow
)
1365 || (errmsgs
&& !w
->mErrorWindow
))
1367 QSize sz
= w
->frameGeometry().size();
1368 if (x
+ sz
.width() > desk
.right())
1374 if (y
+ sz
.height() > desk
.bottom())
1376 ytmp
= desk
.bottom() - sz
.height();
1377 if (ytmp
< desk
.top())
1382 if (ytmp
+ sz
.height() > ynext
)
1383 ynext
= ytmp
+ sz
.height();
1389 // Move all windows to the top left corner
1390 for (int i
= 0, end
= mWindowList
.count(); i
< end
; ++i
)
1391 mWindowList
[i
]->move(desk
.topLeft());
1396 /******************************************************************************
1397 * Check whether message windows are all piled up, or are spread out.
1398 * Reply = true if windows are currently spread, false if piled up.
1400 bool MessageWin::isSpread(const QPoint
& topLeft
)
1402 for (int i
= 0, end
= mWindowList
.count(); i
< end
; ++i
)
1404 if (mWindowList
[i
]->pos() != topLeft
)
1410 /******************************************************************************
1411 * Returns the existing message window (if any) which is displaying the event
1412 * with the specified ID.
1415 MessageWin
* MessageWin::findEvent(const EventId
& eventId
)
1417 MessageWin
* MessageWin::findEvent(const QString
& eventId
)
1420 if (!eventId
.isEmpty())
1422 for (int i
= 0, end
= mWindowList
.count(); i
< end
; ++i
)
1424 MessageWin
* w
= mWindowList
[i
];
1425 if (w
->mEventId
== eventId
&& !w
->mErrorWindow
)
1432 /******************************************************************************
1433 * Beep and play the audio file, as appropriate.
1435 void MessageWin::playAudio()
1439 // Beep using two methods, in case the sound card/speakers are switched off or not working
1440 QApplication::beep(); // beep through the internal speaker
1441 KNotification::beep(); // beep through the sound card & speakers
1443 if (!mAudioFile
.isEmpty())
1445 if (!mVolume
&& mFadeVolume
<= 0)
1446 return; // ensure zero volume doesn't play anything
1447 startAudio(); // play the audio file
1451 // The message is to be spoken. In case of error messges,
1452 // call it on a timer to allow the window to display first.
1453 QTimer::singleShot(0, this, SLOT(slotSpeak()));
1457 /******************************************************************************
1458 * Speak the message.
1459 * Called asynchronously to avoid delaying the display of the message.
1461 void MessageWin::slotSpeak()
1464 OrgKdeKSpeechInterface
* kspeech
= theApp()->kspeechInterface(error
);
1467 if (!haveErrorMessage(ErrMsg_Speak
))
1469 KAMessageBox::detailedError(MainWindow::mainMainWindow(), i18nc("@info", "Unable to speak message"), error
);
1470 clearErrorMessage(ErrMsg_Speak
);
1474 if (!kspeech
->say(mMessage
, 0))
1476 kDebug() << "SayMessage() D-Bus error";
1477 if (!haveErrorMessage(ErrMsg_Speak
))
1479 KAMessageBox::detailedError(MainWindow::mainMainWindow(), i18nc("@info", "Unable to speak message"), i18nc("@info", "D-Bus call say() failed"));
1480 clearErrorMessage(ErrMsg_Speak
);
1485 /******************************************************************************
1486 * Called when another window's audio thread has been destructed.
1487 * Start playing this window's audio file. Because initialising the sound system
1488 * and loading the file may take some time, it is called in a separate thread to
1489 * allow the window to display first.
1491 void MessageWin::startAudio()
1495 // An audio file is already playing for another message
1496 // window, so wait until it has finished.
1497 connect(mAudioThread
, SIGNAL(destroyed(QObject
*)), SLOT(audioTerminating()));
1501 kDebug() << QThread::currentThread();
1502 mAudioThread
= new AudioThread(this, mAudioFile
, mVolume
, mFadeVolume
, mFadeSeconds
, mAudioRepeatPause
);
1503 connect(mAudioThread
, SIGNAL(readyToPlay()), SLOT(playReady()));
1504 connect(mAudioThread
, SIGNAL(finished()), SLOT(playFinished()));
1506 connect(mSilenceButton
, SIGNAL(clicked()), mAudioThread
, SLOT(quit()));
1507 // Notify after creating mAudioThread, so that isAudioPlaying() will
1508 // return the correct value.
1509 theApp()->notifyAudioPlaying(true);
1510 mAudioThread
->start();
1514 /******************************************************************************
1515 * Return whether audio playback is currently active.
1517 bool MessageWin::isAudioPlaying()
1519 return mAudioThread
;
1522 /******************************************************************************
1523 * Stop audio playback.
1525 void MessageWin::stopAudio(bool wait
)
1529 mAudioThread
->stop(wait
);
1532 /******************************************************************************
1533 * Called when another window's audio thread is being destructed.
1534 * Wait until the destructor has finished.
1536 void MessageWin::audioTerminating()
1538 QTimer::singleShot(0, this, SLOT(startAudio()));
1541 /******************************************************************************
1542 * Called when the audio file is ready to start playing.
1544 void MessageWin::playReady()
1547 mSilenceButton
->setEnabled(true);
1550 /******************************************************************************
1551 * Called when the audio file thread finishes.
1553 void MessageWin::playFinished()
1556 mSilenceButton
->setEnabled(false);
1557 if (mAudioThread
) // mAudioThread can actually be null here!
1559 QString errmsg
= mAudioThread
->error();
1560 if (!errmsg
.isEmpty() && !haveErrorMessage(ErrMsg_AudioFile
))
1562 KAMessageBox::error(this, errmsg
);
1563 clearErrorMessage(ErrMsg_AudioFile
);
1566 delete mAudioThread
.data();
1571 /******************************************************************************
1572 * Constructor for audio thread.
1574 AudioThread::AudioThread(MessageWin
* parent
, const QString
& audioFile
, float volume
, float fadeVolume
, int fadeSeconds
, int repeatPause
)
1578 mFadeVolume(fadeVolume
),
1579 mFadeSeconds(fadeSeconds
),
1580 mRepeatPause(repeatPause
),
1584 kError() << "mAudioOwner already set";
1585 mAudioOwner
= parent
;
1588 /******************************************************************************
1589 * Destructor for audio thread. Waits for thread completion and tidies up.
1590 * Note that this destructor is executed in the parent thread.
1592 AudioThread::~AudioThread()
1595 stop(true); // stop playing and tidy up (timeout 3 seconds)
1596 delete mAudioObject
;
1598 if (mAudioOwner
== parent())
1600 // Notify after deleting mAudioThread, so that isAudioPlaying() will
1601 // return the correct value.
1602 QTimer::singleShot(0, theApp(), SLOT(notifyAudioStopped()));
1605 /******************************************************************************
1606 * Quits the thread and waits for thread completion and tidies up.
1608 void AudioThread::stop(bool waiT
)
1611 quit(); // stop playing and tidy up
1612 wait(3000); // wait for run() to exit (timeout 3 seconds)
1615 // Something has gone wrong - forcibly kill the thread
1622 /******************************************************************************
1623 * Kick off the thread to play the audio file.
1625 void AudioThread::run()
1633 kDebug() << QThread::currentThread() << mFile
;
1634 QString audioFile
= mFile
;
1635 mFile
= KAlarm::pathOrUrl(mFile
);
1636 Phonon::MediaSource
source(audioFile
);
1637 if (source
.type() == Phonon::MediaSource::Invalid
)
1639 mError
= i18nc("@info", "Cannot open audio file: <filename>%1</filename>", audioFile
);
1641 kError() << "Open failure:" << audioFile
;
1644 mAudioObject
= new Phonon::MediaObject();
1645 mAudioObject
->setCurrentSource(source
);
1646 mAudioObject
->setTransitionTime(100); // workaround to prevent clipping of end of files in Xine backend
1647 Phonon::AudioOutput
* output
= new Phonon::AudioOutput(Phonon::NotificationCategory
, mAudioObject
);
1648 mPath
= Phonon::createPath(mAudioObject
, output
);
1649 if (mVolume
>= 0 || mFadeVolume
>= 0)
1651 float vol
= (mVolume
>= 0) ? mVolume
: output
->volume();
1652 float maxvol
= qMax(vol
, mFadeVolume
);
1653 output
->setVolume(maxvol
);
1654 if (mFadeVolume
>= 0 && mFadeSeconds
> 0)
1656 Phonon::VolumeFaderEffect
* fader
= new Phonon::VolumeFaderEffect(mAudioObject
);
1657 fader
->setVolume(mFadeVolume
/ maxvol
);
1658 fader
->fadeTo(mVolume
/ maxvol
, mFadeSeconds
* 1000);
1659 mPath
.insertEffect(fader
);
1662 connect(mAudioObject
, SIGNAL(stateChanged(Phonon::State
,Phonon::State
)), SLOT(playStateChanged(Phonon::State
)), Qt::DirectConnection
);
1663 connect(mAudioObject
, SIGNAL(finished()), SLOT(checkAudioPlay()), Qt::DirectConnection
);
1664 mPlayedOnce
= false;
1670 // Start an event loop.
1671 // The function will exit once exit() or quit() is called.
1672 // First, ensure that the thread object is deleted once it has completed.
1673 connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
1678 /******************************************************************************
1679 * Called when the audio file has loaded and is ready to play, or when play
1681 * If it is ready to play, start playing it (for the first time or repeated).
1682 * If play has not yet completed, wait a bit longer.
1684 void AudioThread::checkAudioPlay()
1696 // The file has loaded and is ready to play, or play has completed
1699 if (mRepeatPause
< 0)
1701 // Play has completed
1706 if (mRepeatPause
> 0)
1708 // Pause before playing the file again
1710 QTimer::singleShot(mRepeatPause
* 1000, this, SLOT(checkAudioPlay()));
1718 // Start playing the file, either for the first time or again
1719 kDebug() << "start";
1720 mAudioObject
->play();
1724 /******************************************************************************
1725 * Called when the playback object changes state.
1726 * If an error has occurred, quit and return the error to the caller.
1728 void AudioThread::playStateChanged(Phonon::State newState
)
1730 if (newState
== Phonon::ErrorState
)
1732 QMutexLocker
locker(&mMutex
);
1733 QString err
= mAudioObject
->errorString();
1736 kError() << "Play failure:" << mFile
<< ":" << err
;
1737 mError
= i18nc("@info", "<para>Error playing audio file: <filename>%1</filename></para><para>%2</para>", mFile
, err
);
1743 /******************************************************************************
1744 * Called when play completes, the Silence button is clicked, or the window is
1745 * closed, to terminate audio access.
1747 void AudioThread::stopPlay()
1752 mAudioObject
->stop();
1753 QList
<Phonon::Effect
*> effects
= mPath
.effects();
1754 for (int i
= 0; i
< effects
.count(); ++i
)
1756 mPath
.removeEffect(effects
[i
]);
1759 delete mAudioObject
;
1763 quit(); // exit the event loop, if it's still running
1766 QString
AudioThread::error() const
1768 QMutexLocker
locker(&mMutex
);
1772 /******************************************************************************
1773 * Raise the alarm window, re-output any required audio notification, and
1774 * reschedule the alarm in the calendar file.
1776 void MessageWin::repeat(const KAAlarm
& alarm
)
1782 // Cancel any deferral dialog so that the user notices something's going on,
1783 // and also because the deferral time limit will have changed.
1787 KAEvent
* event
= mEventId
.isEmpty() ? 0 : AlarmCalendar::resources()->event(mEventId
);
1790 mAlarmType
= alarm
.type(); // store new alarm type for use if it is later deferred
1795 if (!mDeferDlg
|| Preferences::modalMessages())
1800 if (mDeferButton
->isVisible())
1802 mDeferButton
->setEnabled(true);
1803 setDeferralLimit(*event
); // ensure that button is disabled when alarm can't be deferred any more
1806 alarmShowing(*event
);
1810 /******************************************************************************
1811 * Display the window.
1812 * If windows are being positioned away from the mouse cursor, it is initially
1813 * positioned at the top left to slightly reduce the number of times the
1814 * windows need to be moved in showEvent().
1816 void MessageWin::show()
1818 if (mCloseTime
.isValid())
1820 // Set a timer to auto-close the window
1821 int delay
= KDateTime::currentUtcDateTime().dateTime().secsTo(mCloseTime
);
1824 QTimer::singleShot(delay
* 1000, this, SLOT(close()));
1826 return; // don't show the window if auto-closing is already due
1828 if (Preferences::messageButtonDelay() == 0)
1830 MainWindowBase::show();
1833 /******************************************************************************
1834 * Returns the window's recommended size exclusive of its frame.
1836 QSize
MessageWin::sizeHint() const
1841 case KAEvent::MESSAGE
:
1842 desired
= MainWindowBase::sizeHint();
1844 case KAEvent::COMMAND
:
1847 // For command output, expand the window to accommodate the text
1848 QSize texthint
= mCommandText
->sizeHint();
1849 int w
= texthint
.width() + 2*KDialog::marginHint();
1852 int ypadding
= height() - mCommandText
->height();
1853 desired
= QSize(w
, texthint
.height() + ypadding
);
1856 // fall through to default
1858 return MainWindowBase::sizeHint();
1861 // Limit the size to fit inside the working area of the desktop
1862 QSize desktop
= KAlarm::desktopWorkArea(mScreenNumber
).size();
1863 QSize frameThickness
= frameGeometry().size() - geometry().size(); // title bar & window frame
1864 return desired
.boundedTo(desktop
- frameThickness
);
1867 /******************************************************************************
1868 * Called when the window is shown.
1869 * The first time, output any required audio notification, and reschedule or
1870 * delete the event from the calendar file.
1872 void MessageWin::showEvent(QShowEvent
* se
)
1874 MainWindowBase::showEvent(se
);
1877 if (mErrorWindow
|| mAlarmType
== KAAlarm::INVALID_ALARM
)
1879 // Don't bother repositioning error messages,
1880 // and invalid alarms should be deleted anyway.
1885 /* Set the window size.
1886 * Note that the frame thickness is not yet known when this
1887 * method is called, so for large windows the size needs to be
1890 bool execComplete
= true;
1891 QSize s
= sizeHint(); // fit the window round the message
1892 if (mAction
== KAEvent::FILE && !mErrorMsgs
.count())
1893 KAlarm::readConfigWindowSize("FileMessage", s
);
1896 QRect desk
= KAlarm::desktopWorkArea(mScreenNumber
);
1897 QRect frame
= frameGeometry();
1899 mButtonDelay
= Preferences::messageButtonDelay() * 1000;
1902 // Position the window in the middle of the screen, and
1903 // delay enabling the buttons.
1904 mPositioning
= true;
1905 move((desk
.width() - frame
.width())/2, (desk
.height() - frame
.height())/2);
1906 execComplete
= false;
1910 /* Try to ensure that the window can't accidentally be acknowledged
1911 * by the user clicking the mouse just as it appears.
1912 * To achieve this, move the window so that the OK button is as far away
1913 * from the cursor as possible. If the buttons are still too close to the
1914 * cursor, disable the buttons for a short time.
1915 * N.B. This can't be done in show(), since the geometry of the window
1916 * is not known until it is displayed. Unfortunately by moving the
1917 * window in showEvent(), a flicker is unavoidable.
1918 * See the Qt documentation on window geometry for more details.
1920 // PROBLEM: The frame size is not known yet!
1921 QPoint cursor
= QCursor::pos();
1922 QRect rect
= geometry();
1923 // Find the offsets from the outside of the frame to the edges of the OK button
1924 QRect
button(mOkButton
->mapToParent(QPoint(0, 0)), mOkButton
->mapToParent(mOkButton
->rect().bottomRight()));
1925 int buttonLeft
= button
.left() + rect
.left() - frame
.left();
1926 int buttonRight
= width() - button
.right() + frame
.right() - rect
.right();
1927 int buttonTop
= button
.top() + rect
.top() - frame
.top();
1928 int buttonBottom
= height() - button
.bottom() + frame
.bottom() - rect
.bottom();
1930 int centrex
= (desk
.width() + buttonLeft
- buttonRight
) / 2;
1931 int centrey
= (desk
.height() + buttonTop
- buttonBottom
) / 2;
1932 int x
= (cursor
.x() < centrex
) ? desk
.right() - frame
.width() : desk
.left();
1933 int y
= (cursor
.y() < centrey
) ? desk
.bottom() - frame
.height() : desk
.top();
1935 // Find the enclosing rectangle for the new button positions
1936 // and check if the cursor is too near
1937 QRect buttons
= mOkButton
->geometry().unite(mKAlarmButton
->geometry());
1938 buttons
.translate(rect
.left() + x
- frame
.left(), rect
.top() + y
- frame
.top());
1939 int minDistance
= proximityMultiple
* mOkButton
->height();
1940 if ((abs(cursor
.x() - buttons
.left()) < minDistance
1941 || abs(cursor
.x() - buttons
.right()) < minDistance
)
1942 && (abs(cursor
.y() - buttons
.top()) < minDistance
1943 || abs(cursor
.y() - buttons
.bottom()) < minDistance
))
1944 mButtonDelay
= proximityButtonDelay
; // too near - disable buttons initially
1946 if (x
!= frame
.left() || y
!= frame
.top())
1948 mPositioning
= true;
1950 execComplete
= false;
1954 displayComplete(); // play audio, etc.
1957 // Set the window size etc. once the frame size is known
1958 QTimer::singleShot(0, this, SLOT(frameDrawn()));
1963 /******************************************************************************
1964 * Called when the window has been moved.
1966 void MessageWin::moveEvent(QMoveEvent
* e
)
1968 MainWindowBase::moveEvent(e
);
1969 theApp()->setSpreadWindowsState(isSpread(KAlarm::desktopWorkArea(mScreenNumber
).topLeft()));
1972 // The window has just been initially positioned
1973 mPositioning
= false;
1974 displayComplete(); // play audio, etc.
1978 /******************************************************************************
1979 * Called after (hopefully) the window frame size is known.
1980 * Reset the initial window size if it exceeds the working area of the desktop.
1981 * Set the 'spread windows' menu item status.
1983 void MessageWin::frameDrawn()
1985 if (!mErrorWindow
&& mAction
== KAEvent::MESSAGE
)
1987 QSize s
= sizeHint();
1988 if (width() > s
.width() || height() > s
.height())
1991 theApp()->setSpreadWindowsState(isSpread(KAlarm::desktopWorkArea(mScreenNumber
).topLeft()));
1994 /******************************************************************************
1995 * Called when the window has been displayed properly (in its correct position),
1996 * to play sounds and reschedule the event.
1998 void MessageWin::displayComplete()
2001 if (mRescheduleEvent
)
2002 alarmShowing(mEvent
);
2006 // Enable the window's buttons either now or after the configured delay
2007 if (mButtonDelay
> 0)
2008 QTimer::singleShot(mButtonDelay
, this, SLOT(enableButtons()));
2014 /******************************************************************************
2015 * Enable the window's buttons.
2017 void MessageWin::enableButtons()
2019 mOkButton
->setEnabled(true);
2020 mKAlarmButton
->setEnabled(true);
2021 if (mDeferButton
->isVisible() && !mDisableDeferral
)
2022 mDeferButton
->setEnabled(true);
2024 mEditButton
->setEnabled(true);
2026 mKMailButton
->setEnabled(true);
2029 /******************************************************************************
2030 * Called when the window's size has changed (before it is painted).
2032 void MessageWin::resizeEvent(QResizeEvent
* re
)
2036 // Restore the window height on session restoration
2037 if (mRestoreHeight
!= re
->size().height())
2039 QSize size
= re
->size();
2040 size
.setHeight(mRestoreHeight
);
2043 else if (isVisible())
2048 if (mShown
&& mAction
== KAEvent::FILE && !mErrorMsgs
.count())
2049 KAlarm::writeConfigWindowSize("FileMessage", re
->size());
2050 MainWindowBase::resizeEvent(re
);
2054 /******************************************************************************
2055 * Called when a close event is received.
2056 * Only quits the application if there is no system tray icon displayed.
2058 void MessageWin::closeEvent(QCloseEvent
* ce
)
2060 // Don't prompt or delete the alarm from the display calendar if the session is closing
2061 if (!mErrorWindow
&& !theApp()->sessionClosingDown())
2063 if (mConfirmAck
&& !mNoCloseConfirm
)
2065 // Ask for confirmation of acknowledgement. Use warningYesNo() because its default is No.
2066 if (KAMessageBox::warningYesNo(this, i18nc("@info", "Do you really want to acknowledge this alarm?"),
2067 i18nc("@action:button", "Acknowledge Alarm"), KGuiItem(i18nc("@action:button", "Acknowledge")), KStandardGuiItem::cancel())
2068 != KMessageBox::Yes
)
2074 if (!mEventId
.isEmpty())
2076 // Delete from the display calendar
2078 KAlarm::deleteDisplayEvent(CalEvent::uid(mEventId
.eventId(), CalEvent::DISPLAYING
));
2080 KAlarm::deleteDisplayEvent(CalEvent::uid(mEventId
, CalEvent::DISPLAYING
));
2084 MainWindowBase::closeEvent(ce
);
2087 /******************************************************************************
2088 * Called when the OK button is clicked.
2090 void MessageWin::slotOk()
2092 if (mDontShowAgainCheck
&& mDontShowAgainCheck
->isChecked())
2093 KAlarm::setDontShowErrors(mEventId
, mDontShowAgain
);
2097 #ifdef KMAIL_SUPPORTED
2098 /******************************************************************************
2099 * Called when the KMail button is clicked.
2100 * Tells KMail to display the email message displayed in this message window.
2102 void MessageWin::slotShowKMailMessage()
2105 if (!mKMailSerialNumber
)
2107 QString err
= KAlarm::runKMail(false);
2110 KAMessageBox::sorry(this, err
);
2113 org::kde::kmail::kmail
kmail(KMAIL_DBUS_SERVICE
, KMAIL_DBUS_PATH
, QDBusConnection::sessionBus());
2114 QDBusReply
<bool> reply
= kmail
.showMail((qulonglong
)mKMailSerialNumber
, QString());
2115 if (!reply
.isValid())
2116 kError() << "kmail D-Bus call failed:" << reply
.error().message();
2117 else if (!reply
.value())
2118 KAMessageBox::sorry(this, i18nc("@info", "Unable to locate this email in <application>KMail</application>"));
2122 /******************************************************************************
2123 * Called when the Edit... button is clicked.
2124 * Displays the alarm edit dialog.
2126 * NOTE: The alarm edit dialog is made a child of the main window, not this
2127 * window, so that if this window closes before the dialog (e.g. on
2128 * auto-close), KAlarm doesn't crash. The dialog is set non-modal so that
2129 * the main window is unaffected, but modal mode is simulated so that
2130 * this window is inactive while the dialog is open.
2132 void MessageWin::slotEdit()
2135 MainWindow
* mainWin
= MainWindow::mainMainWindow();
2136 mEditDlg
= EditAlarmDlg::create(false, &mOriginalEvent
, false, mainWin
, EditAlarmDlg::RES_IGNORE
);
2137 KWindowSystem::setMainWindow(mEditDlg
, winId());
2138 KWindowSystem::setOnAllDesktops(mEditDlg
->winId(), false);
2139 setButtonsReadOnly(true);
2140 connect(mEditDlg
, SIGNAL(accepted()), SLOT(editCloseOk()));
2141 connect(mEditDlg
, SIGNAL(rejected()), SLOT(editCloseCancel()));
2142 connect(mEditDlg
, SIGNAL(destroyed(QObject
*)), SLOT(editCloseCancel()));
2143 connect(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId
)), SLOT(activeWindowChanged(WId
)));
2145 mainWin
->editAlarm(mEditDlg
, mOriginalEvent
);
2147 mainWin
->editAlarm(mEditDlg
, mOriginalEvent
, mResource
);
2151 /******************************************************************************
2152 * Called when OK is clicked in the alarm edit dialog invoked by the Edit button.
2153 * Closes the window.
2155 void MessageWin::editCloseOk()
2158 mNoCloseConfirm
= true; // allow window to close without confirmation prompt
2162 /******************************************************************************
2163 * Called when Cancel is clicked in the alarm edit dialog invoked by the Edit
2164 * button, or when the dialog is deleted.
2166 void MessageWin::editCloseCancel()
2169 setButtonsReadOnly(false);
2172 /******************************************************************************
2173 * Called when the active window has changed. If this window has become the
2174 * active window and there is an alarm edit dialog, simulate a modal dialog by
2175 * making the alarm edit dialog the active window instead.
2177 void MessageWin::activeWindowChanged(WId win
)
2179 if (mEditDlg
&& win
== winId())
2180 KWindowSystem::activateWindow(mEditDlg
->winId());
2183 /******************************************************************************
2184 * Set or clear the read-only state of the dialog buttons.
2186 void MessageWin::setButtonsReadOnly(bool ro
)
2188 mOkButton
->setReadOnly(ro
, true);
2189 mDeferButton
->setReadOnly(ro
, true);
2190 mEditButton
->setReadOnly(ro
, true);
2192 mSilenceButton
->setReadOnly(ro
, true);
2194 mKMailButton
->setReadOnly(ro
, true);
2195 mKAlarmButton
->setReadOnly(ro
, true);
2198 /******************************************************************************
2199 * Set up to disable the defer button when the deferral limit is reached.
2201 void MessageWin::setDeferralLimit(const KAEvent
& event
)
2203 mDeferLimit
= event
.deferralLimit().effectiveKDateTime().toUtc().dateTime();
2204 MidnightTimer::connect(this, SLOT(checkDeferralLimit())); // check every day
2205 mDisableDeferral
= false;
2206 checkDeferralLimit();
2209 /******************************************************************************
2210 * Check whether the deferral limit has been reached.
2211 * If so, disable the Defer button.
2212 * N.B. Ideally, just a single QTimer::singleShot() call would be made to disable
2213 * the defer button at the corret time. But for a 32-bit integer, the
2214 * milliseconds parameter overflows in about 25 days, so instead a daily
2215 * check is done until the day when the deferral limit is reached, followed
2216 * by a non-overflowing QTimer::singleShot() call.
2218 void MessageWin::checkDeferralLimit()
2220 if (!mDeferButton
->isEnabled() || !mDeferLimit
.isValid())
2222 int n
= KDateTime::currentLocalDate().daysTo(KDateTime(mDeferLimit
, KDateTime::LocalZone
).date());
2225 MidnightTimer::disconnect(this, SLOT(checkDeferralLimit()));
2228 // The deferral limit will be reached today
2229 n
= KDateTime::currentUtcDateTime().dateTime().secsTo(mDeferLimit
);
2232 QTimer::singleShot(n
* 1000, this, SLOT(checkDeferralLimit()));
2236 mDeferButton
->setEnabled(false);
2237 mDisableDeferral
= true;
2240 /******************************************************************************
2241 * Called when the Defer... button is clicked.
2242 * Displays the defer message dialog.
2244 void MessageWin::slotDefer()
2246 mDeferDlg
= new DeferAlarmDlg(KDateTime::currentDateTime(Preferences::timeZone()).addSecs(60), mDateTime
.isDateOnly(), false, this);
2247 mDeferDlg
->setObjectName("DeferDlg"); // used by LikeBack
2248 mDeferDlg
->setDeferMinutes(mDefaultDeferMinutes
> 0 ? mDefaultDeferMinutes
: Preferences::defaultDeferTime());
2249 mDeferDlg
->setLimit(mEvent
);
2250 if (!Preferences::modalMessages())
2252 if (mDeferDlg
->exec() == QDialog::Accepted
)
2254 DateTime dateTime
= mDeferDlg
->getDateTime();
2255 int delayMins
= mDeferDlg
->deferMinutes();
2256 // Fetch the up-to-date alarm from the calendar. Note that it could have
2257 // changed since it was displayed.
2258 const KAEvent
* event
= mEventId
.isEmpty() ? 0 : AlarmCalendar::resources()->event(mEventId
);
2261 // The event still exists in the active calendar
2262 kDebug() << "Deferring event" << mEventId
;
2263 KAEvent
newev(*event
);
2264 newev
.defer(dateTime
, (mAlarmType
& KAAlarm::REMINDER_ALARM
), true);
2265 newev
.setDeferDefaultMinutes(delayMins
);
2266 KAlarm::updateEvent(newev
, mDeferDlg
, true);
2267 if (newev
.deferred())
2268 mNoPostAction
= true;
2272 // Try to retrieve the event from the displaying or archive calendars
2274 Akonadi::Collection collection
;
2276 AlarmResource
* resource
= 0;
2279 bool showEdit
, showDefer
;
2281 if (!retrieveEvent(event
, collection
, showEdit
, showDefer
))
2283 if (!retrieveEvent(event
, resource
, showEdit
, showDefer
))
2286 // The event doesn't exist any more !?!, so recurrence data,
2287 // flags, and more, have been lost.
2288 KAMessageBox::error(this, i18nc("@info", "<para>Cannot defer alarm:</para><para>Alarm not found.</para>"));
2292 mDeferButton
->setEnabled(false);
2293 mEditButton
->setEnabled(false);
2296 kDebug() << "Deferring retrieved event" << mEventId
;
2297 event
.defer(dateTime
, (mAlarmType
& KAAlarm::REMINDER_ALARM
), true);
2298 event
.setDeferDefaultMinutes(delayMins
);
2299 event
.setCommandError(mCommandError
);
2300 // Add the event back into the calendar file, retaining its ID
2301 // and not updating KOrganizer.
2303 KAlarm::addEvent(event
, &collection
, mDeferDlg
, KAlarm::USE_EVENT_ID
);
2305 KAlarm::addEvent(event
, resource
, mDeferDlg
, KAlarm::USE_EVENT_ID
);
2307 if (event
.deferred())
2308 mNoPostAction
= true;
2309 // Finally delete it from the archived calendar now that it has
2310 // been reactivated.
2311 event
.setCategory(CalEvent::ARCHIVED
);
2312 KAlarm::deleteEvent(event
, false);
2314 if (theApp()->wantShowInSystemTray())
2316 // Alarms are to be displayed only if the system tray icon is running,
2317 // so start it if necessary so that the deferred alarm will be shown.
2318 theApp()->displayTrayIcon(true);
2320 mNoCloseConfirm
= true; // allow window to close without confirmation prompt
2329 /******************************************************************************
2330 * Called when the KAlarm icon button in the message window is clicked.
2331 * Displays the main window, with the appropriate alarm selected.
2333 void MessageWin::displayMainWindow()
2336 KAlarm::displayMainWindowSelected(mEventItemId
);
2338 KAlarm::displayMainWindowSelected(mEventId
);
2342 /******************************************************************************
2343 * Check whether the specified error message is already displayed for this
2344 * alarm, and note that it will now be displayed.
2345 * Reply = true if message is already displayed.
2347 bool MessageWin::haveErrorMessage(unsigned msg
) const
2349 if (!mErrorMessages
.contains(mEventId
))
2350 mErrorMessages
.insert(mEventId
, 0);
2351 bool result
= (mErrorMessages
[mEventId
] & msg
);
2352 mErrorMessages
[mEventId
] |= msg
;
2356 void MessageWin::clearErrorMessage(unsigned msg
) const
2358 if (mErrorMessages
.contains(mEventId
))
2360 if (mErrorMessages
[mEventId
] == msg
)
2361 mErrorMessages
.remove(mEventId
);
2363 mErrorMessages
[mEventId
] &= ~msg
;
2368 /******************************************************************************
2369 * Check whether the message window should be modal, i.e. with title bar etc.
2370 * Normally this follows the Preferences setting, but if there is a full screen
2371 * window displayed, on X11 the message window has to bypass the window manager
2372 * in order to display on top of it (which has the side effect that it will have
2373 * no window decoration).
2375 * Also find the usable area of the desktop (excluding panel etc.), on the
2376 * appropriate screen if there are multiple screens.
2378 bool MessageWin::getWorkAreaAndModal()
2381 bool modal
= Preferences::modalMessages();
2383 QDesktopWidget
* desktop
= qApp
->desktop();
2384 int numScreens
= desktop
->numScreens();
2387 // There are multiple screens.
2388 // Check for any full screen windows, even if they are not the active
2389 // window, and try not to show the alarm message their screens.
2390 mScreenNumber
= desktop
->screenNumber(MainWindow::mainMainWindow()); // default = KAlarm's screen
2391 if (desktop
->isVirtualDesktop())
2393 // The screens form a single virtual desktop.
2394 // Xinerama, for example, uses this scheme.
2395 QVector
<FullScreenType
> screenTypes(numScreens
);
2396 QVector
<QRect
> screenRects(numScreens
);
2397 for (int s
= 0; s
< numScreens
; ++s
)
2398 screenRects
[s
] = desktop
->screenGeometry(s
);
2399 FullScreenType full
= findFullScreenWindows(screenRects
, screenTypes
);
2400 if (full
== NoFullScreen
|| screenTypes
[mScreenNumber
] == NoFullScreen
)
2402 for (int s
= 0; s
< numScreens
; ++s
)
2404 if (screenTypes
[s
] == NoFullScreen
)
2407 // There is no full screen window on this screen
2412 // All screens contain a full screen window: use one without
2413 // an active full screen window.
2414 for (int s
= 0; s
< numScreens
; ++s
)
2416 if (screenTypes
[s
] == FullScreen
)
2425 // The screens are completely separate from each other.
2426 int inactiveScreen
= -1;
2427 FullScreenType full
= haveFullScreenWindow(mScreenNumber
);
2428 kDebug()<<"full="<<full
<<", screen="<<mScreenNumber
;
2429 if (full
== NoFullScreen
)
2430 return modal
; // KAlarm's screen doesn't contain a full screen window
2431 if (full
== FullScreen
)
2432 inactiveScreen
= mScreenNumber
;
2433 for (int s
= 0; s
< numScreens
; ++s
)
2435 if (s
!= mScreenNumber
)
2437 full
= haveFullScreenWindow(s
);
2438 if (full
== NoFullScreen
)
2440 // There is no full screen window on this screen
2444 if (full
== FullScreen
&& inactiveScreen
< 0)
2448 if (inactiveScreen
>= 0)
2450 // All screens contain a full screen window: use one without
2451 // an active full screen window.
2452 mScreenNumber
= inactiveScreen
;
2456 return false; // can't logically get here, since there can only be one active window...
2461 WId activeId
= KWindowSystem::activeWindow();
2462 KWindowInfo wi
= KWindowSystem::windowInfo(activeId
, NET::WMState
);
2463 if (wi
.valid() && wi
.hasState(NET::FullScreen
))
2464 return false; // the active window is full screen.
2470 /******************************************************************************
2471 * In a multi-screen setup (not a single virtual desktop), find whether the
2472 * specified screen has a full screen window on it.
2474 FullScreenType
haveFullScreenWindow(int screen
)
2476 FullScreenType type
= NoFullScreen
;
2477 Display
* display
= QX11Info::display();
2478 NETRootInfo
rootInfo(display
, NET::ClientList
| NET::ActiveWindow
, screen
);
2479 Window rootWindow
= rootInfo
.rootWindow();
2480 Window activeWindow
= rootInfo
.activeWindow();
2481 const Window
* windows
= rootInfo
.clientList();
2482 int windowCount
= rootInfo
.clientListCount();
2483 kDebug()<<"Screen"<<screen
<<": Window count="<<windowCount
<<", active="<<activeWindow
<<", geom="<<qApp
->desktop()->screenGeometry(screen
);
2486 for (int w
= 0; w
< windowCount
; ++w
)
2488 NETWinInfo
winInfo(display
, windows
[w
], rootWindow
, NET::WMState
|NET::WMGeometry
);
2489 winInfo
.kdeGeometry(frame
, geom
);
2490 QRect
fr(frame
.pos
.x
, frame
.pos
.y
, frame
.size
.width
, frame
.size
.height
);
2491 QRect
gm(geom
.pos
.x
, geom
.pos
.y
, geom
.size
.width
, geom
.size
.height
);
2492 if (winInfo
.state() & NET::FullScreen
)
2494 kDebug()<<"Found FULL SCREEN: "<<windows
[w
]<<", geom="<<gm
<<", frame="<<fr
;
2496 if (windows
[w
] == activeWindow
)
2497 return FullScreenActive
;
2499 //else { kDebug()<<"Found normal: "<<windows[w]<<", geom="<<gm<<", frame="<<fr; }
2504 /******************************************************************************
2505 * In a multi-screen setup (single virtual desktop, e.g. Xinerama), find which
2506 * screens have full screen windows on them.
2508 FullScreenType
findFullScreenWindows(const QVector
<QRect
>& screenRects
, QVector
<FullScreenType
>& screenTypes
)
2510 FullScreenType result
= NoFullScreen
;
2511 screenTypes
.fill(NoFullScreen
);
2512 Display
* display
= QX11Info::display();
2513 NETRootInfo
rootInfo(display
, NET::ClientList
| NET::ActiveWindow
, 0);
2514 Window rootWindow
= rootInfo
.rootWindow();
2515 Window activeWindow
= rootInfo
.activeWindow();
2516 const Window
* windows
= rootInfo
.clientList();
2517 int windowCount
= rootInfo
.clientListCount();
2518 kDebug()<<"Virtual desktops: Window count="<<windowCount
<<", active="<<activeWindow
<<", geom="<<qApp
->desktop()->screenGeometry(0);
2521 for (int w
= 0; w
< windowCount
; ++w
)
2523 NETWinInfo
winInfo(display
, windows
[w
], rootWindow
, NET::WMState
| NET::WMGeometry
);
2524 if (winInfo
.state() & NET::FullScreen
)
2526 // Found a full screen window - find which screen it's on
2527 bool active
= (windows
[w
] == activeWindow
);
2528 winInfo
.kdeGeometry(netframe
, netgeom
);
2529 QRect
winRect(netgeom
.pos
.x
, netgeom
.pos
.y
, netgeom
.size
.width
, netgeom
.size
.height
);
2530 kDebug()<<"Found FULL SCREEN: "<<windows
[w
]<<", geom="<<winRect
;
2531 for (int s
= 0, count
= screenRects
.count(); s
< count
; ++s
)
2533 if (screenRects
[s
].contains(winRect
))
2535 kDebug()<<"FULL SCREEN on screen"<<s
<<", active="<<active
;
2537 screenTypes
[s
] = result
= FullScreenActive
;
2540 if (screenTypes
[s
] == NoFullScreen
)
2541 screenTypes
[s
] = FullScreen
;
2542 if (result
== NoFullScreen
)
2543 result
= FullScreen
;