kalarm

messagewin.cpp
1 /*
2  * messagewin.cpp - displays an alarm message
3  * Program: kalarm
4  * Copyright © 2001-2009 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 
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include <tqfile.h>
27 #include <tqfileinfo.h>
28 #include <tqlayout.h>
29 #include <tqpushbutton.h>
30 #include <tqlabel.h>
31 #include <tqwhatsthis.h>
32 #include <tqtooltip.h>
33 #include <tqdragobject.h>
34 #include <tqtextedit.h>
35 #include <tqtimer.h>
36 
37 #include <kstandarddirs.h>
38 #include <tdeaction.h>
39 #include <kstdguiitem.h>
40 #include <tdeaboutdata.h>
41 #include <tdelocale.h>
42 #include <tdeconfig.h>
43 #include <kiconloader.h>
44 #include <kdialog.h>
45 #include <ktextbrowser.h>
46 #include <tdeglobalsettings.h>
47 #include <kmimetype.h>
48 #include <tdemessagebox.h>
49 #include <twin.h>
50 #include <twinmodule.h>
51 #include <tdeprocess.h>
52 #include <tdeio/netaccess.h>
53 #include <knotifyclient.h>
54 #include <kpushbutton.h>
55 #ifdef WITHOUT_ARTS
56 #include <kaudioplayer.h>
57 #else
58 #include <arts/kartsdispatcher.h>
59 #include <arts/kartsserver.h>
60 #include <arts/kplayobjectfactory.h>
61 #include <arts/kplayobject.h>
62 #endif
63 #include <dcopclient.h>
64 #include <kdebug.h>
65 
66 #include "alarmcalendar.h"
67 #include "deferdlg.h"
68 #include "editdlg.h"
69 #include "functions.h"
70 #include "kalarmapp.h"
71 #include "mainwindow.h"
72 #include "preferences.h"
73 #include "synchtimer.h"
74 #include "messagewin.moc"
75 
76 using namespace KCal;
77 
78 #ifndef WITHOUT_ARTS
79 static const char* KMIX_APP_NAME = "kmix";
80 static const char* KMIX_DCOP_OBJECT = "Mixer0";
81 static const char* KMIX_DCOP_WINDOW = "kmix-mainwindow#1";
82 #endif
83 static const char* KMAIL_DCOP_OBJECT = "KMailIface";
84 
85 // The delay for enabling message window buttons if a zero delay is
86 // configured, i.e. the windows are placed far from the cursor.
87 static const int proximityButtonDelay = 1000; // (milliseconds)
88 static const int proximityMultiple = 10; // multiple of button height distance from cursor for proximity
89 
90 static bool wantModal();
91 
92 // A text label widget which can be scrolled and copied with the mouse
93 class MessageText : public TQTextEdit
94 {
95  public:
96  MessageText(const TQString& text, const TQString& context = TQString(), TQWidget* parent = 0, const char* name = 0)
97  : TQTextEdit(text, context, parent, name)
98  {
99  setReadOnly(true);
100  setWordWrap(TQTextEdit::NoWrap);
101  }
102  int scrollBarHeight() const { return horizontalScrollBar()->height(); }
103  int scrollBarWidth() const { return verticalScrollBar()->width(); }
104  virtual TQSize sizeHint() const { return TQSize(contentsWidth() + scrollBarWidth(), contentsHeight() + scrollBarHeight()); }
105 };
106 
107 
108 class MWMimeSourceFactory : public TQMimeSourceFactory
109 {
110  public:
111  MWMimeSourceFactory(const TQString& absPath, KTextBrowser*);
112  virtual ~MWMimeSourceFactory();
113  virtual const TQMimeSource* data(const TQString& abs_name) const;
114  private:
115  // Prohibit the following methods
116  virtual void setData(const TQString&, TQMimeSource*) {}
117  virtual void setExtensionType(const TQString&, const char*) {}
118 
119  TQString mTextFile;
120  TQCString mMimeType;
121  mutable const TQMimeSource* mLast;
122 };
123 
124 
125 // Basic flags for the window
126 static const TQt::WFlags WFLAGS = TQt::WStyle_StaysOnTop | TQt::WDestructiveClose;
127 
128 // Error message bit masks
129 enum {
130  ErrMsg_Speak = 0x01,
131  ErrMsg_AudioFile = 0x02,
132  ErrMsg_Volume = 0x04
133 };
134 
135 
136 TQValueList<MessageWin*> MessageWin::mWindowList;
137 TQMap<TQString, unsigned> MessageWin::mErrorMessages;
138 
139 
140 /******************************************************************************
141 * Construct the message window for the specified alarm.
142 * Other alarms in the supplied event may have been updated by the caller, so
143 * the whole event needs to be stored for updating the calendar file when it is
144 * displayed.
145 */
146 MessageWin::MessageWin(const KAEvent& event, const KAAlarm& alarm, bool reschedule_event, bool allowDefer)
147  : MainWindowBase(0, "MessageWin", WFLAGS | TQt::WGroupLeader | TQt::WStyle_ContextHelp
148  | (wantModal() ? 0 : TQt::WX11BypassWM)),
149  mMessage(event.cleanText()),
150  mFont(event.font()),
151  mBgColour(event.bgColour()),
152  mFgColour(event.fgColour()),
153  mDateTime((alarm.type() & KAAlarm::REMINDER_ALARM) ? event.mainDateTime(true) : alarm.dateTime(true)),
154  mEventID(event.id()),
155  mAudioFile(event.audioFile()),
156  mVolume(event.soundVolume()),
157  mFadeVolume(event.fadeVolume()),
158  mFadeSeconds(TQMIN(event.fadeSeconds(), 86400)),
159  mDefaultDeferMinutes(event.deferDefaultMinutes()),
160  mAlarmType(alarm.type()),
161  mAction(event.action()),
162  mKMailSerialNumber(event.kmailSerialNumber()),
163  mRestoreHeight(0),
164  mAudioRepeat(event.repeatSound()),
165  mConfirmAck(event.confirmAck()),
166  mShowEdit(!mEventID.isEmpty()),
167  mNoDefer(!allowDefer || alarm.repeatAtLogin()),
168  mInvalid(false),
169  mArtsDispatcher(0),
170  mPlayObject(0),
171  mOldVolume(-1),
172  mEvent(event),
173  mEditButton(0),
174  mDeferButton(0),
175  mSilenceButton(0),
176  mDeferDlg(0),
177  mWinModule(0),
178  mFlags(event.flags()),
179  mLateCancel(event.lateCancel()),
180  mErrorWindow(false),
181  mNoPostAction(alarm.type() & KAAlarm::REMINDER_ALARM),
182  mRecreating(false),
183  mBeep(event.beep()),
184  mSpeak(event.speak()),
185  mRescheduleEvent(reschedule_event),
186  mShown(false),
187  mPositioning(false),
188  mNoCloseConfirm(false),
189  mDisableDeferral(false)
190 {
191  kdDebug(5950) << "MessageWin::MessageWin(event)" << endl;
192  // Set to save settings automatically, but don't save window size.
193  // File alarm window size is saved elsewhere.
194  setAutoSaveSettings(TQString::fromLatin1("MessageWin"), false);
195  initView();
196  mWindowList.append(this);
197  if (event.autoClose())
198  mCloseTime = alarm.dateTime().dateTime().addSecs(event.lateCancel() * 60);
199 }
200 
201 /******************************************************************************
202 * Construct the message window for a specified error message.
203 */
204 MessageWin::MessageWin(const KAEvent& event, const DateTime& alarmDateTime, const TQStringList& errmsgs)
205  : MainWindowBase(0, "MessageWin", WFLAGS | TQt::WGroupLeader | TQt::WStyle_ContextHelp),
206  mMessage(event.cleanText()),
207  mDateTime(alarmDateTime),
208  mEventID(event.id()),
209  mAlarmType(KAAlarm::MAIN_ALARM),
210  mAction(event.action()),
211  mKMailSerialNumber(0),
212  mErrorMsgs(errmsgs),
213  mRestoreHeight(0),
214  mConfirmAck(false),
215  mShowEdit(false),
216  mNoDefer(true),
217  mInvalid(false),
218  mArtsDispatcher(0),
219  mPlayObject(0),
220  mEvent(event),
221  mEditButton(0),
222  mDeferButton(0),
223  mSilenceButton(0),
224  mDeferDlg(0),
225  mWinModule(0),
226  mErrorWindow(true),
227  mNoPostAction(true),
228  mRecreating(false),
229  mRescheduleEvent(false),
230  mShown(false),
231  mPositioning(false),
232  mNoCloseConfirm(false),
233  mDisableDeferral(false)
234 {
235  kdDebug(5950) << "MessageWin::MessageWin(errmsg)" << endl;
236  initView();
237  mWindowList.append(this);
238 }
239 
240 /******************************************************************************
241 * Construct the message window for restoration by session management.
242 * The window is initialised by readProperties().
243 */
244 MessageWin::MessageWin()
245  : MainWindowBase(0, "MessageWin", WFLAGS),
246  mArtsDispatcher(0),
247  mPlayObject(0),
248  mEditButton(0),
249  mDeferButton(0),
250  mSilenceButton(0),
251  mDeferDlg(0),
252  mWinModule(0),
253  mErrorWindow(false),
254  mRecreating(false),
255  mRescheduleEvent(false),
256  mShown(false),
257  mPositioning(false),
258  mNoCloseConfirm(false),
259  mDisableDeferral(false)
260 {
261  kdDebug(5950) << "MessageWin::MessageWin(restore)\n";
262  mWindowList.append(this);
263 }
264 
265 /******************************************************************************
266 * Destructor. Perform any post-alarm actions before tidying up.
267 */
268 MessageWin::~MessageWin()
269 {
270  kdDebug(5950) << "MessageWin::~MessageWin(" << mEventID << ")" << endl;
271  stopPlay();
272  delete mWinModule;
273  mWinModule = 0;
274  mErrorMessages.remove(mEventID);
275  mWindowList.remove(this);
276  if (!mRecreating)
277  {
278  if (!mNoPostAction && !mEvent.postAction().isEmpty())
279  theApp()->alarmCompleted(mEvent);
280  if (!mWindowList.count())
281  theApp()->quitIf();
282  }
283 }
284 
285 /******************************************************************************
286 * Construct the message window.
287 */
288 void MessageWin::initView()
289 {
290  bool reminder = (!mErrorWindow && (mAlarmType & KAAlarm::REMINDER_ALARM));
291  int leading = fontMetrics().leading();
292  setCaption((mAlarmType & KAAlarm::REMINDER_ALARM) ? i18n("Reminder") : i18n("Message"));
293  TQWidget* topWidget = new TQWidget(this, "messageWinTop");
294  setCentralWidget(topWidget);
295  TQVBoxLayout* topLayout = new TQVBoxLayout(topWidget, KDialog::marginHint(), KDialog::spacingHint());
296 
297  if (mDateTime.isValid())
298  {
299  // Show the alarm date/time, together with an "Advance reminder" text where appropriate
300  TQFrame* frame = 0;
301  TQVBoxLayout* layout = topLayout;
302  if (reminder)
303  {
304  frame = new TQFrame(topWidget);
305  frame->setFrameStyle(TQFrame::Box | TQFrame::Raised);
306  topLayout->addWidget(frame, 0, TQt::AlignHCenter);
307  layout = new TQVBoxLayout(frame, leading + frame->frameWidth(), leading);
308  }
309 
310  // Alarm date/time
311  TQLabel* label = new TQLabel(frame ? frame : topWidget);
312  label->setText(mDateTime.isDateOnly()
313  ? TDEGlobal::locale()->formatDate(mDateTime.date(), true)
314  : TDEGlobal::locale()->formatDateTime(mDateTime.dateTime()));
315  if (!frame)
316  label->setFrameStyle(TQFrame::Box | TQFrame::Raised);
317  label->setFixedSize(label->sizeHint());
318  layout->addWidget(label, 0, TQt::AlignHCenter);
319  TQWhatsThis::add(label,
320  i18n("The scheduled date/time for the message (as opposed to the actual time of display)."));
321 
322  if (frame)
323  {
324  label = new TQLabel(frame);
325  label->setText(i18n("Reminder"));
326  label->setFixedSize(label->sizeHint());
327  layout->addWidget(label, 0, TQt::AlignHCenter);
328  frame->setFixedSize(frame->sizeHint());
329  }
330  }
331 
332  if (!mErrorWindow)
333  {
334  // It's a normal alarm message window
335  switch (mAction)
336  {
337  case KAEvent::FILE:
338  {
339  // Display the file name
340  TQLabel* label = new TQLabel(mMessage, topWidget);
341  label->setFrameStyle(TQFrame::Box | TQFrame::Raised);
342  label->setFixedSize(label->sizeHint());
343  TQWhatsThis::add(label, i18n("The file whose contents are displayed below"));
344  topLayout->addWidget(label, 0, TQt::AlignHCenter);
345 
346  // Display contents of file
347  bool opened = false;
348  bool dir = false;
349  TQString tmpFile;
350  KURL url(mMessage);
351  if (TDEIO::NetAccess::download(url, tmpFile, MainWindow::mainMainWindow()))
352  {
353  TQFile qfile(tmpFile);
354  TQFileInfo info(qfile);
355  if (!(dir = info.isDir()))
356  {
357  opened = true;
358  KTextBrowser* view = new KTextBrowser(topWidget, "fileContents");
359  MWMimeSourceFactory msf(tmpFile, view);
360  view->setMinimumSize(view->sizeHint());
361  topLayout->addWidget(view);
362 
363  // Set the default size to 20 lines square.
364  // Note that after the first file has been displayed, this size
365  // is overridden by the user-set default stored in the config file.
366  // So there is no need to calculate an accurate size.
367  int h = 20*view->fontMetrics().lineSpacing() + 2*view->frameWidth();
368  view->resize(TQSize(h, h).expandedTo(view->sizeHint()));
369  TQWhatsThis::add(view, i18n("The contents of the file to be displayed"));
370  }
371  TDEIO::NetAccess::removeTempFile(tmpFile);
372  }
373  if (!opened)
374  {
375  // File couldn't be opened
376  bool exists = TDEIO::NetAccess::exists(url, true, MainWindow::mainMainWindow());
377  mErrorMsgs += dir ? i18n("File is a folder") : exists ? i18n("Failed to open file") : i18n("File not found");
378  }
379  break;
380  }
381  case KAEvent::MESSAGE:
382  {
383  // Message label
384  // Using MessageText instead of TQLabel allows scrolling and mouse copying
385  MessageText* text = new MessageText(mMessage, TQString(), topWidget);
386  text->setFrameStyle(TQFrame::NoFrame);
387  text->setPaper(mBgColour);
388  text->setPaletteForegroundColor(mFgColour);
389  text->setFont(mFont);
390  int lineSpacing = text->fontMetrics().lineSpacing();
391  TQSize s = text->sizeHint();
392  int h = s.height();
393  text->setMaximumHeight(h + text->scrollBarHeight());
394  text->setMinimumHeight(TQMIN(h, lineSpacing*4));
395  text->setMaximumWidth(s.width() + text->scrollBarWidth());
396  TQWhatsThis::add(text, i18n("The alarm message"));
397  int vspace = lineSpacing/2;
398  int hspace = lineSpacing - KDialog::marginHint();
399  topLayout->addSpacing(vspace);
400  topLayout->addStretch();
401  // Don't include any horizontal margins if message is 2/3 screen width
402  if (!mWinModule)
403  mWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP);
404  if (text->sizeHint().width() >= mWinModule->workArea().width()*2/3)
405  topLayout->addWidget(text, 1, TQt::AlignHCenter);
406  else
407  {
408  TQBoxLayout* layout = new TQHBoxLayout(topLayout);
409  layout->addSpacing(hspace);
410  layout->addWidget(text, 1, TQt::AlignHCenter);
411  layout->addSpacing(hspace);
412  }
413  if (!reminder)
414  topLayout->addStretch();
415  break;
416  }
417  case KAEvent::COMMAND:
418  case KAEvent::EMAIL:
419  default:
420  break;
421  }
422 
423  if (reminder)
424  {
425  // Reminder: show remaining time until the actual alarm
426  mRemainingText = new TQLabel(topWidget);
427  mRemainingText->setFrameStyle(TQFrame::Box | TQFrame::Raised);
428  mRemainingText->setMargin(leading);
429  if (mDateTime.isDateOnly() || TQDate::currentDate().daysTo(mDateTime.date()) > 0)
430  {
431  setRemainingTextDay();
432  MidnightTimer::connect(this, TQ_SLOT(setRemainingTextDay())); // update every day
433  }
434  else
435  {
436  setRemainingTextMinute();
437  MinuteTimer::connect(this, TQ_SLOT(setRemainingTextMinute())); // update every minute
438  }
439  topLayout->addWidget(mRemainingText, 0, TQt::AlignHCenter);
440  topLayout->addSpacing(KDialog::spacingHint());
441  topLayout->addStretch();
442  }
443  }
444  else
445  {
446  // It's an error message
447  switch (mAction)
448  {
449  case KAEvent::EMAIL:
450  {
451  // Display the email addresses and subject.
452  TQFrame* frame = new TQFrame(topWidget);
453  frame->setFrameStyle(TQFrame::Box | TQFrame::Raised);
454  TQWhatsThis::add(frame, i18n("The email to send"));
455  topLayout->addWidget(frame, 0, TQt::AlignHCenter);
456  TQGridLayout* grid = new TQGridLayout(frame, 2, 2, KDialog::marginHint(), KDialog::spacingHint());
457 
458  TQLabel* label = new TQLabel(i18n("Email addressee", "To:"), frame);
459  label->setFixedSize(label->sizeHint());
460  grid->addWidget(label, 0, 0, TQt::AlignLeft);
461  label = new TQLabel(mEvent.emailAddresses("\n"), frame);
462  label->setFixedSize(label->sizeHint());
463  grid->addWidget(label, 0, 1, TQt::AlignLeft);
464 
465  label = new TQLabel(i18n("Email subject", "Subject:"), frame);
466  label->setFixedSize(label->sizeHint());
467  grid->addWidget(label, 1, 0, TQt::AlignLeft);
468  label = new TQLabel(mEvent.emailSubject(), frame);
469  label->setFixedSize(label->sizeHint());
470  grid->addWidget(label, 1, 1, TQt::AlignLeft);
471  break;
472  }
473  case KAEvent::COMMAND:
474  case KAEvent::FILE:
475  case KAEvent::MESSAGE:
476  default:
477  // Just display the error message strings
478  break;
479  }
480  }
481 
482  if (!mErrorMsgs.count())
483  topWidget->setBackgroundColor(mBgColour);
484  else
485  {
486  setCaption(i18n("Error"));
487  TQBoxLayout* layout = new TQHBoxLayout(topLayout);
488  layout->setMargin(2*KDialog::marginHint());
489  layout->addStretch();
490  TQLabel* label = new TQLabel(topWidget);
491  label->setPixmap(DesktopIcon("error"));
492  label->setFixedSize(label->sizeHint());
493  layout->addWidget(label, 0, TQt::AlignRight);
494  TQBoxLayout* vlayout = new TQVBoxLayout(layout);
495  for (TQStringList::Iterator it = mErrorMsgs.begin(); it != mErrorMsgs.end(); ++it)
496  {
497  label = new TQLabel(*it, topWidget);
498  label->setFixedSize(label->sizeHint());
499  vlayout->addWidget(label, 0, TQt::AlignLeft);
500  }
501  layout->addStretch();
502  }
503 
504  TQGridLayout* grid = new TQGridLayout(1, 4);
505  topLayout->addLayout(grid);
506  grid->setColStretch(0, 1); // keep the buttons right-adjusted in the window
507  int gridIndex = 1;
508 
509  // Close button
510  mOkButton = new KPushButton(KStdGuiItem::close(), topWidget);
511  // Prevent accidental acknowledgement of the message if the user is typing when the window appears
512  mOkButton->clearFocus();
513  mOkButton->setFocusPolicy(TQWidget::ClickFocus); // don't allow keyboard selection
514  mOkButton->setFixedSize(mOkButton->sizeHint());
515  connect(mOkButton, TQ_SIGNAL(clicked()), TQ_SLOT(close()));
516  grid->addWidget(mOkButton, 0, gridIndex++, AlignHCenter);
517  TQWhatsThis::add(mOkButton, i18n("Acknowledge the alarm"));
518 
519  if (mShowEdit)
520  {
521  // Edit button
522  mEditButton = new TQPushButton(i18n("&Edit..."), topWidget);
523  mEditButton->setFocusPolicy(TQWidget::ClickFocus); // don't allow keyboard selection
524  mEditButton->setFixedSize(mEditButton->sizeHint());
525  connect(mEditButton, TQ_SIGNAL(clicked()), TQ_SLOT(slotEdit()));
526  grid->addWidget(mEditButton, 0, gridIndex++, AlignHCenter);
527  TQWhatsThis::add(mEditButton, i18n("Edit the alarm."));
528  }
529 
530  if (!mNoDefer)
531  {
532  // Defer button
533  mDeferButton = new TQPushButton(i18n("&Defer..."), topWidget);
534  mDeferButton->setFocusPolicy(TQWidget::ClickFocus); // don't allow keyboard selection
535  mDeferButton->setFixedSize(mDeferButton->sizeHint());
536  connect(mDeferButton, TQ_SIGNAL(clicked()), TQ_SLOT(slotDefer()));
537  grid->addWidget(mDeferButton, 0, gridIndex++, AlignHCenter);
538  TQWhatsThis::add(mDeferButton,
539  i18n("Defer the alarm until later.\n"
540  "You will be prompted to specify when the alarm should be redisplayed."));
541 
542  setDeferralLimit(mEvent); // ensure that button is disabled when alarm can't be deferred any more
543  }
544 
545 #ifndef WITHOUT_ARTS
546  if (!mAudioFile.isEmpty() && (mVolume || mFadeVolume > 0))
547  {
548  // Silence button to stop sound repetition
549  TQPixmap pixmap = MainBarIcon("media-playback-stop");
550  mSilenceButton = new TQPushButton(topWidget);
551  mSilenceButton->setPixmap(pixmap);
552  mSilenceButton->setFixedSize(mSilenceButton->sizeHint());
553  connect(mSilenceButton, TQ_SIGNAL(clicked()), TQ_SLOT(stopPlay()));
554  grid->addWidget(mSilenceButton, 0, gridIndex++, AlignHCenter);
555  TQToolTip::add(mSilenceButton, i18n("Stop sound"));
556  TQWhatsThis::add(mSilenceButton, i18n("Stop playing the sound"));
557  // To avoid getting in a mess, disable the button until sound playing has been set up
558  mSilenceButton->setEnabled(false);
559  }
560 #endif
561 
562  TDEIconLoader iconLoader;
563  if (mKMailSerialNumber)
564  {
565  // KMail button
566  TQPixmap pixmap = iconLoader.loadIcon(TQString::fromLatin1("kmail"), TDEIcon::MainToolbar);
567  mKMailButton = new TQPushButton(topWidget);
568  mKMailButton->setPixmap(pixmap);
569  mKMailButton->setFixedSize(mKMailButton->sizeHint());
570  connect(mKMailButton, TQ_SIGNAL(clicked()), TQ_SLOT(slotShowKMailMessage()));
571  grid->addWidget(mKMailButton, 0, gridIndex++, AlignHCenter);
572  TQToolTip::add(mKMailButton, i18n("Locate this email in KMail", "Locate in KMail"));
573  TQWhatsThis::add(mKMailButton, i18n("Locate and highlight this email in KMail"));
574  }
575  else
576  mKMailButton = 0;
577 
578  // KAlarm button
579  TQPixmap pixmap = iconLoader.loadIcon(TQString::fromLatin1(kapp->aboutData()->appName()), TDEIcon::MainToolbar);
580  mKAlarmButton = new TQPushButton(topWidget);
581  mKAlarmButton->setPixmap(pixmap);
582  mKAlarmButton->setFixedSize(mKAlarmButton->sizeHint());
583  connect(mKAlarmButton, TQ_SIGNAL(clicked()), TQ_SLOT(displayMainWindow()));
584  grid->addWidget(mKAlarmButton, 0, gridIndex++, AlignHCenter);
585  TQString actKAlarm = i18n("Activate KAlarm");
586  TQToolTip::add(mKAlarmButton, actKAlarm);
587  TQWhatsThis::add(mKAlarmButton, actKAlarm);
588 
589  // Disable all buttons initially, to prevent accidental clicking on if they happen to be
590  // under the mouse just as the window appears.
591  mOkButton->setEnabled(false);
592  if (mDeferButton)
593  mDeferButton->setEnabled(false);
594  if (mEditButton)
595  mEditButton->setEnabled(false);
596  if (mKMailButton)
597  mKMailButton->setEnabled(false);
598  mKAlarmButton->setEnabled(false);
599 
600  topLayout->activate();
601  setMinimumSize(TQSize(grid->sizeHint().width() + 2*KDialog::marginHint(), sizeHint().height()));
602 
603  bool modal = !(getWFlags() & TQt::WX11BypassWM);
604 
605  unsigned long wstate = (modal ? NET::Modal : 0) | NET::Sticky | NET::KeepAbove;
606  WId winid = winId();
607  KWin::setState(winid, wstate);
608  KWin::setOnAllDesktops(winid, true);
609 }
610 
611 /******************************************************************************
612 * Set the remaining time text in a reminder window.
613 * Called at the start of every day (at the user-defined start-of-day time).
614 */
615 void MessageWin::setRemainingTextDay()
616 {
617  TQString text;
618  int days = TQDate::currentDate().daysTo(mDateTime.date());
619  if (days <= 0 && !mDateTime.isDateOnly())
620  {
621  // The alarm is due today, so start refreshing every minute
622  MidnightTimer::disconnect(this, TQ_SLOT(setRemainingTextDay()));
623  setRemainingTextMinute();
624  MinuteTimer::connect(this, TQ_SLOT(setRemainingTextMinute())); // update every minute
625  }
626  else
627  {
628  if (days <= 0)
629  text = i18n("Today");
630  else if (days % 7)
631  text = i18n("Tomorrow", "in %n days' time", days);
632  else
633  text = i18n("in 1 week's time", "in %n weeks' time", days/7);
634  }
635  mRemainingText->setText(text);
636 }
637 
638 /******************************************************************************
639 * Set the remaining time text in a reminder window.
640 * Called on every minute boundary.
641 */
642 void MessageWin::setRemainingTextMinute()
643 {
644  TQString text;
645  int mins = (TQDateTime::currentDateTime().secsTo(mDateTime.dateTime()) + 59) / 60;
646  if (mins < 60)
647  text = i18n("in 1 minute's time", "in %n minutes' time", (mins > 0 ? mins : 0));
648  else if (mins % 60 == 0)
649  text = i18n("in 1 hour's time", "in %n hours' time", mins/60);
650  else if (mins % 60 == 1)
651  text = i18n("in 1 hour 1 minute's time", "in %n hours 1 minute's time", mins/60);
652  else
653  text = i18n("in 1 hour %1 minutes' time", "in %n hours %1 minutes' time", mins/60).arg(mins%60);
654  mRemainingText->setText(text);
655 }
656 
657 /******************************************************************************
658 * Save settings to the session managed config file, for restoration
659 * when the program is restored.
660 */
661 void MessageWin::saveProperties(TDEConfig* config)
662 {
663  if (mShown && !mErrorWindow)
664  {
665  config->writeEntry(TQString::fromLatin1("EventID"), mEventID);
666  config->writeEntry(TQString::fromLatin1("AlarmType"), mAlarmType);
667  config->writeEntry(TQString::fromLatin1("Message"), mMessage);
668  config->writeEntry(TQString::fromLatin1("Type"), mAction);
669  config->writeEntry(TQString::fromLatin1("Font"), mFont);
670  config->writeEntry(TQString::fromLatin1("BgColour"), mBgColour);
671  config->writeEntry(TQString::fromLatin1("FgColour"), mFgColour);
672  config->writeEntry(TQString::fromLatin1("ConfirmAck"), mConfirmAck);
673  if (mDateTime.isValid())
674  {
675  config->writeEntry(TQString::fromLatin1("Time"), mDateTime.dateTime());
676  config->writeEntry(TQString::fromLatin1("DateOnly"), mDateTime.isDateOnly());
677  }
678  if (mCloseTime.isValid())
679  config->writeEntry(TQString::fromLatin1("Expiry"), mCloseTime);
680 #ifndef WITHOUT_ARTS
681  if (mAudioRepeat && mSilenceButton && mSilenceButton->isEnabled())
682  {
683  // Only need to restart sound file playing if it's being repeated
684  config->writePathEntry(TQString::fromLatin1("AudioFile"), mAudioFile);
685  config->writeEntry(TQString::fromLatin1("Volume"), static_cast<int>(mVolume * 100));
686  }
687 #endif
688  config->writeEntry(TQString::fromLatin1("Speak"), mSpeak);
689  config->writeEntry(TQString::fromLatin1("Height"), height());
690  config->writeEntry(TQString::fromLatin1("DeferMins"), mDefaultDeferMinutes);
691  config->writeEntry(TQString::fromLatin1("NoDefer"), mNoDefer);
692  config->writeEntry(TQString::fromLatin1("NoPostAction"), mNoPostAction);
693  config->writeEntry(TQString::fromLatin1("KMailSerial"), mKMailSerialNumber);
694  }
695  else
696  config->writeEntry(TQString::fromLatin1("Invalid"), true);
697 }
698 
699 /******************************************************************************
700 * Read settings from the session managed config file.
701 * This function is automatically called whenever the app is being restored.
702 * Read in whatever was saved in saveProperties().
703 */
704 void MessageWin::readProperties(TDEConfig* config)
705 {
706  mInvalid = config->readBoolEntry(TQString::fromLatin1("Invalid"), false);
707  mEventID = config->readEntry(TQString::fromLatin1("EventID"));
708  mAlarmType = KAAlarm::Type(config->readNumEntry(TQString::fromLatin1("AlarmType")));
709  mMessage = config->readEntry(TQString::fromLatin1("Message"));
710  mAction = KAEvent::Action(config->readNumEntry(TQString::fromLatin1("Type")));
711  mFont = config->readFontEntry(TQString::fromLatin1("Font"));
712  mBgColour = config->readColorEntry(TQString::fromLatin1("BgColour"));
713  mFgColour = config->readColorEntry(TQString::fromLatin1("FgColour"));
714  mConfirmAck = config->readBoolEntry(TQString::fromLatin1("ConfirmAck"));
715  TQDateTime invalidDateTime;
716  TQDateTime dt = config->readDateTimeEntry(TQString::fromLatin1("Time"), &invalidDateTime);
717  bool dateOnly = config->readBoolEntry(TQString::fromLatin1("DateOnly"));
718  mDateTime.set(dt, dateOnly);
719  mCloseTime = config->readDateTimeEntry(TQString::fromLatin1("Expiry"), &invalidDateTime);
720 #ifndef WITHOUT_ARTS
721  mAudioFile = config->readPathEntry(TQString::fromLatin1("AudioFile"));
722  mVolume = static_cast<float>(config->readNumEntry(TQString::fromLatin1("Volume"))) / 100;
723  mFadeVolume = -1;
724  mFadeSeconds = 0;
725  if (!mAudioFile.isEmpty())
726  mAudioRepeat = true;
727 #endif
728  mSpeak = config->readBoolEntry(TQString::fromLatin1("Speak"));
729  mRestoreHeight = config->readNumEntry(TQString::fromLatin1("Height"));
730  mDefaultDeferMinutes = config->readNumEntry(TQString::fromLatin1("DeferMins"));
731  mNoDefer = config->readBoolEntry(TQString::fromLatin1("NoDefer"));
732  mNoPostAction = config->readBoolEntry(TQString::fromLatin1("NoPostAction"));
733  mKMailSerialNumber = config->readUnsignedLongNumEntry(TQString::fromLatin1("KMailSerial"));
734  mShowEdit = false;
735  kdDebug(5950) << "MessageWin::readProperties(" << mEventID << ")" << endl;
736  if (mAlarmType != KAAlarm::INVALID_ALARM)
737  {
738  // Recreate the event from the calendar file (if possible)
739  if (!mEventID.isEmpty())
740  {
741  const Event* kcalEvent = AlarmCalendar::activeCalendar()->event(mEventID);
742  if (!kcalEvent)
743  {
744  // It's not in the active calendar, so try the displaying calendar
745  AlarmCalendar* cal = AlarmCalendar::displayCalendar();
746  if (cal->isOpen())
747  kcalEvent = cal->event(KAEvent::uid(mEventID, KAEvent::DISPLAYING));
748  }
749  if (kcalEvent)
750  {
751  mEvent.set(*kcalEvent);
752  mEvent.setUid(KAEvent::ACTIVE); // in case it came from the display calendar
753  mShowEdit = true;
754  }
755  }
756  initView();
757  }
758 }
759 
760 /******************************************************************************
761 * Returns the existing message window (if any) which is displaying the event
762 * with the specified ID.
763 */
764 MessageWin* MessageWin::findEvent(const TQString& eventID)
765 {
766  for (TQValueList<MessageWin*>::Iterator it = mWindowList.begin(); it != mWindowList.end(); ++it)
767  {
768  MessageWin* w = *it;
769  if (w->mEventID == eventID && !w->mErrorWindow)
770  return w;
771  }
772  return 0;
773 }
774 
775 /******************************************************************************
776 * Beep and play the audio file, as appropriate.
777 */
778 void MessageWin::playAudio()
779 {
780  if (mBeep)
781  {
782  // Beep using two methods, in case the sound card/speakers are switched off or not working
783  KNotifyClient::beep(); // beep through the sound card & speakers
784  TQApplication::beep(); // beep through the internal speaker
785  }
786  if (!mAudioFile.isEmpty())
787  {
788  if (!mVolume && mFadeVolume <= 0)
789  return; // ensure zero volume doesn't play anything
790 #ifdef WITHOUT_ARTS
791  TQString play = mAudioFile;
792  TQString file = TQString::fromLatin1("file:");
793  if (mAudioFile.startsWith(file))
794  play = mAudioFile.mid(file.length());
795  KAudioPlayer::play(TQFile::encodeName(play));
796 #else
797  // An audio file is specified. Because loading it may take some time,
798  // call it on a timer to allow the window to display first.
799  TQTimer::singleShot(0, this, TQ_SLOT(slotPlayAudio()));
800 #endif
801  }
802  else if (mSpeak)
803  {
804  // The message is to be spoken. In case of error messges,
805  // call it on a timer to allow the window to display first.
806  TQTimer::singleShot(0, this, TQ_SLOT(slotSpeak()));
807  }
808 }
809 
810 /******************************************************************************
811 * Speak the message.
812 * Called asynchronously to avoid delaying the display of the message.
813 */
814 void MessageWin::slotSpeak()
815 {
816  DCOPClient* client = kapp->dcopClient();
817  if (!client->isApplicationRegistered("kttsd"))
818  {
819  // kttsd is not running, so start it
820  TQString error;
821  if (kapp->startServiceByDesktopName("kttsd", TQStringList(), &error))
822  {
823  kdDebug(5950) << "MessageWin::slotSpeak(): failed to start kttsd: " << error << endl;
824  if (!haveErrorMessage(ErrMsg_Speak))
825  {
826  KMessageBox::detailedError(0, i18n("Unable to speak message"), error);
827  clearErrorMessage(ErrMsg_Speak);
828  }
829  return;
830  }
831  }
832  TQByteArray data;
833  TQDataStream arg(data, IO_WriteOnly);
834  arg << mMessage << "";
835  if (!client->send("kttsd", "KSpeech", "sayMessage(TQString,TQString)", data))
836  {
837  kdDebug(5950) << "MessageWin::slotSpeak(): sayMessage() DCOP error" << endl;
838  if (!haveErrorMessage(ErrMsg_Speak))
839  {
840  KMessageBox::detailedError(0, i18n("Unable to speak message"), i18n("DCOP Call sayMessage failed"));
841  clearErrorMessage(ErrMsg_Speak);
842  }
843  }
844 }
845 
846 /******************************************************************************
847 * Play the audio file.
848 * Called asynchronously to avoid delaying the display of the message.
849 */
850 void MessageWin::slotPlayAudio()
851 {
852 #ifndef WITHOUT_ARTS
853  // First check that it exists, to avoid possible crashes if the filename is badly specified
854  MainWindow* mmw = MainWindow::mainMainWindow();
855  KURL url(mAudioFile);
856  if (!url.isValid() || !TDEIO::NetAccess::exists(url, true, mmw)
857  || !TDEIO::NetAccess::download(url, mLocalAudioFile, mmw))
858  {
859  kdError(5950) << "MessageWin::playAudio(): Open failure: " << mAudioFile << endl;
860  if (!haveErrorMessage(ErrMsg_AudioFile))
861  {
862  KMessageBox::error(this, i18n("Cannot open audio file:\n%1").arg(mAudioFile));
863  clearErrorMessage(ErrMsg_AudioFile);
864  }
865  return;
866  }
867  if (!mArtsDispatcher)
868  {
869  mFadeTimer = 0;
870  mPlayTimer = new TQTimer(this);
871  connect(mPlayTimer, TQ_SIGNAL(timeout()), TQ_SLOT(checkAudioPlay()));
872  mArtsDispatcher = new KArtsDispatcher;
873  mPlayedOnce = false;
874  mAudioFileStart = TQTime::currentTime();
875  initAudio(true);
876  if (!mPlayObject->object().isNull())
877  checkAudioPlay();
878  if (!mUsingKMix && mVolume >= 0)
879  {
880  // Output error message now that everything else has been done.
881  // (Outputting it earlier would delay things until it is acknowledged.)
882  kdWarning(5950) << "Unable to set master volume (KMix: " << mKMixError << ")\n";
883  if (!haveErrorMessage(ErrMsg_Volume))
884  {
885  KMessageBox::information(this, i18n("Unable to set master volume\n(Error accessing KMix:\n%1)").arg(mKMixError),
886  TQString(), TQString::fromLatin1("KMixError"));
887  clearErrorMessage(ErrMsg_Volume);
888  }
889  }
890  }
891 #endif
892 }
893 
894 #ifndef WITHOUT_ARTS
895 /******************************************************************************
896 * Set up the audio file for playing.
897 */
898 void MessageWin::initAudio(bool firstTime)
899 {
900  KArtsServer aserver;
901  Arts::SoundServerV2 sserver = aserver.server();
902  KDE::PlayObjectFactory factory(sserver);
903  mPlayObject = factory.createPlayObject(mLocalAudioFile, true);
904  if (firstTime)
905  {
906  // Save the existing sound volume setting for restoration afterwards,
907  // and set the desired volume for the alarm.
908  mUsingKMix = false;
909  float volume = mVolume; // initial volume
910  if (volume >= 0)
911  {
912  // The volume has been specified
913  if (mFadeVolume >= 0)
914  volume = mFadeVolume; // fading, so adjust the initial volume
915 
916  // Get the current master volume from KMix
917  int vol = getKMixVolume();
918  if (vol >= 0)
919  {
920  mOldVolume = vol; // success
921  mUsingKMix = true;
922  setKMixVolume(static_cast<int>(volume * 100));
923  }
924  }
925  if (!mUsingKMix)
926  {
927  /* Adjust within the current master volume, because either
928  * a) the volume is not specified, in which case we want to play
929  * at 100% of the current master volume setting, or
930  * b) KMix is not available to set the master volume.
931  */
932  mOldVolume = sserver.outVolume().scaleFactor(); // save volume for restoration afterwards
933  sserver.outVolume().scaleFactor(volume >= 0 ? volume : 1);
934  }
935  }
936  mSilenceButton->setEnabled(true);
937  mPlayed = false;
938  connect(mPlayObject, TQ_SIGNAL(playObjectCreated()), TQ_SLOT(checkAudioPlay()));
939  if (!mPlayObject->object().isNull())
940  checkAudioPlay();
941 }
942 #endif
943 
944 /******************************************************************************
945 * Called when the audio file has loaded and is ready to play, or on a timer
946 * when play is expected to have completed.
947 * If it is ready to play, start playing it (for the first time or repeated).
948 * If play has not yet completed, wait a bit longer.
949 */
950 void MessageWin::checkAudioPlay()
951 {
952 #ifndef WITHOUT_ARTS
953  if (!mPlayObject)
954  return;
955  if (mPlayObject->state() == Arts::posIdle)
956  {
957  // The file has loaded and is ready to play, or play has completed
958  if (mPlayedOnce && !mAudioRepeat)
959  {
960  // Play has completed
961  stopPlay();
962  return;
963  }
964 
965  // Start playing the file, either for the first time or again
966  kdDebug(5950) << "MessageWin::checkAudioPlay(): start\n";
967  if (!mPlayedOnce)
968  {
969  // Start playing the file for the first time
970  TQTime now = TQTime::currentTime();
971  mAudioFileLoadSecs = mAudioFileStart.secsTo(now);
972  if (mAudioFileLoadSecs < 0)
973  mAudioFileLoadSecs += 86400;
974  if (mVolume >= 0 && mFadeVolume >= 0 && mFadeSeconds > 0)
975  {
976  // Set up volume fade
977  mAudioFileStart = now;
978  mFadeTimer = new TQTimer(this);
979  connect(mFadeTimer, TQ_SIGNAL(timeout()), TQ_SLOT(slotFade()));
980  mFadeTimer->start(1000); // adjust volume every second
981  }
982  mPlayedOnce = true;
983  }
984  if (mAudioFileLoadSecs < 3)
985  {
986  /* The aRts library takes several attempts before a PlayObject can
987  * be replayed, leaving a gap of perhaps 5 seconds between plays.
988  * So if loading the file takes a short time, it's better to reload
989  * the PlayObject rather than try to replay the same PlayObject.
990  */
991  if (mPlayed)
992  {
993  // Playing has completed. Start playing again.
994  delete mPlayObject;
995  initAudio(false);
996  if (mPlayObject->object().isNull())
997  return;
998  }
999  mPlayed = true;
1000  mPlayObject->play();
1001  }
1002  else
1003  {
1004  // The file is slow to load, so attempt to replay the PlayObject
1005  static Arts::poTime t0((long)0, (long)0, 0, std::string());
1006  Arts::poTime current = mPlayObject->currentTime();
1007  if (current.seconds || current.ms)
1008  mPlayObject->seek(t0);
1009  else
1010  mPlayObject->play();
1011  }
1012  }
1013 
1014  // The sound file is still playing
1015  Arts::poTime overall = mPlayObject->overallTime();
1016  Arts::poTime current = mPlayObject->currentTime();
1017  int time = 1000*(overall.seconds - current.seconds) + overall.ms - current.ms;
1018  if (time < 0)
1019  time = 0;
1020  kdDebug(5950) << "MessageWin::checkAudioPlay(): wait for " << (time+100) << "ms\n";
1021  mPlayTimer->start(time + 100, true);
1022 #endif
1023 }
1024 
1025 /******************************************************************************
1026 * Called when play completes, the Silence button is clicked, or the window is
1027 * closed, to reset the sound volume and terminate audio access.
1028 */
1029 void MessageWin::stopPlay()
1030 {
1031 #ifndef WITHOUT_ARTS
1032  if (mArtsDispatcher)
1033  {
1034  // Restore the sound volume to what it was before the sound file
1035  // was played, provided that nothing else has modified it since.
1036  if (!mUsingKMix)
1037  {
1038  KArtsServer aserver;
1039  Arts::StereoVolumeControl svc = aserver.server().outVolume();
1040  float currentVolume = svc.scaleFactor();
1041  float eventVolume = mVolume;
1042  if (eventVolume < 0)
1043  eventVolume = 1;
1044  if (currentVolume == eventVolume)
1045  svc.scaleFactor(mOldVolume);
1046  }
1047  else if (mVolume >= 0)
1048  {
1049  int eventVolume = static_cast<int>(mVolume * 100);
1050  int currentVolume = getKMixVolume();
1051  // Volume returned isn't always exactly equal to volume set
1052  if (currentVolume < 0 || abs(currentVolume - eventVolume) < 5)
1053  setKMixVolume(static_cast<int>(mOldVolume));
1054  }
1055  }
1056  delete mPlayObject; mPlayObject = 0;
1057  delete mArtsDispatcher; mArtsDispatcher = 0;
1058  if (!mLocalAudioFile.isEmpty())
1059  {
1060  TDEIO::NetAccess::removeTempFile(mLocalAudioFile); // removes it only if it IS a temporary file
1061  mLocalAudioFile = TQString();
1062  }
1063  if (mSilenceButton)
1064  mSilenceButton->setEnabled(false);
1065 #endif
1066 }
1067 
1068 /******************************************************************************
1069 * Called every second to fade the volume when the audio file starts playing.
1070 */
1071 void MessageWin::slotFade()
1072 {
1073 #ifndef WITHOUT_ARTS
1074  TQTime now = TQTime::currentTime();
1075  int elapsed = mAudioFileStart.secsTo(now);
1076  if (elapsed < 0)
1077  elapsed += 86400; // it's the next day
1078  float volume;
1079  if (elapsed >= mFadeSeconds)
1080  {
1081  // The fade has finished. Set to normal volume.
1082  volume = mVolume;
1083  delete mFadeTimer;
1084  mFadeTimer = 0;
1085  if (!mVolume)
1086  {
1087  kdDebug(5950) << "MessageWin::slotFade(0)\n";
1088  stopPlay();
1089  return;
1090  }
1091  }
1092  else
1093  volume = mFadeVolume + ((mVolume - mFadeVolume) * elapsed) / mFadeSeconds;
1094  kdDebug(5950) << "MessageWin::slotFade(" << volume << ")\n";
1095  if (mArtsDispatcher)
1096  {
1097  if (mUsingKMix)
1098  setKMixVolume(static_cast<int>(volume * 100));
1099  else if (mArtsDispatcher)
1100  {
1101  KArtsServer aserver;
1102  aserver.server().outVolume().scaleFactor(volume);
1103  }
1104  }
1105 #endif
1106 }
1107 
1108 #ifndef WITHOUT_ARTS
1109 /******************************************************************************
1110 * Get the master volume from KMix.
1111 * Reply < 0 if failure.
1112 */
1113 int MessageWin::getKMixVolume()
1114 {
1115  if (!KAlarm::runProgram(KMIX_APP_NAME, KMIX_DCOP_WINDOW, mKMixName, mKMixError)) // start KMix if it isn't already running
1116  return -1;
1117  TQByteArray data, replyData;
1118  TQCString replyType;
1119  TQDataStream arg(data, IO_WriteOnly);
1120  if (!kapp->dcopClient()->call(mKMixName, KMIX_DCOP_OBJECT, "masterVolume()", data, replyType, replyData)
1121  || replyType != "int")
1122  return -1;
1123  int result;
1124  TQDataStream reply(replyData, IO_ReadOnly);
1125  reply >> result;
1126  return (result >= 0) ? result : 0;
1127 }
1128 
1129 /******************************************************************************
1130 * Set the master volume using KMix.
1131 */
1132 void MessageWin::setKMixVolume(int percent)
1133 {
1134  if (!mUsingKMix)
1135  return;
1136  if (!KAlarm::runProgram(KMIX_APP_NAME, KMIX_DCOP_WINDOW, mKMixName, mKMixError)) // start KMix if it isn't already running
1137  return;
1138  TQByteArray data;
1139  TQDataStream arg(data, IO_WriteOnly);
1140  arg << percent;
1141  if (!kapp->dcopClient()->send(mKMixName, KMIX_DCOP_OBJECT, "setMasterVolume(int)", data))
1142  kdError(5950) << "MessageWin::setKMixVolume(): kmix dcop call failed\n";
1143 }
1144 #endif
1145 
1146 /******************************************************************************
1147 * Raise the alarm window, re-output any required audio notification, and
1148 * reschedule the alarm in the calendar file.
1149 */
1150 void MessageWin::repeat(const KAAlarm& alarm)
1151 {
1152  if (mDeferDlg)
1153  {
1154  // Cancel any deferral dialogue so that the user notices something's going on,
1155  // and also because the deferral time limit will have changed.
1156  delete mDeferDlg;
1157  mDeferDlg = 0;
1158  }
1159  const Event* kcalEvent = mEventID.isNull() ? 0 : AlarmCalendar::activeCalendar()->event(mEventID);
1160  if (kcalEvent)
1161  {
1162  mAlarmType = alarm.type(); // store new alarm type for use if it is later deferred
1163  if (!mDeferDlg || Preferences::modalMessages())
1164  {
1165  raise();
1166  playAudio();
1167  }
1168  KAEvent event(*kcalEvent);
1169  mDeferButton->setEnabled(true);
1170  setDeferralLimit(event); // ensure that button is disabled when alarm can't be deferred any more
1171  theApp()->alarmShowing(event, mAlarmType, mDateTime);
1172  }
1173 }
1174 
1175 /******************************************************************************
1176 * Display the window.
1177 * If windows are being positioned away from the mouse cursor, it is initially
1178 * positioned at the top left to slightly reduce the number of times the
1179 * windows need to be moved in showEvent().
1180 */
1181 void MessageWin::show()
1182 {
1183  if (mCloseTime.isValid())
1184  {
1185  // Set a timer to auto-close the window
1186  int delay = TQDateTime::currentDateTime().secsTo(mCloseTime);
1187  if (delay < 0)
1188  delay = 0;
1189  TQTimer::singleShot(delay * 1000, this, TQ_SLOT(close()));
1190  if (!delay)
1191  return; // don't show the window if auto-closing is already due
1192  }
1193  if (Preferences::messageButtonDelay() == 0)
1194  move(0, 0);
1195  MainWindowBase::show();
1196 }
1197 
1198 /******************************************************************************
1199 * Returns the window's recommended size exclusive of its frame.
1200 * For message windows, the size if limited to fit inside the working area of
1201 * the desktop.
1202 */
1203 TQSize MessageWin::sizeHint() const
1204 {
1205  if (mAction != KAEvent::MESSAGE)
1206  return MainWindowBase::sizeHint();
1207  if (!mWinModule)
1208  mWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP);
1209  TQSize frame = frameGeometry().size();
1210  TQSize contents = geometry().size();
1211  TQSize desktop = mWinModule->workArea().size();
1212  TQSize maxSize(desktop.width() - (frame.width() - contents.width()),
1213  desktop.height() - (frame.height() - contents.height()));
1214  return MainWindowBase::sizeHint().boundedTo(maxSize);
1215 }
1216 
1217 /******************************************************************************
1218 * Called when the window is shown.
1219 * The first time, output any required audio notification, and reschedule or
1220 * delete the event from the calendar file.
1221 */
1222 void MessageWin::showEvent(TQShowEvent* se)
1223 {
1224  MainWindowBase::showEvent(se);
1225  if (!mShown)
1226  {
1227  if (mErrorWindow)
1228  enableButtons(); // don't bother repositioning error messages
1229  else
1230  {
1231  /* Set the window size.
1232  * Note that the frame thickness is not yet known when this
1233  * method is called, so for large windows the size needs to be
1234  * set again later.
1235  */
1236  TQSize s = sizeHint(); // fit the window round the message
1237  if (mAction == KAEvent::FILE && !mErrorMsgs.count())
1238  KAlarm::readConfigWindowSize("FileMessage", s);
1239  resize(s);
1240 
1241  mButtonDelay = Preferences::messageButtonDelay() * 1000;
1242  if (!mButtonDelay)
1243  {
1244  /* Try to ensure that the window can't accidentally be acknowledged
1245  * by the user clicking the mouse just as it appears.
1246  * To achieve this, move the window so that the OK button is as far away
1247  * from the cursor as possible. If the buttons are still too close to the
1248  * cursor, disable the buttons for a short time.
1249  * N.B. This can't be done in show(), since the geometry of the window
1250  * is not known until it is displayed. Unfortunately by moving the
1251  * window in showEvent(), a flicker is unavoidable.
1252  * See the TQt documentation on window geometry for more details.
1253  */
1254  // PROBLEM: The frame size is not known yet!
1255 
1256  /* Find the usable area of the desktop or, if the desktop comprises
1257  * multiple screens, the usable area of the current screen. (If the
1258  * message is displayed on a screen other than that currently being
1259  * worked with, it might not be noticed.)
1260  */
1261  TQPoint cursor = TQCursor::pos();
1262  if (!mWinModule)
1263  mWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP);
1264  TQRect desk = mWinModule->workArea();
1265  TQDesktopWidget* dw = TQApplication::desktop();
1266  if (dw->numScreens() > 1)
1267  desk &= dw->screenGeometry(dw->screenNumber(cursor));
1268 
1269  TQRect frame = frameGeometry();
1270  TQRect rect = geometry();
1271  // Find the offsets from the outside of the frame to the edges of the OK button
1272  TQRect button(mOkButton->mapToParent(TQPoint(0, 0)), mOkButton->mapToParent(mOkButton->rect().bottomRight()));
1273  int buttonLeft = button.left() + rect.left() - frame.left();
1274  int buttonRight = width() - button.right() + frame.right() - rect.right();
1275  int buttonTop = button.top() + rect.top() - frame.top();
1276  int buttonBottom = height() - button.bottom() + frame.bottom() - rect.bottom();
1277 
1278  int centrex = (desk.width() + buttonLeft - buttonRight) / 2;
1279  int centrey = (desk.height() + buttonTop - buttonBottom) / 2;
1280  int x = (cursor.x() < centrex) ? desk.right() - frame.width() : desk.left();
1281  int y = (cursor.y() < centrey) ? desk.bottom() - frame.height() : desk.top();
1282 
1283  // Find the enclosing rectangle for the new button positions
1284  // and check if the cursor is too near
1285  TQRect buttons = mOkButton->geometry().unite(mKAlarmButton->geometry());
1286  buttons.moveBy(rect.left() + x - frame.left(), rect.top() + y - frame.top());
1287  int minDistance = proximityMultiple * mOkButton->height();
1288  if ((abs(cursor.x() - buttons.left()) < minDistance
1289  || abs(cursor.x() - buttons.right()) < minDistance)
1290  && (abs(cursor.y() - buttons.top()) < minDistance
1291  || abs(cursor.y() - buttons.bottom()) < minDistance))
1292  mButtonDelay = proximityButtonDelay; // too near - disable buttons initially
1293 
1294  if (x != frame.left() || y != frame.top())
1295  {
1296  mPositioning = true;
1297  move(x, y);
1298  }
1299  }
1300  if (!mPositioning)
1301  displayComplete(); // play audio, etc.
1302  if (mAction == KAEvent::MESSAGE)
1303  {
1304  // Set the window size once the frame size is known
1305  TQTimer::singleShot(0, this, TQ_SLOT(setMaxSize()));
1306  }
1307  }
1308  mShown = true;
1309  }
1310 }
1311 
1312 /******************************************************************************
1313 * Called when the window has been moved.
1314 */
1315 void MessageWin::moveEvent(TQMoveEvent* e)
1316 {
1317  MainWindowBase::moveEvent(e);
1318  if (mPositioning)
1319  {
1320  // The window has just been initially positioned
1321  mPositioning = false;
1322  displayComplete(); // play audio, etc.
1323  }
1324 }
1325 
1326 /******************************************************************************
1327 * Reset the iniital window size if it exceeds the working area of the desktop.
1328 */
1329 void MessageWin::setMaxSize()
1330 {
1331  TQSize s = sizeHint();
1332  if (width() > s.width() || height() > s.height())
1333  resize(s);
1334 }
1335 
1336 /******************************************************************************
1337 * Called when the window has been displayed properly (in its correct position),
1338 * to play sounds and reschedule the event.
1339 */
1340 void MessageWin::displayComplete()
1341 {
1342  playAudio();
1343  if (mRescheduleEvent)
1344  theApp()->alarmShowing(mEvent, mAlarmType, mDateTime);
1345 
1346  // Enable the window's buttons either now or after the configured delay
1347  if (mButtonDelay > 0)
1348  TQTimer::singleShot(mButtonDelay, this, TQ_SLOT(enableButtons()));
1349  else
1350  enableButtons();
1351 }
1352 
1353 /******************************************************************************
1354 * Enable the window's buttons.
1355 */
1356 void MessageWin::enableButtons()
1357 {
1358  mOkButton->setEnabled(true);
1359  mKAlarmButton->setEnabled(true);
1360  if (mDeferButton && !mDisableDeferral)
1361  mDeferButton->setEnabled(true);
1362  if (mEditButton)
1363  mEditButton->setEnabled(true);
1364  if (mKMailButton)
1365  mKMailButton->setEnabled(true);
1366 }
1367 
1368 /******************************************************************************
1369 * Called when the window's size has changed (before it is painted).
1370 */
1371 void MessageWin::resizeEvent(TQResizeEvent* re)
1372 {
1373  if (mRestoreHeight)
1374  {
1375  // Restore the window height on session restoration
1376  if (mRestoreHeight != re->size().height())
1377  {
1378  TQSize size = re->size();
1379  size.setHeight(mRestoreHeight);
1380  resize(size);
1381  }
1382  else if (isVisible())
1383  mRestoreHeight = 0;
1384  }
1385  else
1386  {
1387  if (mShown && mAction == KAEvent::FILE && !mErrorMsgs.count())
1388  KAlarm::writeConfigWindowSize("FileMessage", re->size());
1389  MainWindowBase::resizeEvent(re);
1390  }
1391 }
1392 
1393 /******************************************************************************
1394 * Called when a close event is received.
1395 * Only quits the application if there is no system tray icon displayed.
1396 */
1397 void MessageWin::closeEvent(TQCloseEvent* ce)
1398 {
1399  // Don't prompt or delete the alarm from the display calendar if the session is closing
1400  if (!mErrorWindow && !theApp()->sessionClosingDown())
1401  {
1402  if (mConfirmAck && !mNoCloseConfirm)
1403  {
1404  // Ask for confirmation of acknowledgement. Use warningYesNo() because its default is No.
1405  if (KMessageBox::warningYesNo(this, i18n("Do you really want to acknowledge this alarm?"),
1406  i18n("Acknowledge Alarm"), i18n("&Acknowledge"), KStdGuiItem::cancel())
1407  != KMessageBox::Yes)
1408  {
1409  ce->ignore();
1410  return;
1411  }
1412  }
1413  if (!mEventID.isNull())
1414  {
1415  // Delete from the display calendar
1416  KAlarm::deleteDisplayEvent(KAEvent::uid(mEventID, KAEvent::DISPLAYING));
1417  }
1418  }
1419  MainWindowBase::closeEvent(ce);
1420 }
1421 
1422 /******************************************************************************
1423 * Called when the KMail button is clicked.
1424 * Tells KMail to display the email message displayed in this message window.
1425 */
1426 void MessageWin::slotShowKMailMessage()
1427 {
1428  kdDebug(5950) << "MessageWin::slotShowKMailMessage()\n";
1429  if (!mKMailSerialNumber)
1430  return;
1431  TQString err = KAlarm::runKMail(false);
1432  if (!err.isNull())
1433  {
1434  KMessageBox::sorry(this, err);
1435  return;
1436  }
1437  TQCString replyType;
1438  TQByteArray data, replyData;
1439  TQDataStream arg(data, IO_WriteOnly);
1440  arg << (TQ_UINT32)mKMailSerialNumber << TQString();
1441  if (kapp->dcopClient()->call("kmail", KMAIL_DCOP_OBJECT, "showMail(TQ_UINT32,TQString)", data, replyType, replyData)
1442  && replyType == "bool")
1443  {
1444  bool result;
1445  TQDataStream replyStream(replyData, IO_ReadOnly);
1446  replyStream >> result;
1447  if (result)
1448  return; // success
1449  }
1450  kdError(5950) << "MessageWin::slotShowKMailMessage(): kmail dcop call failed\n";
1451  KMessageBox::sorry(this, i18n("Unable to locate this email in KMail"));
1452 }
1453 
1454 /******************************************************************************
1455 * Called when the Edit... button is clicked.
1456 * Displays the alarm edit dialog.
1457 */
1458 void MessageWin::slotEdit()
1459 {
1460  kdDebug(5950) << "MessageWin::slotEdit()" << endl;
1461  EditAlarmDlg editDlg(false, i18n("Edit Alarm"), this, "editDlg", &mEvent);
1462  if (editDlg.exec() == TQDialog::Accepted)
1463  {
1464  KAEvent event;
1465  editDlg.getEvent(event);
1466 
1467  // Update the displayed lists and the calendar file
1468  KAlarm::UpdateStatus status;
1469  if (AlarmCalendar::activeCalendar()->event(mEventID))
1470  {
1471  // The old alarm hasn't expired yet, so replace it
1472  status = KAlarm::modifyEvent(mEvent, event, 0, &editDlg);
1473  Undo::saveEdit(mEvent, event);
1474  }
1475  else
1476  {
1477  // The old event has expired, so simply create a new one
1478  status = KAlarm::addEvent(event, 0, &editDlg);
1479  Undo::saveAdd(event);
1480  }
1481 
1482  if (status == KAlarm::UPDATE_KORG_ERR)
1483  KAlarm::displayKOrgUpdateError(&editDlg, KAlarm::KORG_ERR_MODIFY, 1);
1484  KAlarm::outputAlarmWarnings(&editDlg, &event);
1485 
1486  // Close the alarm window
1487  mNoCloseConfirm = true; // allow window to close without confirmation prompt
1488  close();
1489  }
1490 }
1491 
1492 /******************************************************************************
1493 * Set up to disable the defer button when the deferral limit is reached.
1494 */
1495 void MessageWin::setDeferralLimit(const KAEvent& event)
1496 {
1497  if (mDeferButton)
1498  {
1499  mDeferLimit = event.deferralLimit().dateTime();
1500  MidnightTimer::connect(this, TQ_SLOT(checkDeferralLimit())); // check every day
1501  mDisableDeferral = false;
1502  checkDeferralLimit();
1503  }
1504 }
1505 
1506 /******************************************************************************
1507 * Check whether the deferral limit has been reached.
1508 * If so, disable the Defer button.
1509 * N.B. Ideally, just a single TQTimer::singleShot() call would be made to disable
1510 * the defer button at the corret time. But for a 32-bit integer, the
1511 * milliseconds parameter overflows in about 25 days, so instead a daily
1512 * check is done until the day when the deferral limit is reached, followed
1513 * by a non-overflowing TQTimer::singleShot() call.
1514 */
1515 void MessageWin::checkDeferralLimit()
1516 {
1517  if (!mDeferButton || !mDeferLimit.isValid())
1518  return;
1519  int n = TQDate::currentDate().daysTo(mDeferLimit.date());
1520  if (n > 0)
1521  return;
1522  MidnightTimer::disconnect(this, TQ_SLOT(checkDeferralLimit()));
1523  if (n == 0)
1524  {
1525  // The deferral limit will be reached today
1526  n = TQTime::currentTime().secsTo(mDeferLimit.time());
1527  if (n > 0)
1528  {
1529  TQTimer::singleShot(n * 1000, this, TQ_SLOT(checkDeferralLimit()));
1530  return;
1531  }
1532  }
1533  mDeferButton->setEnabled(false);
1534  mDisableDeferral = true;
1535 }
1536 
1537 /******************************************************************************
1538 * Called when the Defer... button is clicked.
1539 * Displays the defer message dialog.
1540 */
1541 void MessageWin::slotDefer()
1542 {
1543  mDeferDlg = new DeferAlarmDlg(i18n("Defer Alarm"), TQDateTime(TQDateTime::currentDateTime().addSecs(60)),
1544  false, this, "deferDlg");
1545  if (mDefaultDeferMinutes > 0)
1546  mDeferDlg->setDeferMinutes(mDefaultDeferMinutes);
1547  mDeferDlg->setLimit(mEventID);
1548  if (!Preferences::modalMessages())
1549  lower();
1550  if (mDeferDlg->exec() == TQDialog::Accepted)
1551  {
1552  DateTime dateTime = mDeferDlg->getDateTime();
1553  int delayMins = mDeferDlg->deferMinutes();
1554  const Event* kcalEvent = mEventID.isNull() ? 0 : AlarmCalendar::activeCalendar()->event(mEventID);
1555  if (kcalEvent)
1556  {
1557  // The event still exists in the calendar file.
1558  KAEvent event(*kcalEvent);
1559  bool repeat = event.defer(dateTime, (mAlarmType & KAAlarm::REMINDER_ALARM), true);
1560  event.setDeferDefaultMinutes(delayMins);
1561  KAlarm::updateEvent(event, 0, mDeferDlg, true, !repeat);
1562  if (event.deferred())
1563  mNoPostAction = true;
1564  }
1565  else
1566  {
1567  KAEvent event;
1568  kcalEvent = AlarmCalendar::displayCalendar()->event(KAEvent::uid(mEventID, KAEvent::DISPLAYING));
1569  if (kcalEvent)
1570  {
1571  event.reinstateFromDisplaying(KAEvent(*kcalEvent));
1572  event.defer(dateTime, (mAlarmType & KAAlarm::REMINDER_ALARM), true);
1573  }
1574  else
1575  {
1576  // The event doesn't exist any more !?!, so create a new one
1577  event.set(dateTime.dateTime(), mMessage, mBgColour, mFgColour, mFont, mAction, mLateCancel, mFlags);
1578  event.setAudioFile(mAudioFile, mVolume, mFadeVolume, mFadeSeconds);
1579  event.setArchive();
1580  event.setEventID(mEventID);
1581  }
1582  event.setDeferDefaultMinutes(delayMins);
1583  // Add the event back into the calendar file, retaining its ID
1584  // and not updating KOrganizer
1585  KAlarm::addEvent(event, 0, mDeferDlg, true, false);
1586  if (event.deferred())
1587  mNoPostAction = true;
1588  if (kcalEvent)
1589  {
1590  event.setUid(KAEvent::EXPIRED);
1591  KAlarm::deleteEvent(event, false);
1592  }
1593  }
1594  if (theApp()->wantRunInSystemTray())
1595  {
1596  // Alarms are to be displayed only if the system tray icon is running,
1597  // so start it if necessary so that the deferred alarm will be shown.
1598  theApp()->displayTrayIcon(true);
1599  }
1600  mNoCloseConfirm = true; // allow window to close without confirmation prompt
1601  close();
1602  }
1603  else
1604  raise();
1605  delete mDeferDlg;
1606  mDeferDlg = 0;
1607 }
1608 
1609 /******************************************************************************
1610 * Called when the KAlarm icon button in the message window is clicked.
1611 * Displays the main window, with the appropriate alarm selected.
1612 */
1613 void MessageWin::displayMainWindow()
1614 {
1615  KAlarm::displayMainWindowSelected(mEventID);
1616 }
1617 
1618 /******************************************************************************
1619 * Check whether the specified error message is already displayed for this
1620 * alarm, and note that it will now be displayed.
1621 * Reply = true if message is already displayed.
1622 */
1623 bool MessageWin::haveErrorMessage(unsigned msg) const
1624 {
1625  if (!mErrorMessages.contains(mEventID))
1626  mErrorMessages.insert(mEventID, 0);
1627  bool result = (mErrorMessages[mEventID] & msg);
1628  mErrorMessages[mEventID] |= msg;
1629  return result;
1630 }
1631 
1632 void MessageWin::clearErrorMessage(unsigned msg) const
1633 {
1634  if (mErrorMessages.contains(mEventID))
1635  {
1636  if (mErrorMessages[mEventID] == msg)
1637  mErrorMessages.remove(mEventID);
1638  else
1639  mErrorMessages[mEventID] &= ~msg;
1640  }
1641 }
1642 
1643 
1644 /******************************************************************************
1645 * Check whether the message window should be modal, i.e. with title bar etc.
1646 * Normally this follows the Preferences setting, but if there is a full screen
1647 * window displayed, on X11 the message window has to bypass the window manager
1648 * in order to display on top of it (which has the side effect that it will have
1649 * no window decoration).
1650 */
1651 bool wantModal()
1652 {
1653  bool modal = Preferences::modalMessages();
1654  if (modal)
1655  {
1656  KWinModule wm(0, KWinModule::INFO_DESKTOP);
1657  KWin::WindowInfo wi = KWin::windowInfo(wm.activeWindow(), NET::WMState);
1658  modal = !(wi.valid() && wi.hasState(NET::FullScreen));
1659  }
1660  return modal;
1661 }
1662 
1663 
1664 /*=============================================================================
1665 = Class MWMimeSourceFactory
1666 * Gets the mime type of a text file from not only its extension (as per
1667 * TQMimeSourceFactory), but also from its contents. This allows the detection
1668 * of plain text files without file name extensions.
1669 =============================================================================*/
1670 MWMimeSourceFactory::MWMimeSourceFactory(const TQString& absPath, KTextBrowser* view)
1671  : TQMimeSourceFactory(),
1672  mMimeType("text/plain"),
1673  mLast(0)
1674 {
1675  view->setMimeSourceFactory(this);
1676  TQString type = KMimeType::findByPath(absPath)->name();
1677  switch (KAlarm::fileType(type))
1678  {
1679  case KAlarm::TextPlain:
1680  case KAlarm::TextFormatted:
1681  mMimeType = type.latin1();
1682  // fall through to 'TextApplication'
1683  case KAlarm::TextApplication:
1684  default:
1685  // It's assumed to be a text file
1686  mTextFile = absPath;
1687  view->TQTextBrowser::setSource(absPath);
1688  break;
1689 
1690  case KAlarm::Image:
1691  // It's an image file
1692  TQString text = "<img source=\"";
1693  text += absPath;
1694  text += "\">";
1695  view->setText(text);
1696  break;
1697  }
1698  setFilePath(TQFileInfo(absPath).dirPath(true));
1699 }
1700 
1701 MWMimeSourceFactory::~MWMimeSourceFactory()
1702 {
1703  delete mLast;
1704 }
1705 
1706 const TQMimeSource* MWMimeSourceFactory::data(const TQString& abs_name) const
1707 {
1708  if (abs_name == mTextFile)
1709  {
1710  TQFileInfo fi(abs_name);
1711  if (fi.isReadable())
1712  {
1713  TQFile f(abs_name);
1714  if (f.open(IO_ReadOnly) && f.size())
1715  {
1716  TQByteArray ba(f.size());
1717  f.readBlock(ba.data(), ba.size());
1718  TQStoredDrag* sr = new TQStoredDrag(mMimeType);
1719  sr->setEncodedData(ba);
1720  delete mLast;
1721  mLast = sr;
1722  return sr;
1723  }
1724  }
1725  }
1726  return TQMimeSourceFactory::data(abs_name);
1727 }
Provides read and write access to calendar files.
Definition: alarmcalendar.h:37
KAEvent corresponds to a KCal::Event instance.
Definition: alarmevent.h:232
The MainWindowBase class is a base class for KAlarm's main window and message window.
MessageWin: A window to display an alarm message.
Definition: messagewin.h:45
miscellaneous functions
UpdateStatus
Return codes from calendar update functions.
Definition: functions.h:51
the KAlarm application object
main application window
bool view(TQWidget *parent, Attachment *attachment)