• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdepim API Reference
  • KDE Home
  • Contact Us
 

kalarm

  • sources
  • kde-4.12
  • kdepim
  • kalarm
messagewin.cpp
Go to the documentation of this file.
1 /*
2  * messagewin.cpp - displays an alarm message
3  * Program: kalarm
4  * Copyright © 2001-2013 by David Jarvie <djarvie@kde.org>
5  *
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.
10  *
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.
15  *
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.
19  */
20 
21 #include "kalarm.h"
22 #include "messagewin_p.moc"
23 #include "messagewin.moc"
24 
25 #include "alarmcalendar.h"
26 #include "autoqpointer.h"
27 #ifdef USE_AKONADI
28 #include "collectionmodel.h"
29 #endif
30 #include "deferdlg.h"
31 #include "desktop.h"
32 #include "editdlg.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"
41 
42 #include "kspeechinterface.h"
43 
44 #include <kstandarddirs.h>
45 #include <kaction.h>
46 #include <kstandardguiitem.h>
47 #include <kaboutdata.h>
48 #include <klocale.h>
49 #include <kconfig.h>
50 #include <kiconloader.h>
51 #include <kdialog.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>
65 #include <kdebug.h>
66 #include <ktoolinvocation.h>
67 #ifdef Q_WS_X11
68 #include <netwm.h>
69 #include <QX11Info>
70 #endif
71 
72 #include <QScrollBar>
73 #include <QtDBus/QtDBus>
74 #include <QFile>
75 #include <QFileInfo>
76 #include <QCheckBox>
77 #include <QLabel>
78 #include <QPalette>
79 #include <QTimer>
80 #include <QPixmap>
81 #include <QByteArray>
82 #include <QFrame>
83 #include <QGridLayout>
84 #include <QVBoxLayout>
85 #include <QHBoxLayout>
86 #include <QResizeEvent>
87 #include <QCloseEvent>
88 #include <QDesktopWidget>
89 #include <QMutexLocker>
90 
91 #include <stdlib.h>
92 #include <string.h>
93 
94 #ifdef USE_AKONADI
95 using namespace KCalCore;
96 #else
97 using namespace KCal;
98 #endif
99 using namespace KAlarmCal;
100 
101 #ifdef Q_WS_X11
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);
105 #endif
106 
107 #ifdef KMAIL_SUPPORTED
108 #include "kmailinterface.h"
109 static const QLatin1String KMAIL_DBUS_SERVICE("org.kde.kmail");
110 static const QLatin1String KMAIL_DBUS_PATH("/KMail");
111 #endif
112 
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
117 
118 // A text label widget which can be scrolled and copied with the mouse
119 class MessageText : public KTextEdit
120 {
121  public:
122  MessageText(QWidget* parent = 0)
123  : KTextEdit(parent),
124  mNewLine(false)
125  {
126  setReadOnly(true);
127  setFrameStyle(NoFrame);
128  setLineWrapMode(NoWrap);
129  }
130  int scrollBarHeight() const { return horizontalScrollBar()->height(); }
131  int scrollBarWidth() const { return verticalScrollBar()->width(); }
132  void setBackgroundColour(const QColor& c)
133  {
134  QPalette pal = viewport()->palette();
135  pal.setColor(viewport()->backgroundRole(), c);
136  viewport()->setPalette(pal);
137  }
138  virtual QSize sizeHint() const
139  {
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());
143  }
144  bool newLine() const { return mNewLine; }
145  void setNewLine(bool nl) { mNewLine = nl; }
146  private:
147  bool mNewLine;
148 };
149 
150 
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;
155 
156 // Error message bit masks
157 enum {
158  ErrMsg_Speak = 0x01,
159  ErrMsg_AudioFile = 0x02
160 };
161 
162 
163 QList<MessageWin*> MessageWin::mWindowList;
164 #ifdef USE_AKONADI
165 QMap<EventId, unsigned> MessageWin::mErrorMessages;
166 #else
167 QMap<QString, unsigned> MessageWin::mErrorMessages;
168 #endif
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;
174 
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
179 * displayed.
180 */
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()),
187 #ifdef USE_AKONADI
188  mEventItemId(event->itemId()),
189  mEventId(*event),
190 #else
191  mEventId(event->id()),
192 #endif
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()),
202 #else
203  mKMailSerialNumber(0),
204 #endif
205  mCommandError(event->commandError()),
206  mRestoreHeight(0),
207  mAudioRepeatPause(event->repeatSoundPause()),
208  mConfirmAck(event->confirmAck()),
209  mNoDefer(true),
210  mInvalid(false),
211  mEvent(*event),
212  mOriginalEvent(*event),
213 #ifdef USE_AKONADI
214  mCollection(AlarmCalendar::resources()->collectionForEvent(mEventItemId)),
215 #else
216  mResource(AlarmCalendar::resources()->resourceForEvent(mEventId)),
217 #endif
218  mTimeLabel(0),
219  mRemainingText(0),
220  mEditButton(0),
221  mDeferButton(0),
222  mSilenceButton(0),
223  mKMailButton(0),
224  mCommandText(0),
225  mDontShowAgainCheck(0),
226  mEditDlg(0),
227  mDeferDlg(0),
228  mAlwaysHide(flags & ALWAYS_HIDE),
229  mErrorWindow(false),
230  mInitialised(false),
231  mNoPostAction(alarm.type() & KAAlarm::REMINDER_ALARM),
232  mRecreating(false),
233  mBeep(event->beep()),
234  mSpeak(event->speak()),
235  mRescheduleEvent(!(flags & NO_RESCHEDULE)),
236  mShown(false),
237  mPositioning(false),
238  mNoCloseConfirm(false),
239  mDisableDeferral(false)
240 {
241  kDebug() << "event";
242  setAttribute(static_cast<Qt::WidgetAttribute>(WidgetFlags));
243  setWindowModality(Qt::WindowModal);
244  setObjectName(QLatin1String("MessageWin")); // used by LikeBack
245  if (alarm.type() & KAAlarm::REMINDER_ALARM)
246  {
247  if (event->reminderMinutes() < 0)
248  {
249  event->previousOccurrence(alarm.dateTime(false).effectiveKDateTime(), mDateTime, false);
250  if (!mDateTime.isValid() && event->repeatAtLogin())
251  mDateTime = alarm.dateTime().addSecs(event->reminderMinutes() * 60);
252  }
253  else
254  mDateTime = event->mainDateTime(true);
255  }
256  else
257  mDateTime = alarm.dateTime(true);
258  if (!(flags & (NO_INIT_VIEW | ALWAYS_HIDE)))
259  {
260 #ifdef USE_AKONADI
261  bool readonly = AlarmCalendar::resources()->eventReadOnly(mEventItemId);
262 #else
263  bool readonly = AlarmCalendar::resources()->eventReadOnly(mEventId);
264 #endif
265  mShowEdit = !mEventId.isEmpty() && !readonly;
266  mNoDefer = readonly || (flags & NO_DEFER) || alarm.repeatAtLogin();
267  initView();
268  }
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);
275  if (mAlwaysHide)
276  {
277  hide();
278  displayComplete(); // play audio, etc.
279  }
280 }
281 
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'.
286 */
287 void MessageWin::showError(const KAEvent& event, const DateTime& alarmDateTime,
288  const QStringList& errmsgs, const QString& dontShowAgain)
289 {
290 #ifdef USE_AKONADI
291  if (!dontShowAgain.isEmpty()
292  && KAlarm::dontShowErrors(EventId(event), dontShowAgain))
293 #else
294  if (!dontShowAgain.isEmpty()
295  && KAlarm::dontShowErrors(event.id(), dontShowAgain))
296 #endif
297  return;
298 
299  // Don't pile up duplicate error messages for the same alarm
300  for (int i = 0, end = mWindowList.count(); i < end; ++i)
301  {
302  MessageWin* w = mWindowList[i];
303 #ifdef USE_AKONADI
304  if (w->mErrorWindow && w->mEventId == EventId(event)
305  && w->mErrorMsgs == errmsgs && w->mDontShowAgain == dontShowAgain)
306 #else
307  if (w->mErrorWindow && w->mEventId == event.id()
308  && w->mErrorMsgs == errmsgs && w->mDontShowAgain == dontShowAgain)
309 #endif
310  return;
311  }
312 
313  (new MessageWin(&event, alarmDateTime, errmsgs, dontShowAgain))->show();
314 }
315 
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'.
320 */
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),
326 #ifdef USE_AKONADI
327  mEventItemId(event->itemId()),
328  mEventId(*event),
329 #else
330  mEventId(event->id()),
331 #endif
332  mAlarmType(KAAlarm::MAIN_ALARM),
333  mAction(event->actionSubType()),
334  mKMailSerialNumber(0),
335  mCommandError(KAEvent::CMD_NO_ERROR),
336  mErrorMsgs(errmsgs),
337  mDontShowAgain(dontShowAgain),
338  mRestoreHeight(0),
339  mConfirmAck(false),
340  mShowEdit(false),
341  mNoDefer(true),
342  mInvalid(false),
343  mEvent(*event),
344  mOriginalEvent(*event),
345 #ifndef USE_AKONADI
346  mResource(0),
347 #endif
348  mTimeLabel(0),
349  mRemainingText(0),
350  mEditButton(0),
351  mDeferButton(0),
352  mSilenceButton(0),
353  mKMailButton(0),
354  mCommandText(0),
355  mDontShowAgainCheck(0),
356  mEditDlg(0),
357  mDeferDlg(0),
358  mAlwaysHide(false),
359  mErrorWindow(true),
360  mInitialised(false),
361  mNoPostAction(true),
362  mRecreating(false),
363  mRescheduleEvent(false),
364  mShown(false),
365  mPositioning(false),
366  mNoCloseConfirm(false),
367  mDisableDeferral(false)
368 {
369  kDebug() << "errmsg";
370  setAttribute(static_cast<Qt::WidgetAttribute>(WidgetFlags));
371  setWindowModality(Qt::WindowModal);
372  setObjectName(QLatin1String("ErrorWin")); // used by LikeBack
373  getWorkAreaAndModal();
374  initView();
375  mWindowList.append(this);
376 }
377 
378 /******************************************************************************
379 * Construct the message window for restoration by session management.
380 * The window is initialised by readProperties().
381 */
382 MessageWin::MessageWin()
383  : MainWindowBase(0, WFLAGS),
384  mTimeLabel(0),
385  mRemainingText(0),
386  mEditButton(0),
387  mDeferButton(0),
388  mSilenceButton(0),
389  mKMailButton(0),
390  mCommandText(0),
391  mDontShowAgainCheck(0),
392  mEditDlg(0),
393  mDeferDlg(0),
394  mAlwaysHide(false),
395  mErrorWindow(false),
396  mInitialised(false),
397  mRecreating(false),
398  mRescheduleEvent(false),
399  mShown(false),
400  mPositioning(false),
401  mNoCloseConfirm(false),
402  mDisableDeferral(false)
403 {
404  kDebug() << "restore";
405  setAttribute(WidgetFlags);
406  setWindowModality(Qt::WindowModal);
407  setObjectName(QLatin1String("RestoredMsgWin")); // used by LikeBack
408  getWorkAreaAndModal();
409  mWindowList.append(this);
410 }
411 
412 /******************************************************************************
413 * Destructor. Perform any post-alarm actions before tidying up.
414 */
415 MessageWin::~MessageWin()
416 {
417  kDebug() << mEventId;
418  if (AudioThread::mAudioOwner == this && !mAudioThread.isNull())
419  mAudioThread->quit();
420  mErrorMessages.remove(mEventId);
421  mWindowList.removeAll(this);
422  if (!mRecreating)
423  {
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
428  }
429 }
430 
431 /******************************************************************************
432 * Construct the message window.
433 */
434 void MessageWin::initView()
435 {
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());
444 
445  QPalette labelPalette = palette();
446  labelPalette.setColor(backgroundRole(), labelPalette.color(QPalette::Window));
447 
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)."));
457 
458  if (mDateTime.isValid())
459  {
460  // Reminder
461  if (reminder)
462  {
463  QString s = i18nc("@info", "Reminder");
464  QRegExp re(QLatin1String("^(<[^>]+>)*"));
465  re.indexIn(s);
466  s.insert(re.matchedLength(), mTimeLabel->text() + QLatin1String("<br/>"));
467  mTimeLabel->setText(s);
468  mTimeLabel->setAlignment(Qt::AlignHCenter);
469  }
470  }
471  else
472  mTimeLabel->hide();
473 
474  if (!mErrorWindow)
475  {
476  // It's a normal alarm message window
477  switch (mAction)
478  {
479  case KAEvent::FILE:
480  {
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);
489 
490  // Display contents of file
491  bool opened = false;
492  bool dir = false;
493  QString tmpFile;
494  KUrl url(mMessage);
495  if (KIO::NetAccess::download(url, tmpFile, MainWindow::mainMainWindow()))
496  {
497  QFile qfile(tmpFile);
498  QFileInfo info(qfile);
499  if (!(dir = info.isDir()))
500  {
501  opened = true;
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(QLatin1String("application/octet-stream")))
512  mime = KMimeType::findByFileContent(tmpFile);
513  switch (KAlarm::fileType(mime))
514  {
515  case KAlarm::Image:
516  view->setHtml(QLatin1String("<img source=\"") + tmpFile + QLatin1String("\">"));
517  break;
518  case KAlarm::TextFormatted:
519  view->QTextBrowser::setSource(tmpFile); //krazy:exclude=qclasses
520  break;
521  default:
522  {
523  // Assume a plain text file
524  if (qfile.open(QIODevice::ReadOnly))
525  {
526  QTextStream str(&qfile);
527 
528  view->setPlainText(str.readAll());
529  qfile.close();
530  }
531  break;
532  }
533  }
534  view->setMinimumSize(view->sizeHint());
535  topLayout->addWidget(view);
536 
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"));
544  }
545  KIO::NetAccess::removeTempFile(tmpFile);
546  }
547  if (!opened)
548  {
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");
552  }
553  break;
554  }
555  case KAEvent::MESSAGE:
556  {
557  // Message label
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();
567  int h = s.height();
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);
579  else
580  {
581  QHBoxLayout* layout = new QHBoxLayout();
582  layout->addSpacing(hspace);
583  layout->addWidget(text, 1, Qt::AlignHCenter);
584  layout->addSpacing(hspace);
585  topLayout->addLayout(layout);
586  }
587  if (!reminder)
588  topLayout->addStretch();
589  break;
590  }
591  case KAEvent::COMMAND:
592  {
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*)));
600  break;
601  }
602  case KAEvent::EMAIL:
603  default:
604  break;
605  }
606 
607  if (reminder && mEvent.reminderMinutes() > 0)
608  {
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)
616  {
617  setRemainingTextDay();
618  MidnightTimer::connect(this, SLOT(setRemainingTextDay())); // update every day
619  }
620  else
621  {
622  setRemainingTextMinute();
623  MinuteTimer::connect(this, SLOT(setRemainingTextMinute())); // update every minute
624  }
625  topLayout->addWidget(mRemainingText, 0, Qt::AlignHCenter);
626  topLayout->addSpacing(KDialog::spacingHint());
627  topLayout->addStretch();
628  }
629  }
630  else
631  {
632  // It's an error message
633  switch (mAction)
634  {
635  case KAEvent::EMAIL:
636  {
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());
645 
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(QLatin1String("\n")), frame);
650  label->setFixedSize(label->sizeHint());
651  grid->addWidget(label, 0, 1, Qt::AlignLeft);
652 
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);
659  break;
660  }
661  case KAEvent::COMMAND:
662  case KAEvent::FILE:
663  case KAEvent::MESSAGE:
664  default:
665  // Just display the error message strings
666  break;
667  }
668  }
669 
670  if (!mErrorMsgs.count())
671  {
672  topWidget->setAutoFillBackground(true);
673  QPalette palette = topWidget->palette();
674  palette.setColor(topWidget->backgroundRole(), mBgColour);
675  topWidget->setPalette(palette);
676  }
677  else
678  {
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(QLatin1String("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)
691  {
692  label = new QLabel(*it, topWidget);
693  label->setFixedSize(label->sizeHint());
694  vlayout->addWidget(label, 0, Qt::AlignLeft);
695  }
696  layout->addStretch();
697  if (!mDontShowAgain.isEmpty())
698  {
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);
702  }
703  }
704 
705  QGridLayout* grid = new QGridLayout();
706  grid->setColumnStretch(0, 1); // keep the buttons right-adjusted in the window
707  topLayout->addLayout(grid);
708  int gridIndex = 1;
709 
710  // Close button
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"));
719 
720  if (mShowEdit)
721  {
722  // Edit button
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."));
729  }
730 
731  // Defer button
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>"));
739 
740  if (mNoDefer)
741  mDeferButton->hide();
742  else
743  setDeferralLimit(mEvent); // ensure that button is disabled when alarm can't be deferred any more
744 
745  if (!mAudioFile.isEmpty() && (mVolume || mFadeVolume > 0))
746  {
747  // Silence button to stop sound repetition
748  QPixmap pixmap = MainBarIcon(QLatin1String("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);
756  }
757 
758  KIconLoader iconLoader;
759  if (mKMailSerialNumber)
760  {
761  // KMail button
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>"));
769  }
770 
771  // KAlarm button
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>"));
779 
780  int butsize = mKAlarmButton->sizeHint().height();
781  if (mSilenceButton)
782  butsize = qMax(butsize, mSilenceButton->sizeHint().height());
783  if (mKMailButton)
784  butsize = qMax(butsize, mKMailButton->sizeHint().height());
785  mKAlarmButton->setFixedSize(butsize, butsize);
786  if (mSilenceButton)
787  mSilenceButton->setFixedSize(butsize, butsize);
788  if (mKMailButton)
789  mKMailButton->setFixedSize(butsize, butsize);
790 
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);
796  if (mEditButton)
797  mEditButton->setEnabled(false);
798  if (mKMailButton)
799  mKMailButton->setEnabled(false);
800  mKAlarmButton->setEnabled(false);
801 
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;
806  WId winid = winId();
807  KWindowSystem::setState(winid, wstate);
808  KWindowSystem::setOnAllDesktops(winid, true);
809 
810  mInitialised = true; // the window's widgets have been created
811 }
812 
813 /******************************************************************************
814 * Return the number of message windows, optionally excluding always-hidden ones.
815 */
816 int MessageWin::instanceCount(bool excludeAlwaysHidden)
817 {
818  int count = mWindowList.count();
819  if (excludeAlwaysHidden)
820  {
821  foreach (MessageWin* win, mWindowList)
822  {
823  if (win->mAlwaysHide)
824  --count;
825  }
826  }
827  return count;
828 }
829 
830 bool MessageWin::hasDefer() const
831 {
832  return mDeferButton && mDeferButton->isVisible();
833 }
834 
835 /******************************************************************************
836 * Show the Defer button when it was previously hidden.
837 */
838 void MessageWin::showDefer()
839 {
840  if (mDeferButton)
841  {
842  mNoDefer = false;
843  mDeferButton->show();
844  setDeferralLimit(mEvent); // ensure that button is disabled when alarm can't be deferred any more
845  resize(sizeHint());
846  }
847 }
848 
849 /******************************************************************************
850 * Convert a reminder window into a normal alarm window.
851 */
852 void MessageWin::cancelReminder(const KAEvent& event, const KAAlarm& alarm)
853 {
854  if (!mInitialised)
855  return;
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());
863  if (mRemainingText)
864  mRemainingText->hide();
865  MidnightTimer::disconnect(this, SLOT(setRemainingTextDay()));
866  MinuteTimer::disconnect(this, SLOT(setRemainingTextMinute()));
867  setMinimumHeight(0);
868  centralWidget()->layout()->activate();
869  setMinimumHeight(sizeHint().height());
870  resize(sizeHint());
871 }
872 
873 /******************************************************************************
874 * Show the alarm's trigger time.
875 * This is assumed to have previously been hidden.
876 */
877 void MessageWin::showDateTime(const KAEvent& event, const KAAlarm& alarm)
878 {
879  if (!mTimeLabel)
880  return;
881  mDateTime = (alarm.type() & KAAlarm::REMINDER_ALARM) ? event.mainDateTime(true) : alarm.dateTime(true);
882  if (mDateTime.isValid())
883  {
884  mTimeLabel->setText(dateTimeToDisplay());
885  mTimeLabel->show();
886  }
887 }
888 
889 /******************************************************************************
890 * Get the trigger time to display.
891 */
892 QString MessageWin::dateTimeToDisplay()
893 {
894  QString tm;
895  if (mDateTime.isValid())
896  {
897  if (mDateTime.isDateOnly())
898  tm = KGlobal::locale()->formatDate(mDateTime.date(), KLocale::ShortDate);
899  else
900  {
901  bool showZone = false;
902  if (mDateTime.timeType() == KDateTime::UTC
903  || (mDateTime.timeType() == KDateTime::TimeZone && !mDateTime.isLocalZone()))
904  {
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);
913  }
914  tm = KGlobal::locale()->formatDateTime(mDateTime.kDateTime(), KLocale::ShortDate, KLocale::DateTimeFormatOptions(showZone ? KLocale::TimeZone : 0));
915  }
916  }
917  return tm;
918 }
919 
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).
923 */
924 void MessageWin::setRemainingTextDay()
925 {
926  QString text;
927  int days = KDateTime::currentLocalDate().daysTo(mDateTime.date());
928  if (days <= 0 && !mDateTime.isDateOnly())
929  {
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
934  }
935  else
936  {
937  if (days <= 0)
938  text = i18nc("@info", "Today");
939  else if (days % 7)
940  text = i18ncp("@info", "Tomorrow", "in %1 days' time", days);
941  else
942  text = i18ncp("@info", "in 1 week's time", "in %1 weeks' time", days/7);
943  }
944  mRemainingText->setText(text);
945 }
946 
947 /******************************************************************************
948 * Set the remaining time text in a reminder window.
949 * Called on every minute boundary.
950 */
951 void MessageWin::setRemainingTextMinute()
952 {
953  QString text;
954  int mins = (KDateTime::currentUtcDateTime().secsTo(mDateTime.effectiveKDateTime()) + 59) / 60;
955  if (mins < 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);
959  else
960  {
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);
963  }
964  mRemainingText->setText(text);
965 }
966 
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.
970 */
971 void MessageWin::readProcessOutput(ShellProcess* proc)
972 {
973  QByteArray data = proc->readAll();
974  if (!data.isEmpty())
975  {
976  // Strip any trailing newline, to avoid showing trailing blank line
977  // in message window.
978  if (mCommandText->newLine())
979  mCommandText->append(QLatin1String("\n"));
980  int nl = data.endsWith('\n') ? 1 : 0;
981  mCommandText->setNewLine(nl);
982  mCommandText->insertPlainText(QString::fromLocal8Bit(data.data(), data.length() - nl));
983  resize(sizeHint());
984  }
985 }
986 
987 /******************************************************************************
988 * Save settings to the session managed config file, for restoration
989 * when the program is restored.
990 */
991 void MessageWin::saveProperties(KConfigGroup& config)
992 {
993  if (mShown && !mErrorWindow && !mAlwaysHide)
994  {
995 #ifdef USE_AKONADI
996  config.writeEntry("EventID", mEventId.eventId());
997  config.writeEntry("EventItemID", mEventItemId);
998 #else
999  config.writeEntry("EventID", mEventId);
1000 #endif
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())
1011  {
1012 //TODO: Write KDateTime when it becomes possible
1013  config.writeEntry("Time", mDateTime.effectiveDateTime());
1014  config.writeEntry("DateOnly", mDateTime.isDateOnly());
1015  QString zone;
1016  if (mDateTime.isUtc())
1017  zone = QLatin1String("UTC");
1018  else
1019  {
1020  KTimeZone tz = mDateTime.timeZone();
1021  if (tz.isValid())
1022  zone = tz.name();
1023  }
1024  config.writeEntry("TimeZone", zone);
1025  }
1026  if (mCloseTime.isValid())
1027  config.writeEntry("Expiry", mCloseTime);
1028  if (mAudioRepeatPause >= 0 && mSilenceButton && mSilenceButton->isEnabled())
1029  {
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);
1034  }
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);
1043  }
1044  else
1045  config.writeEntry("Invalid", true);
1046 }
1047 
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().
1052 */
1053 void MessageWin::readProperties(const KConfigGroup& config)
1054 {
1055  mInvalid = config.readEntry("Invalid", false);
1056 #ifdef USE_AKONADI
1057  mEventItemId = config.readEntry("EventItemID", Akonadi::Item::Id(-1));
1058  mCollection = AkonadiModel::instance()->collectionForItem(mEventItemId);
1059  mEventId = EventId(mCollection.id(), config.readEntry("EventID"));
1060 #else
1061  mEventId = config.readEntry("EventID");
1062 #endif
1063  mAlarmType = static_cast<KAAlarm::Type>(config.readEntry("AlarmType", 0));
1064  if (mAlarmType == KAAlarm::INVALID_ALARM)
1065  {
1066  mInvalid = true;
1067  kError() << "Invalid alarm: id=" << mEventId;
1068  }
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");
1078  if (zone.isEmpty())
1079  mDateTime = KDateTime(dt, KDateTime::ClockTime);
1080  else if (zone == QLatin1String("UTC"))
1081  {
1082  dt.setTimeSpec(Qt::UTC);
1083  mDateTime = KDateTime(dt, KDateTime::UTC);
1084  }
1085  else
1086  {
1087  KTimeZone tz = KSystemTimeZones::zone(zone);
1088  mDateTime = KDateTime(dt, (tz.isValid() ? tz : KSystemTimeZones::local()));
1089  }
1090  bool dateOnly = config.readEntry("DateOnly", false);
1091  if (dateOnly)
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;
1097  mFadeVolume = -1;
1098  mFadeSeconds = 0;
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());
1110  mShowEdit = false;
1111 #ifdef USE_AKONADI
1112  mCollection = Akonadi::Collection();
1113 #else
1114  mResource = 0;
1115 #endif
1116  kDebug() << mEventId;
1117  if (mAlarmType != KAAlarm::INVALID_ALARM)
1118  {
1119  // Recreate the event from the calendar file (if possible)
1120  if (!mEventId.isEmpty())
1121  {
1122  KAEvent* event = AlarmCalendar::resources()->event(mEventId);
1123  if (event)
1124  {
1125  mEvent = *event;
1126 #ifndef USE_AKONADI
1127  mResource = AlarmCalendar::resources()->resourceForEvent(mEventId);
1128 #endif
1129  mShowEdit = true;
1130  }
1131  else
1132  {
1133  // It's not in the active calendar, so try the displaying or archive calendars
1134 #ifdef USE_AKONADI
1135  retrieveEvent(mEvent, mCollection, mShowEdit, mNoDefer);
1136 #else
1137  retrieveEvent(mEvent, mResource, mShowEdit, mNoDefer);
1138 #endif
1139  mNoDefer = !mNoDefer;
1140  }
1141  }
1142  initView();
1143  }
1144 }
1145 
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.
1151 */
1152 void MessageWin::redisplayAlarms()
1153 {
1154  AlarmCalendar* cal = AlarmCalendar::displayCalendar();
1155  if (cal->isOpen())
1156  {
1157  KAEvent event;
1158 #ifdef USE_AKONADI
1159  Akonadi::Collection collection;
1160 #else
1161  AlarmResource* resource;
1162 #endif
1163  Event::List events = cal->kcalEvents();
1164  for (int i = 0, end = events.count(); i < end; ++i)
1165  {
1166  bool showDefer, showEdit;
1167 #ifdef USE_AKONADI
1168  reinstateFromDisplaying(events[i], event, collection, showEdit, showDefer);
1169  if (!findEvent(EventId(event)))
1170 #else
1171  reinstateFromDisplaying(events[i], event, resource, showEdit, showDefer);
1172  if (!findEvent(event.id()))
1173 #endif
1174  {
1175  // This event should be displayed, but currently isn't being
1176  KAAlarm alarm = event.convertDisplayingAlarm();
1177  if (alarm.type() == KAAlarm::INVALID_ALARM)
1178  {
1179  kError() << "Invalid alarm: id=" << event.id();
1180  continue;
1181  }
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);
1186 #ifdef USE_AKONADI
1187  win->mCollection = collection;
1188  bool rw = CollectionControlModel::isWritableEnabled(collection, event.category()) > 0;
1189 #else
1190  win->mResource = resource;
1191  bool rw = resource && resource->writable();
1192 #endif
1193  win->mShowEdit = rw ? showEdit : false;
1194  win->mNoDefer = (rw && !login) ? !showDefer : true;
1195  win->initView();
1196  win->show();
1197  }
1198  }
1199  }
1200 }
1201 
1202 /******************************************************************************
1203 * Retrieves the event with the current ID from the displaying calendar file,
1204 * or if not found there, from the archive calendar.
1205 */
1206 #ifdef USE_AKONADI
1207 bool MessageWin::retrieveEvent(KAEvent& event, Akonadi::Collection& resource, bool& showEdit, bool& showDefer)
1208 #else
1209 bool MessageWin::retrieveEvent(KAEvent& event, AlarmResource*& resource, bool& showEdit, bool& showDefer)
1210 #endif
1211 {
1212 #ifdef USE_AKONADI
1213  Event::Ptr kcalEvent = AlarmCalendar::displayCalendar()->kcalEvent(CalEvent::uid(mEventId.eventId(), CalEvent::DISPLAYING));
1214 #else
1215  const Event* kcalEvent = AlarmCalendar::displayCalendar()->kcalEvent(CalEvent::uid(mEventId, CalEvent::DISPLAYING));
1216 #endif
1217  if (!reinstateFromDisplaying(kcalEvent, event, resource, showEdit, showDefer))
1218  {
1219  // The event isn't in the displaying calendar.
1220  // Try to retrieve it from the archive calendar.
1221 #ifdef USE_AKONADI
1222  KAEvent* ev = 0;
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)));
1226 #else
1227  KAEvent* ev = AlarmCalendar::resources()->event(CalEvent::uid(mEventId, CalEvent::ARCHIVED));
1228 #endif
1229  if (!ev)
1230  return false;
1231  event = *ev;
1232  event.setArchive(); // ensure that it gets re-archived if it's saved
1233  event.setCategory(CalEvent::ACTIVE);
1234 #ifdef USE_AKONADI
1235  if (mEventId.eventId() != event.id())
1236  kError() << "Wrong event ID";
1237  event.setEventId(mEventId.eventId());
1238  resource = Akonadi::Collection();
1239 #else
1240  if (mEventId != event.id())
1241  kError() << "Wrong event ID";
1242  event.setEventId(mEventId);
1243  resource = 0;
1244 #endif
1245  showEdit = true;
1246  showDefer = true;
1247  kDebug() << event.id() << ": success";
1248  }
1249  return true;
1250 }
1251 
1252 /******************************************************************************
1253 * Retrieves the displayed event from the calendar file, or if not found there,
1254 * from the displaying calendar.
1255 */
1256 #ifdef USE_AKONADI
1257 bool MessageWin::reinstateFromDisplaying(const Event::Ptr& kcalEvent, KAEvent& event, Akonadi::Collection& collection, bool& showEdit, bool& showDefer)
1258 #else
1259 bool MessageWin::reinstateFromDisplaying(const Event* kcalEvent, KAEvent& event, AlarmResource*& resource, bool& showEdit, bool& showDefer)
1260 #endif
1261 {
1262  if (!kcalEvent)
1263  return false;
1264 #ifdef USE_AKONADI
1265  Akonadi::Collection::Id collectionId;
1266  event.reinstateFromDisplaying(kcalEvent, collectionId, showEdit, showDefer);
1267  collection = AkonadiModel::instance()->collectionById(collectionId);
1268 #else
1269  QString resourceID;
1270  event.reinstateFromDisplaying(kcalEvent, resourceID, showEdit, showDefer);
1271  resource = AlarmResources::instance()->resourceWithId(resourceID);
1272  if (resource && !resource->isOpen())
1273  resource = 0;
1274 #endif
1275  kDebug() << event.id() << ": success";
1276  return true;
1277 }
1278 
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.
1283 */
1284 void MessageWin::alarmShowing(KAEvent& event)
1285 {
1286  kDebug() << event.id() << "," << KAAlarm::debugType(mAlarmType);
1287 #ifndef USE_AKONADI
1288  const KCal::Event* kcalEvent = AlarmCalendar::resources()->kcalEvent(event.id());
1289  if (!kcalEvent)
1290  {
1291  kError() << "Event ID not found:" << event.id();
1292  return;
1293  }
1294 #endif
1295  KAAlarm alarm = event.alarm(mAlarmType);
1296  if (!alarm.isValid())
1297  {
1298  kError() << "Alarm type not found:" << event.id() << ":" << mAlarmType;
1299  return;
1300  }
1301  if (!mAlwaysHide)
1302  {
1303  // Copy the alarm to the displaying calendar in case of a crash, etc.
1304 #ifdef USE_AKONADI
1305  KAEvent dispEvent;
1306  Akonadi::Collection collection = AkonadiModel::instance()->collectionForItem(event.itemId());
1307  dispEvent.setDisplaying(event, mAlarmType, collection.id(),
1308  mDateTime.effectiveKDateTime(), mShowEdit, !mNoDefer);
1309 #else
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);
1314 #endif
1315  AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen();
1316  if (cal)
1317  {
1318 #ifdef USE_AKONADI
1319  cal->deleteDisplayEvent(dispEvent.id()); // in case it already exists
1320  cal->addEvent(dispEvent);
1321 #else
1322  cal->deleteEvent(dispEvent->id()); // in case it already exists
1323  if (!cal->addEvent(dispEvent))
1324  delete dispEvent;
1325 #endif
1326  cal->save();
1327  }
1328 #ifndef USE_AKONADI
1329  else
1330  delete dispEvent;
1331 #endif
1332  }
1333  theApp()->rescheduleAlarm(event, alarm);
1334 }
1335 
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.
1340 */
1341 bool MessageWin::spread(bool scatter)
1342 {
1343  if (instanceCount(true) <= 1) // ignore always-hidden windows
1344  return false;
1345 
1346  QRect desk = KAlarm::desktopWorkArea(); // get the usable area of the desktop
1347  if (scatter == isSpread(desk.topLeft()))
1348  return scatter;
1349 
1350  if (scatter)
1351  {
1352  // Usually there won't be many windows, so a crude
1353  // scattering algorithm should suffice.
1354  int x = desk.left();
1355  int y = desk.top();
1356  int ynext = y;
1357  for (int errmsgs = 0; errmsgs < 2; ++errmsgs)
1358  {
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)
1362  {
1363  MessageWin* w = mWindowList[i];
1364  if ((!errmsgs && w->mErrorWindow)
1365  || (errmsgs && !w->mErrorWindow))
1366  continue;
1367  QSize sz = w->frameGeometry().size();
1368  if (x + sz.width() > desk.right())
1369  {
1370  x = desk.left();
1371  y = ynext;
1372  }
1373  int ytmp = y;
1374  if (y + sz.height() > desk.bottom())
1375  {
1376  ytmp = desk.bottom() - sz.height();
1377  if (ytmp < desk.top())
1378  ytmp = desk.top();
1379  }
1380  w->move(x, ytmp);
1381  x += sz.width();
1382  if (ytmp + sz.height() > ynext)
1383  ynext = ytmp + sz.height();
1384  }
1385  }
1386  }
1387  else
1388  {
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());
1392  }
1393  return scatter;
1394 }
1395 
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.
1399 */
1400 bool MessageWin::isSpread(const QPoint& topLeft)
1401 {
1402  for (int i = 0, end = mWindowList.count(); i < end; ++i)
1403  {
1404  if (mWindowList[i]->pos() != topLeft)
1405  return true;
1406  }
1407  return false;
1408 }
1409 
1410 /******************************************************************************
1411 * Returns the existing message window (if any) which is displaying the event
1412 * with the specified ID.
1413 */
1414 #ifdef USE_AKONADI
1415 MessageWin* MessageWin::findEvent(const EventId& eventId)
1416 #else
1417 MessageWin* MessageWin::findEvent(const QString& eventId)
1418 #endif
1419 {
1420  if (!eventId.isEmpty())
1421  {
1422  for (int i = 0, end = mWindowList.count(); i < end; ++i)
1423  {
1424  MessageWin* w = mWindowList[i];
1425  if (w->mEventId == eventId && !w->mErrorWindow)
1426  return w;
1427  }
1428  }
1429  return 0;
1430 }
1431 
1432 /******************************************************************************
1433 * Beep and play the audio file, as appropriate.
1434 */
1435 void MessageWin::playAudio()
1436 {
1437  if (mBeep)
1438  {
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
1442  }
1443  if (!mAudioFile.isEmpty())
1444  {
1445  if (!mVolume && mFadeVolume <= 0)
1446  return; // ensure zero volume doesn't play anything
1447  startAudio(); // play the audio file
1448  }
1449  else if (mSpeak)
1450  {
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()));
1454  }
1455 }
1456 
1457 /******************************************************************************
1458 * Speak the message.
1459 * Called asynchronously to avoid delaying the display of the message.
1460 */
1461 void MessageWin::slotSpeak()
1462 {
1463  QString error;
1464  OrgKdeKSpeechInterface* kspeech = theApp()->kspeechInterface(error);
1465  if (!kspeech)
1466  {
1467  if (!haveErrorMessage(ErrMsg_Speak))
1468  {
1469  KAMessageBox::detailedError(MainWindow::mainMainWindow(), i18nc("@info", "Unable to speak message"), error);
1470  clearErrorMessage(ErrMsg_Speak);
1471  }
1472  return;
1473  }
1474  if (!kspeech->say(mMessage, 0))
1475  {
1476  kDebug() << "SayMessage() D-Bus error";
1477  if (!haveErrorMessage(ErrMsg_Speak))
1478  {
1479  KAMessageBox::detailedError(MainWindow::mainMainWindow(), i18nc("@info", "Unable to speak message"), i18nc("@info", "D-Bus call say() failed"));
1480  clearErrorMessage(ErrMsg_Speak);
1481  }
1482  }
1483 }
1484 
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.
1490 */
1491 void MessageWin::startAudio()
1492 {
1493  if (mAudioThread)
1494  {
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()));
1498  }
1499  else
1500  {
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()));
1505  if (mSilenceButton)
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();
1511  }
1512 }
1513 
1514 /******************************************************************************
1515 * Return whether audio playback is currently active.
1516 */
1517 bool MessageWin::isAudioPlaying()
1518 {
1519  return mAudioThread;
1520 }
1521 
1522 /******************************************************************************
1523 * Stop audio playback.
1524 */
1525 void MessageWin::stopAudio(bool wait)
1526 {
1527  kDebug();
1528  if (mAudioThread)
1529  mAudioThread->stop(wait);
1530 }
1531 
1532 /******************************************************************************
1533 * Called when another window's audio thread is being destructed.
1534 * Wait until the destructor has finished.
1535 */
1536 void MessageWin::audioTerminating()
1537 {
1538  QTimer::singleShot(0, this, SLOT(startAudio()));
1539 }
1540 
1541 /******************************************************************************
1542 * Called when the audio file is ready to start playing.
1543 */
1544 void MessageWin::playReady()
1545 {
1546  if (mSilenceButton)
1547  mSilenceButton->setEnabled(true);
1548 }
1549 
1550 /******************************************************************************
1551 * Called when the audio file thread finishes.
1552 */
1553 void MessageWin::playFinished()
1554 {
1555  if (mSilenceButton)
1556  mSilenceButton->setEnabled(false);
1557  if (mAudioThread) // mAudioThread can actually be null here!
1558  {
1559  QString errmsg = mAudioThread->error();
1560  if (!errmsg.isEmpty() && !haveErrorMessage(ErrMsg_AudioFile))
1561  {
1562  KAMessageBox::error(this, errmsg);
1563  clearErrorMessage(ErrMsg_AudioFile);
1564  }
1565  }
1566  delete mAudioThread.data();
1567  if (mAlwaysHide)
1568  close();
1569 }
1570 
1571 /******************************************************************************
1572 * Constructor for audio thread.
1573 */
1574 AudioThread::AudioThread(MessageWin* parent, const QString& audioFile, float volume, float fadeVolume, int fadeSeconds, int repeatPause)
1575  : QThread(parent),
1576  mFile(audioFile),
1577  mVolume(volume),
1578  mFadeVolume(fadeVolume),
1579  mFadeSeconds(fadeSeconds),
1580  mRepeatPause(repeatPause),
1581  mAudioObject(0)
1582 {
1583  if (mAudioOwner)
1584  kError() << "mAudioOwner already set";
1585  mAudioOwner = parent;
1586 }
1587 
1588 /******************************************************************************
1589 * Destructor for audio thread. Waits for thread completion and tidies up.
1590 * Note that this destructor is executed in the parent thread.
1591 */
1592 AudioThread::~AudioThread()
1593 {
1594  kDebug();
1595  stop(true); // stop playing and tidy up (timeout 3 seconds)
1596  delete mAudioObject;
1597  mAudioObject = 0;
1598  if (mAudioOwner == parent())
1599  mAudioOwner = 0;
1600  // Notify after deleting mAudioThread, so that isAudioPlaying() will
1601  // return the correct value.
1602  QTimer::singleShot(0, theApp(), SLOT(notifyAudioStopped()));
1603 }
1604 
1605 /******************************************************************************
1606 * Quits the thread and waits for thread completion and tidies up.
1607 */
1608 void AudioThread::stop(bool waiT)
1609 {
1610  kDebug();
1611  quit(); // stop playing and tidy up
1612  wait(3000); // wait for run() to exit (timeout 3 seconds)
1613  if (!isFinished())
1614  {
1615  // Something has gone wrong - forcibly kill the thread
1616  terminate();
1617  if (waiT)
1618  wait();
1619  }
1620 }
1621 
1622 /******************************************************************************
1623 * Kick off the thread to play the audio file.
1624 */
1625 void AudioThread::run()
1626 {
1627  mMutex.lock();
1628  if (mAudioObject)
1629  {
1630  mMutex.unlock();
1631  return;
1632  }
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)
1638  {
1639  mError = i18nc("@info", "Cannot open audio file: <filename>%1</filename>", audioFile);
1640  mMutex.unlock();
1641  kError() << "Open failure:" << audioFile;
1642  return;
1643  }
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)
1650  {
1651  float vol = (mVolume >= 0) ? mVolume : output->volume();
1652  float maxvol = qMax(vol, mFadeVolume);
1653  output->setVolume(maxvol);
1654  if (mFadeVolume >= 0 && mFadeSeconds > 0)
1655  {
1656  Phonon::VolumeFaderEffect* fader = new Phonon::VolumeFaderEffect(mAudioObject);
1657  fader->setVolume(mFadeVolume / maxvol);
1658  fader->fadeTo(mVolume / maxvol, mFadeSeconds * 1000);
1659  mPath.insertEffect(fader);
1660  }
1661  }
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;
1665  mPausing = false;
1666  mMutex.unlock();
1667  emit readyToPlay();
1668  checkAudioPlay();
1669 
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()));
1674  exec();
1675  stopPlay();
1676 }
1677 
1678 /******************************************************************************
1679 * Called when the audio file has loaded and is ready to play, or when play
1680 * has completed.
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.
1683 */
1684 void AudioThread::checkAudioPlay()
1685 {
1686  mMutex.lock();
1687  if (!mAudioObject)
1688  {
1689  mMutex.unlock();
1690  return;
1691  }
1692  if (mPausing)
1693  mPausing = false;
1694  else
1695  {
1696  // The file has loaded and is ready to play, or play has completed
1697  if (mPlayedOnce)
1698  {
1699  if (mRepeatPause < 0)
1700  {
1701  // Play has completed
1702  mMutex.unlock();
1703  stopPlay();
1704  return;
1705  }
1706  if (mRepeatPause > 0)
1707  {
1708  // Pause before playing the file again
1709  mPausing = true;
1710  QTimer::singleShot(mRepeatPause * 1000, this, SLOT(checkAudioPlay()));
1711  mMutex.unlock();
1712  return;
1713  }
1714  }
1715  mPlayedOnce = true;
1716  }
1717 
1718  // Start playing the file, either for the first time or again
1719  kDebug() << "start";
1720  mAudioObject->play();
1721  mMutex.unlock();
1722 }
1723 
1724 /******************************************************************************
1725 * Called when the playback object changes state.
1726 * If an error has occurred, quit and return the error to the caller.
1727 */
1728 void AudioThread::playStateChanged(Phonon::State newState)
1729 {
1730  if (newState == Phonon::ErrorState)
1731  {
1732  QMutexLocker locker(&mMutex);
1733  QString err = mAudioObject->errorString();
1734  if (!err.isEmpty())
1735  {
1736  kError() << "Play failure:" << mFile << ":" << err;
1737  mError = i18nc("@info", "<para>Error playing audio file: <filename>%1</filename></para><para>%2</para>", mFile, err);
1738  exit(1);
1739  }
1740  }
1741 }
1742 
1743 /******************************************************************************
1744 * Called when play completes, the Silence button is clicked, or the window is
1745 * closed, to terminate audio access.
1746 */
1747 void AudioThread::stopPlay()
1748 {
1749  mMutex.lock();
1750  if (mAudioObject)
1751  {
1752  mAudioObject->stop();
1753  QList<Phonon::Effect*> effects = mPath.effects();
1754  for (int i = 0; i < effects.count(); ++i)
1755  {
1756  mPath.removeEffect(effects[i]);
1757  delete effects[i];
1758  }
1759  delete mAudioObject;
1760  mAudioObject = 0;
1761  }
1762  mMutex.unlock();
1763  quit(); // exit the event loop, if it's still running
1764 }
1765 
1766 QString AudioThread::error() const
1767 {
1768  QMutexLocker locker(&mMutex);
1769  return mError;
1770 }
1771 
1772 /******************************************************************************
1773 * Raise the alarm window, re-output any required audio notification, and
1774 * reschedule the alarm in the calendar file.
1775 */
1776 void MessageWin::repeat(const KAAlarm& alarm)
1777 {
1778  if (!mInitialised)
1779  return;
1780  if (mDeferDlg)
1781  {
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.
1784  delete mDeferDlg;
1785  mDeferDlg = 0;
1786  }
1787  KAEvent* event = mEventId.isEmpty() ? 0 : AlarmCalendar::resources()->event(mEventId);
1788  if (event)
1789  {
1790  mAlarmType = alarm.type(); // store new alarm type for use if it is later deferred
1791  if (mAlwaysHide)
1792  playAudio();
1793  else
1794  {
1795  if (!mDeferDlg || Preferences::modalMessages())
1796  {
1797  raise();
1798  playAudio();
1799  }
1800  if (mDeferButton->isVisible())
1801  {
1802  mDeferButton->setEnabled(true);
1803  setDeferralLimit(*event); // ensure that button is disabled when alarm can't be deferred any more
1804  }
1805  }
1806  alarmShowing(*event);
1807  }
1808 }
1809 
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().
1815 */
1816 void MessageWin::show()
1817 {
1818  if (mCloseTime.isValid())
1819  {
1820  // Set a timer to auto-close the window
1821  int delay = KDateTime::currentUtcDateTime().dateTime().secsTo(mCloseTime);
1822  if (delay < 0)
1823  delay = 0;
1824  QTimer::singleShot(delay * 1000, this, SLOT(close()));
1825  if (!delay)
1826  return; // don't show the window if auto-closing is already due
1827  }
1828  if (Preferences::messageButtonDelay() == 0)
1829  move(0, 0);
1830  MainWindowBase::show();
1831 }
1832 
1833 /******************************************************************************
1834 * Returns the window's recommended size exclusive of its frame.
1835 */
1836 QSize MessageWin::sizeHint() const
1837 {
1838  QSize desired;
1839  switch (mAction)
1840  {
1841  case KAEvent::MESSAGE:
1842  desired = MainWindowBase::sizeHint();
1843  break;
1844  case KAEvent::COMMAND:
1845  if (mShown)
1846  {
1847  // For command output, expand the window to accommodate the text
1848  QSize texthint = mCommandText->sizeHint();
1849  int w = texthint.width() + 2*KDialog::marginHint();
1850  if (w < width())
1851  w = width();
1852  int ypadding = height() - mCommandText->height();
1853  desired = QSize(w, texthint.height() + ypadding);
1854  break;
1855  }
1856  // fall through to default
1857  default:
1858  return MainWindowBase::sizeHint();
1859  }
1860 
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);
1865 }
1866 
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.
1871 */
1872 void MessageWin::showEvent(QShowEvent* se)
1873 {
1874  MainWindowBase::showEvent(se);
1875  if (mShown)
1876  return;
1877  if (mErrorWindow || mAlarmType == KAAlarm::INVALID_ALARM)
1878  {
1879  // Don't bother repositioning error messages,
1880  // and invalid alarms should be deleted anyway.
1881  enableButtons();
1882  }
1883  else
1884  {
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
1888  * set again later.
1889  */
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);
1894  resize(s);
1895 
1896  QRect desk = KAlarm::desktopWorkArea(mScreenNumber);
1897  QRect frame = frameGeometry();
1898 
1899  mButtonDelay = Preferences::messageButtonDelay() * 1000;
1900  if (mButtonDelay)
1901  {
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;
1907  }
1908  else
1909  {
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.
1919  */
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();
1929 
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();
1934 
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
1945 
1946  if (x != frame.left() || y != frame.top())
1947  {
1948  mPositioning = true;
1949  move(x, y);
1950  execComplete = false;
1951  }
1952  }
1953  if (execComplete)
1954  displayComplete(); // play audio, etc.
1955  }
1956 
1957  // Set the window size etc. once the frame size is known
1958  QTimer::singleShot(0, this, SLOT(frameDrawn()));
1959 
1960  mShown = true;
1961 }
1962 
1963 /******************************************************************************
1964 * Called when the window has been moved.
1965 */
1966 void MessageWin::moveEvent(QMoveEvent* e)
1967 {
1968  MainWindowBase::moveEvent(e);
1969  theApp()->setSpreadWindowsState(isSpread(KAlarm::desktopWorkArea(mScreenNumber).topLeft()));
1970  if (mPositioning)
1971  {
1972  // The window has just been initially positioned
1973  mPositioning = false;
1974  displayComplete(); // play audio, etc.
1975  }
1976 }
1977 
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.
1982 */
1983 void MessageWin::frameDrawn()
1984 {
1985  if (!mErrorWindow && mAction == KAEvent::MESSAGE)
1986  {
1987  QSize s = sizeHint();
1988  if (width() > s.width() || height() > s.height())
1989  resize(s);
1990  }
1991  theApp()->setSpreadWindowsState(isSpread(KAlarm::desktopWorkArea(mScreenNumber).topLeft()));
1992 }
1993 
1994 /******************************************************************************
1995 * Called when the window has been displayed properly (in its correct position),
1996 * to play sounds and reschedule the event.
1997 */
1998 void MessageWin::displayComplete()
1999 {
2000  playAudio();
2001  if (mRescheduleEvent)
2002  alarmShowing(mEvent);
2003 
2004  if (!mAlwaysHide)
2005  {
2006  // Enable the window's buttons either now or after the configured delay
2007  if (mButtonDelay > 0)
2008  QTimer::singleShot(mButtonDelay, this, SLOT(enableButtons()));
2009  else
2010  enableButtons();
2011  }
2012 }
2013 
2014 /******************************************************************************
2015 * Enable the window's buttons.
2016 */
2017 void MessageWin::enableButtons()
2018 {
2019  mOkButton->setEnabled(true);
2020  mKAlarmButton->setEnabled(true);
2021  if (mDeferButton->isVisible() && !mDisableDeferral)
2022  mDeferButton->setEnabled(true);
2023  if (mEditButton)
2024  mEditButton->setEnabled(true);
2025  if (mKMailButton)
2026  mKMailButton->setEnabled(true);
2027 }
2028 
2029 /******************************************************************************
2030 * Called when the window's size has changed (before it is painted).
2031 */
2032 void MessageWin::resizeEvent(QResizeEvent* re)
2033 {
2034  if (mRestoreHeight)
2035  {
2036  // Restore the window height on session restoration
2037  if (mRestoreHeight != re->size().height())
2038  {
2039  QSize size = re->size();
2040  size.setHeight(mRestoreHeight);
2041  resize(size);
2042  }
2043  else if (isVisible())
2044  mRestoreHeight = 0;
2045  }
2046  else
2047  {
2048  if (mShown && mAction == KAEvent::FILE && !mErrorMsgs.count())
2049  KAlarm::writeConfigWindowSize("FileMessage", re->size());
2050  MainWindowBase::resizeEvent(re);
2051  }
2052 }
2053 
2054 /******************************************************************************
2055 * Called when a close event is received.
2056 * Only quits the application if there is no system tray icon displayed.
2057 */
2058 void MessageWin::closeEvent(QCloseEvent* ce)
2059 {
2060  // Don't prompt or delete the alarm from the display calendar if the session is closing
2061  if (!mErrorWindow && !theApp()->sessionClosingDown())
2062  {
2063  if (mConfirmAck && !mNoCloseConfirm)
2064  {
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)
2069  {
2070  ce->ignore();
2071  return;
2072  }
2073  }
2074  if (!mEventId.isEmpty())
2075  {
2076  // Delete from the display calendar
2077 #ifdef USE_AKONADI
2078  KAlarm::deleteDisplayEvent(CalEvent::uid(mEventId.eventId(), CalEvent::DISPLAYING));
2079 #else
2080  KAlarm::deleteDisplayEvent(CalEvent::uid(mEventId, CalEvent::DISPLAYING));
2081 #endif
2082  }
2083  }
2084  MainWindowBase::closeEvent(ce);
2085 }
2086 
2087 /******************************************************************************
2088 * Called when the OK button is clicked.
2089 */
2090 void MessageWin::slotOk()
2091 {
2092  if (mDontShowAgainCheck && mDontShowAgainCheck->isChecked())
2093  KAlarm::setDontShowErrors(mEventId, mDontShowAgain);
2094  close();
2095 }
2096 
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.
2101 */
2102 void MessageWin::slotShowKMailMessage()
2103 {
2104  kDebug();
2105  if (!mKMailSerialNumber)
2106  return;
2107  QString err = KAlarm::runKMail(false);
2108  if (!err.isNull())
2109  {
2110  KAMessageBox::sorry(this, err);
2111  return;
2112  }
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>"));
2119 }
2120 #endif
2121 
2122 /******************************************************************************
2123 * Called when the Edit... button is clicked.
2124 * Displays the alarm edit dialog.
2125 *
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.
2131 */
2132 void MessageWin::slotEdit()
2133 {
2134  kDebug();
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)));
2144 #ifdef USE_AKONADI
2145  mainWin->editAlarm(mEditDlg, mOriginalEvent);
2146 #else
2147  mainWin->editAlarm(mEditDlg, mOriginalEvent, mResource);
2148 #endif
2149 }
2150 
2151 /******************************************************************************
2152 * Called when OK is clicked in the alarm edit dialog invoked by the Edit button.
2153 * Closes the window.
2154 */
2155 void MessageWin::editCloseOk()
2156 {
2157  mEditDlg = 0;
2158  mNoCloseConfirm = true; // allow window to close without confirmation prompt
2159  close();
2160 }
2161 
2162 /******************************************************************************
2163 * Called when Cancel is clicked in the alarm edit dialog invoked by the Edit
2164 * button, or when the dialog is deleted.
2165 */
2166 void MessageWin::editCloseCancel()
2167 {
2168  mEditDlg = 0;
2169  setButtonsReadOnly(false);
2170 }
2171 
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.
2176 */
2177 void MessageWin::activeWindowChanged(WId win)
2178 {
2179  if (mEditDlg && win == winId())
2180  KWindowSystem::activateWindow(mEditDlg->winId());
2181 }
2182 
2183 /******************************************************************************
2184 * Set or clear the read-only state of the dialog buttons.
2185 */
2186 void MessageWin::setButtonsReadOnly(bool ro)
2187 {
2188  mOkButton->setReadOnly(ro, true);
2189  mDeferButton->setReadOnly(ro, true);
2190  mEditButton->setReadOnly(ro, true);
2191  if (mSilenceButton)
2192  mSilenceButton->setReadOnly(ro, true);
2193  if (mKMailButton)
2194  mKMailButton->setReadOnly(ro, true);
2195  mKAlarmButton->setReadOnly(ro, true);
2196 }
2197 
2198 /******************************************************************************
2199 * Set up to disable the defer button when the deferral limit is reached.
2200 */
2201 void MessageWin::setDeferralLimit(const KAEvent& event)
2202 {
2203  mDeferLimit = event.deferralLimit().effectiveKDateTime().toUtc().dateTime();
2204  MidnightTimer::connect(this, SLOT(checkDeferralLimit())); // check every day
2205  mDisableDeferral = false;
2206  checkDeferralLimit();
2207 }
2208 
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.
2217 */
2218 void MessageWin::checkDeferralLimit()
2219 {
2220  if (!mDeferButton->isEnabled() || !mDeferLimit.isValid())
2221  return;
2222  int n = KDateTime::currentLocalDate().daysTo(KDateTime(mDeferLimit, KDateTime::LocalZone).date());
2223  if (n > 0)
2224  return;
2225  MidnightTimer::disconnect(this, SLOT(checkDeferralLimit()));
2226  if (n == 0)
2227  {
2228  // The deferral limit will be reached today
2229  n = KDateTime::currentUtcDateTime().dateTime().secsTo(mDeferLimit);
2230  if (n > 0)
2231  {
2232  QTimer::singleShot(n * 1000, this, SLOT(checkDeferralLimit()));
2233  return;
2234  }
2235  }
2236  mDeferButton->setEnabled(false);
2237  mDisableDeferral = true;
2238 }
2239 
2240 /******************************************************************************
2241 * Called when the Defer... button is clicked.
2242 * Displays the defer message dialog.
2243 */
2244 void MessageWin::slotDefer()
2245 {
2246  mDeferDlg = new DeferAlarmDlg(KDateTime::currentDateTime(Preferences::timeZone()).addSecs(60), mDateTime.isDateOnly(), false, this);
2247  mDeferDlg->setObjectName(QLatin1String("DeferDlg")); // used by LikeBack
2248  mDeferDlg->setDeferMinutes(mDefaultDeferMinutes > 0 ? mDefaultDeferMinutes : Preferences::defaultDeferTime());
2249  mDeferDlg->setLimit(mEvent);
2250  if (!Preferences::modalMessages())
2251  lower();
2252  if (mDeferDlg->exec() == QDialog::Accepted)
2253  {
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);
2259  if (event)
2260  {
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;
2269  }
2270  else
2271  {
2272  // Try to retrieve the event from the displaying or archive calendars
2273 #ifdef USE_AKONADI
2274  Akonadi::Collection collection;
2275 #else
2276  AlarmResource* resource = 0;
2277 #endif
2278  KAEvent event;
2279  bool showEdit, showDefer;
2280 #ifdef USE_AKONADI
2281  if (!retrieveEvent(event, collection, showEdit, showDefer))
2282 #else
2283  if (!retrieveEvent(event, resource, showEdit, showDefer))
2284 #endif
2285  {
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>"));
2289  raise();
2290  delete mDeferDlg;
2291  mDeferDlg = 0;
2292  mDeferButton->setEnabled(false);
2293  mEditButton->setEnabled(false);
2294  return;
2295  }
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.
2302 #ifdef USE_AKONADI
2303  KAlarm::addEvent(event, &collection, mDeferDlg, KAlarm::USE_EVENT_ID);
2304 #else
2305  KAlarm::addEvent(event, resource, mDeferDlg, KAlarm::USE_EVENT_ID);
2306 #endif
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);
2313  }
2314  if (theApp()->wantShowInSystemTray())
2315  {
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);
2319  }
2320  mNoCloseConfirm = true; // allow window to close without confirmation prompt
2321  close();
2322  }
2323  else
2324  raise();
2325  delete mDeferDlg;
2326  mDeferDlg = 0;
2327 }
2328 
2329 /******************************************************************************
2330 * Called when the KAlarm icon button in the message window is clicked.
2331 * Displays the main window, with the appropriate alarm selected.
2332 */
2333 void MessageWin::displayMainWindow()
2334 {
2335 #ifdef USE_AKONADI
2336  KAlarm::displayMainWindowSelected(mEventItemId);
2337 #else
2338  KAlarm::displayMainWindowSelected(mEventId);
2339 #endif
2340 }
2341 
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.
2346 */
2347 bool MessageWin::haveErrorMessage(unsigned msg) const
2348 {
2349  if (!mErrorMessages.contains(mEventId))
2350  mErrorMessages.insert(mEventId, 0);
2351  bool result = (mErrorMessages[mEventId] & msg);
2352  mErrorMessages[mEventId] |= msg;
2353  return result;
2354 }
2355 
2356 void MessageWin::clearErrorMessage(unsigned msg) const
2357 {
2358  if (mErrorMessages.contains(mEventId))
2359  {
2360  if (mErrorMessages[mEventId] == msg)
2361  mErrorMessages.remove(mEventId);
2362  else
2363  mErrorMessages[mEventId] &= ~msg;
2364  }
2365 }
2366 
2367 
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).
2374 *
2375 * Also find the usable area of the desktop (excluding panel etc.), on the
2376 * appropriate screen if there are multiple screens.
2377 */
2378 bool MessageWin::getWorkAreaAndModal()
2379 {
2380  mScreenNumber = -1;
2381  bool modal = Preferences::modalMessages();
2382 #ifdef Q_WS_X11
2383  QDesktopWidget* desktop = qApp->desktop();
2384  int numScreens = desktop->numScreens();
2385  if (numScreens > 1)
2386  {
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())
2392  {
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)
2401  return modal;
2402  for (int s = 0; s < numScreens; ++s)
2403  {
2404  if (screenTypes[s] == NoFullScreen)
2405 
2406  {
2407  // There is no full screen window on this screen
2408  mScreenNumber = s;
2409  return modal;
2410  }
2411  }
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)
2415  {
2416  if (screenTypes[s] == FullScreen)
2417  {
2418  mScreenNumber = s;
2419  return modal;
2420  }
2421  }
2422  }
2423  else
2424  {
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)
2434  {
2435  if (s != mScreenNumber)
2436  {
2437  full = haveFullScreenWindow(s);
2438  if (full == NoFullScreen)
2439  {
2440  // There is no full screen window on this screen
2441  mScreenNumber = s;
2442  return modal;
2443  }
2444  if (full == FullScreen && inactiveScreen < 0)
2445  inactiveScreen = s;
2446  }
2447  }
2448  if (inactiveScreen >= 0)
2449  {
2450  // All screens contain a full screen window: use one without
2451  // an active full screen window.
2452  mScreenNumber = inactiveScreen;
2453  return modal;
2454  }
2455  }
2456  return false; // can't logically get here, since there can only be one active window...
2457  }
2458 #endif
2459  if (modal)
2460  {
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.
2465  }
2466  return modal;
2467 }
2468 
2469 #ifdef Q_WS_X11
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.
2473 */
2474 FullScreenType haveFullScreenWindow(int screen)
2475 {
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);
2484 NETRect geom;
2485 NETRect frame;
2486  for (int w = 0; w < windowCount; ++w)
2487  {
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)
2493  {
2494 kDebug()<<"Found FULL SCREEN: "<<windows[w]<<", geom="<<gm<<", frame="<<fr;
2495  type = FullScreen;
2496  if (windows[w] == activeWindow)
2497  return FullScreenActive;
2498  }
2499 //else { kDebug()<<"Found normal: "<<windows[w]<<", geom="<<gm<<", frame="<<fr; }
2500  }
2501  return type;
2502 }
2503 
2504 /******************************************************************************
2505 * In a multi-screen setup (single virtual desktop, e.g. Xinerama), find which
2506 * screens have full screen windows on them.
2507 */
2508 FullScreenType findFullScreenWindows(const QVector<QRect>& screenRects, QVector<FullScreenType>& screenTypes)
2509 {
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);
2519  NETRect netgeom;
2520  NETRect netframe;
2521  for (int w = 0; w < windowCount; ++w)
2522  {
2523  NETWinInfo winInfo(display, windows[w], rootWindow, NET::WMState | NET::WMGeometry);
2524  if (winInfo.state() & NET::FullScreen)
2525  {
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)
2532  {
2533  if (screenRects[s].contains(winRect))
2534  {
2535 kDebug()<<"FULL SCREEN on screen"<<s<<", active="<<active;
2536  if (active)
2537  screenTypes[s] = result = FullScreenActive;
2538  else
2539  {
2540  if (screenTypes[s] == NoFullScreen)
2541  screenTypes[s] = FullScreen;
2542  if (result == NoFullScreen)
2543  result = FullScreen;
2544  }
2545  break;
2546  }
2547  }
2548  }
2549  }
2550  return result;
2551 }
2552 #endif
2553 
2554 // vim: et sw=4:
PreferencesBase::defaultDeferTime
static int defaultDeferTime()
Get Default defer time interval.
Definition: kalarmconfig.h:367
PreferencesBase::messageButtonDelay
static int messageButtonDelay()
Get Delay before message window buttons are enabled.
Definition: kalarmconfig.h:462
synchtimer.h
MessageWin::resizeEvent
virtual void resizeEvent(QResizeEvent *)
Definition: messagewin.cpp:2032
KAlarmApp::rescheduleAlarm
void rescheduleAlarm(KAEvent &e, const KAAlarm &a)
Definition: kalarmapp.h:84
CollectionControlModel::getStandard
static Akonadi::Collection getStandard(CalEvent::Type, bool useDefault=false)
Return the standard collection for a specified mime type.
Definition: collectionmodel.cpp:991
MessageWin::moveEvent
virtual void moveEvent(QMoveEvent *)
Definition: messagewin.cpp:1966
MessageWin::NO_INIT_VIEW
Definition: messagewin.h:69
AudioThread::readyToPlay
void readyToPlay()
AlarmCalendar::deleteEvent
bool deleteEvent(const QString &eventID, bool save=false)
Definition: alarmcalendar.cpp:1656
MessageWin::~MessageWin
~MessageWin()
Definition: messagewin.cpp:415
CollectionControlModel::isWritableEnabled
static int isWritableEnabled(const Akonadi::Collection &, CalEvent::Type)
Return whether a collection is both enabled and fully writable for a given alarm type, i.e.
Definition: collectionmodel.cpp:966
MessageWin::isAudioPlaying
static bool isAudioPlaying()
Definition: messagewin.cpp:1517
AudioThread::AudioThread
AudioThread(MessageWin *parent, const QString &audioFile, float volume, float fadeVolume, int fadeSeconds, int repeatPause)
Definition: messagewin.cpp:1574
MidnightTimer::connect
static void connect(QObject *receiver, const char *member)
AlarmCalendar::eventReadOnly
bool eventReadOnly(const QString &uniqueID) const
Definition: alarmcalendar.cpp:2093
text
virtual QByteArray text(quint32 serialNumber) const =0
MessageWin::spread
static bool spread(bool scatter)
Definition: messagewin.cpp:1341
deferdlg.h
date
time_t date() const
AlarmCalendar::kcalEvent
KCal::Event * kcalEvent(const QString &uniqueId)
Definition: alarmcalendar.cpp:1861
MainWindowBase
The MainWindowBase class is a base class for KAlarm's main window and message window.
Definition: mainwindowbase.h:36
AlarmCalendar::resources
static AlarmCalendar * resources()
Definition: alarmcalendar.h:130
QWidget
KAMessageBox::error
static void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Options(Notify|WindowModal))
PushButton::setReadOnly
virtual void setReadOnly(bool readOnly, bool noHighlight=false)
AudioThread::~AudioThread
~AudioThread()
Definition: messagewin.cpp:1592
editdlg.h
AlarmCalendar::addEvent
bool addEvent(KAEvent *, QWidget *promptParent=0, bool useEventID=false, AlarmResource *=0, bool noPrompt=false, bool *cancelled=0)
Definition: alarmcalendar.cpp:1261
alarmcalendar.h
WidgetFlags
static const Qt::WidgetAttribute WidgetFlags
Definition: messagewin.cpp:154
DeferAlarmDlg::setDeferMinutes
void setDeferMinutes(int mins)
Definition: deferdlg.cpp:125
MessageWin::closeEvent
virtual void closeEvent(QCloseEvent *)
Definition: messagewin.cpp:2058
AlarmCalendar::resourceForEvent
AlarmResource * resourceForEvent(const QString &eventID) const
Definition: alarmcalendar.cpp:2120
DeferAlarmDlg
Definition: deferdlg.h:38
QObject
AudioThread::run
virtual void run()
Definition: messagewin.cpp:1625
AkonadiModel::collectionById
Akonadi::Collection collectionById(Akonadi::Collection::Id) const
Definition: akonadimodel.cpp:1780
AlarmCalendar::isOpen
bool isOpen()
Definition: alarmcalendar.cpp:235
QPointer< AudioThread >
MessageWin::ALWAYS_HIDE
Definition: messagewin.h:68
PushButton
ErrMsg_AudioFile
Definition: messagewin.cpp:159
kalarmapp.h
the KAlarm application object
pushbutton.h
MainWindow::editAlarm
void editAlarm(EditAlarmDlg *, const KAEvent &, AlarmResource *)
Definition: mainwindow.cpp:1725
KAlarmApp::quitIf
bool quitIf()
Definition: kalarmapp.h:69
MessageWin
MessageWin: A window to display an alarm or error message.
Definition: messagewin.h:61
DeferAlarmDlg::deferMinutes
int deferMinutes() const
Definition: deferdlg.h:47
WFLAGS2
static const Qt::WindowFlags WFLAGS2
Definition: messagewin.cpp:153
MessageWin::saveProperties
virtual void saveProperties(KConfigGroup &)
Definition: messagewin.cpp:991
autoqpointer.h
KAlarmApp::execCommandAlarm
ShellProcess * execCommandAlarm(const KAEvent &, const KAAlarm &, const QObject *receiver=0, const char *slot=0)
Definition: kalarmapp.cpp:2025
Preferences::timeZone
static KTimeZone timeZone(bool reload=false)
Definition: preferences.cpp:122
KAlarmApp::notifyAudioPlaying
void notifyAudioPlaying(bool playing)
Definition: kalarmapp.cpp:2449
desktop.h
AkonadiModel::instance
static AkonadiModel * instance()
Definition: akonadimodel.cpp:83
proximityMultiple
static const int proximityMultiple
Definition: messagewin.cpp:116
ErrMsg_Speak
Definition: messagewin.cpp:158
MessageWin::stopAudio
static void stopAudio(bool wait=false)
Definition: messagewin.cpp:1525
mainwindow.h
main application window
MessageWin::showDefer
void showDefer()
Definition: messagewin.cpp:838
MessageWin::hasDefer
bool hasDefer() const
Definition: messagewin.cpp:830
EditAlarmDlg::create
static EditAlarmDlg * create(bool Template, Type, QWidget *parent=0, GetResourceType=RES_PROMPT)
Definition: editdlg.cpp:106
WFLAGS
static const Qt::WindowFlags WFLAGS
Definition: messagewin.cpp:152
MinuteTimer::disconnect
static void disconnect(QObject *receiver, const char *member=0)
MinuteTimer::connect
static void connect(QObject *receiver, const char *member)
messagebox.h
MessageWin::dateTime
const DateTime & dateTime()
Definition: messagewin.h:77
KAMessageBox::detailedError
static void detailedError(QWidget *parent, const QString &text, const QString &details, const QString &caption=QString(), Options options=Options(Notify|WindowModal))
MessageWin::MessageWin
MessageWin()
Definition: messagewin.cpp:382
theApp
KAlarmApp * theApp()
Definition: kalarmapp.h:259
MessageWin::instanceCount
static int instanceCount(bool excludeAlwaysHidden=false)
Definition: messagewin.cpp:816
MessageWin::redisplayAlarms
static void redisplayAlarms()
Definition: messagewin.cpp:1152
MessageWin::sizeHint
virtual QSize sizeHint() const
Definition: messagewin.cpp:1836
MessageWin::findEvent
static MessageWin * findEvent(const QString &eventId)
Definition: messagewin.cpp:1417
preferences.h
collectionmodel.h
MessageWin::showDateTime
void showDateTime(const KAEvent &, const KAAlarm &)
Definition: messagewin.cpp:877
EventId
Unique event identifier for Akonadi.
Definition: eventid.h:38
AlarmCalendar::kcalEvents
KCal::Event::List kcalEvents(CalEvent::Type s=CalEvent::EMPTY)
Definition: alarmcalendar.h:107
DeferAlarmDlg::getDateTime
const DateTime & getDateTime() const
Definition: deferdlg.h:45
MessageWin::repeat
void repeat(const KAAlarm &)
Definition: messagewin.cpp:1776
AudioThread
Definition: messagewin_p.h:33
KAMessageBox::warningYesNo
static int warningYesNo(QWidget *parent, const QString &text, const QString &caption=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous|WindowModal))
functions.h
miscellaneous functions
KAMessageBox::sorry
static void sorry(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Options(Notify|WindowModal))
DeferAlarmDlg::setLimit
void setLimit(const DateTime &)
Definition: deferdlg.cpp:142
MessageWin::showError
static void showError(const KAEvent &, const DateTime &alarmDateTime, const QStringList &errmsgs, const QString &dontShowAgain=QString())
Definition: messagewin.cpp:287
kalarm.h
PreferencesBase::modalMessages
static bool modalMessages()
Get Message windows have a title bar and take keyboard focus.
Definition: kalarmconfig.h:423
QLabel
MessageWin::cancelReminder
void cancelReminder(const KAEvent &, const KAAlarm &)
Definition: messagewin.cpp:852
MidnightTimer::disconnect
static void disconnect(QObject *receiver, const char *member=0)
MessageWin::show
virtual void show()
Definition: messagewin.cpp:1816
MessageWin::readProperties
virtual void readProperties(const KConfigGroup &)
Definition: messagewin.cpp:1053
KAlarmApp::displayTrayIcon
bool displayTrayIcon(bool show, MainWindow *=0)
Definition: kalarmapp.cpp:977
AlarmCalendar
Provides read and write access to calendar files and resources.
Definition: alarmcalendar.h:58
AlarmCalendar::displayCalendarOpen
static AlarmCalendar * displayCalendarOpen()
Definition: alarmcalendar.cpp:130
proximityButtonDelay
static const int proximityButtonDelay
Definition: messagewin.cpp:115
AudioThread::stop
void stop(bool wait=false)
Definition: messagewin.cpp:1608
KAlarmApp::kspeechInterface
OrgKdeKSpeechInterface * kspeechInterface(QString &error) const
Definition: kalarmapp.cpp:2356
shellprocess.h
AlarmCalendar::event
KAEvent * event(const QString &uniqueId)
Definition: alarmcalendar.cpp:1820
AlarmCalendar::save
bool save()
Definition: alarmcalendar.cpp:1212
EditAlarmDlg::RES_IGNORE
Definition: editdlg.h:69
MainWindow::mainMainWindow
static MainWindow * mainMainWindow()
Definition: mainwindow.cpp:288
QCheckBox
KAlarm::desktopWorkArea
QRect desktopWorkArea(int screen)
QFrame
QThread
MessageWin::showEvent
virtual void showEvent(QShowEvent *)
Definition: messagewin.cpp:1872
AkonadiModel::collectionForItem
Akonadi::Collection collectionForItem(Akonadi::Item::Id) const
Definition: akonadimodel.cpp:1813
MainWindowBase::closeEvent
virtual void closeEvent(QCloseEvent *)
Definition: mainwindowbase.cpp:39
MessageWin::NO_RESCHEDULE
Definition: messagewin.h:66
AlarmCalendar::displayCalendar
static AlarmCalendar * displayCalendar()
Definition: alarmcalendar.h:131
MainWindow
Definition: mainwindow.h:71
EventId::isEmpty
bool isEmpty() const
Return whether the instance contains any data.
Definition: eventid.h:49
MessageWin::NO_DEFER
Definition: messagewin.h:67
ShellProcess
KAlarmApp::setSpreadWindowsState
void setSpreadWindowsState(bool spread)
Definition: kalarmapp.cpp:1320
AudioThread::error
QString error() const
Definition: messagewin.cpp:1766
KTextEdit
QList< MessageWin * >
KAlarmApp::alarmCompleted
void alarmCompleted(const KAEvent &)
Definition: kalarmapp.cpp:1662
AudioThread::mAudioOwner
static MessageWin * mAudioOwner
Definition: messagewin_p.h:42
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:59:10 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

kalarm

Skip menu "kalarm"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members

kdepim API Reference

Skip menu "kdepim API Reference"
  • akonadi_next
  • akregator
  • blogilo
  • calendarsupport
  • console
  •   kabcclient
  •   konsolekalendar
  • kaddressbook
  • kalarm
  •   lib
  • kdgantt2
  • kjots
  • kleopatra
  • kmail
  • knode
  • knotes
  • kontact
  • korgac
  • korganizer
  • ktimetracker
  • libkdepim
  • libkleo
  • libkpgp
  • mailcommon
  • messagelist
  • messageviewer

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal