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 <tdestandarddirs.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
76using namespace KCal;
77
78#ifndef WITHOUT_ARTS
79static const char* KMIX_APP_NAME = "kmix";
80static const char* KMIX_DCOP_OBJECT = "Mixer0";
81static const char* KMIX_DCOP_WINDOW = "kmix-mainwindow#1";
82#endif
83static 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.
87static const int proximityButtonDelay = 1000; // (milliseconds)
88static const int proximityMultiple = 10; // multiple of button height distance from cursor for proximity
89
90static bool wantModal();
91
92// A text label widget which can be scrolled and copied with the mouse
93class 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
108class 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
126static const TQt::WFlags WFLAGS = TQt::WStyle_StaysOnTop | TQt::WDestructiveClose;
127
128// Error message bit masks
129enum {
130 ErrMsg_Speak = 0x01,
131 ErrMsg_AudioFile = 0x02,
132 ErrMsg_Volume = 0x04
133};
134
135
136TQValueList<MessageWin*> MessageWin::mWindowList;
137TQMap<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*/
146MessageWin::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*/
204MessageWin::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*/
244MessageWin::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*/
268MessageWin::~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*/
288void 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(tdeApp->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*/
615void 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*/
642void 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*/
661void 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*/
704void 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*/
764MessageWin* 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*/
778void 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*/
814void MessageWin::slotSpeak()
815{
816 DCOPClient* client = tdeApp->dcopClient();
817 if (!client->isApplicationRegistered("kttsd"))
818 {
819 // kttsd is not running, so start it
820 TQString error;
821 if (tdeApp->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*/
850void 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*/
898void 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*/
950void 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*/
1029void 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*/
1071void 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*/
1113int 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 (!tdeApp->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*/
1132void 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 (!tdeApp->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*/
1150void 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*/
1181void 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*/
1203TQSize 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*/
1222void 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*/
1315void 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*/
1329void 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*/
1340void 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*/
1356void 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*/
1371void 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*/
1397void 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*/
1426void 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 (tdeApp->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*/
1458void 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*/
1495void 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*/
1515void 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*/
1541void 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*/
1613void 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*/
1623bool 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
1632void 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*/
1651bool 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=============================================================================*/
1670MWMimeSourceFactory::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
1701MWMimeSourceFactory::~MWMimeSourceFactory()
1702{
1703 delete mLast;
1704}
1705
1706const 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)