messagewin.cpp
00001 /* 00002 * messagewin.cpp - displays an alarm message 00003 * Program: kalarm 00004 * Copyright © 2001-2009 by David Jarvie <djarvie@kde.org> 00005 * 00006 * This program is free software; you can redistribute it and/or modify 00007 * it under the terms of the GNU General Public License as published by 00008 * the Free Software Foundation; either version 2 of the License, or 00009 * (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License along 00017 * with this program; if not, write to the Free Software Foundation, Inc., 00018 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "kalarm.h" 00022 00023 #include <stdlib.h> 00024 #include <string.h> 00025 00026 #include <tqfile.h> 00027 #include <tqfileinfo.h> 00028 #include <tqlayout.h> 00029 #include <tqpushbutton.h> 00030 #include <tqlabel.h> 00031 #include <tqwhatsthis.h> 00032 #include <tqtooltip.h> 00033 #include <tqdragobject.h> 00034 #include <tqtextedit.h> 00035 #include <tqtimer.h> 00036 00037 #include <kstandarddirs.h> 00038 #include <kaction.h> 00039 #include <kstdguiitem.h> 00040 #include <kaboutdata.h> 00041 #include <klocale.h> 00042 #include <kconfig.h> 00043 #include <kiconloader.h> 00044 #include <kdialog.h> 00045 #include <ktextbrowser.h> 00046 #include <kglobalsettings.h> 00047 #include <kmimetype.h> 00048 #include <kmessagebox.h> 00049 #include <kwin.h> 00050 #include <kwinmodule.h> 00051 #include <kprocess.h> 00052 #include <kio/netaccess.h> 00053 #include <knotifyclient.h> 00054 #include <kpushbutton.h> 00055 #ifdef WITHOUT_ARTS 00056 #include <kaudioplayer.h> 00057 #else 00058 #include <arts/kartsdispatcher.h> 00059 #include <arts/kartsserver.h> 00060 #include <arts/kplayobjectfactory.h> 00061 #include <arts/kplayobject.h> 00062 #endif 00063 #include <dcopclient.h> 00064 #include <kdebug.h> 00065 00066 #include "alarmcalendar.h" 00067 #include "deferdlg.h" 00068 #include "editdlg.h" 00069 #include "functions.h" 00070 #include "kalarmapp.h" 00071 #include "mainwindow.h" 00072 #include "preferences.h" 00073 #include "synchtimer.h" 00074 #include "messagewin.moc" 00075 00076 using namespace KCal; 00077 00078 #ifndef WITHOUT_ARTS 00079 static const char* KMIX_APP_NAME = "kmix"; 00080 static const char* KMIX_DCOP_OBJECT = "Mixer0"; 00081 static const char* KMIX_DCOP_WINDOW = "kmix-mainwindow#1"; 00082 #endif 00083 static const char* KMAIL_DCOP_OBJECT = "KMailIface"; 00084 00085 // The delay for enabling message window buttons if a zero delay is 00086 // configured, i.e. the windows are placed far from the cursor. 00087 static const int proximityButtonDelay = 1000; // (milliseconds) 00088 static const int proximityMultiple = 10; // multiple of button height distance from cursor for proximity 00089 00090 static bool wantModal(); 00091 00092 // A text label widget which can be scrolled and copied with the mouse 00093 class MessageText : public TQTextEdit 00094 { 00095 public: 00096 MessageText(const TQString& text, const TQString& context = TQString(), TQWidget* parent = 0, const char* name = 0) 00097 : TQTextEdit(text, context, parent, name) 00098 { 00099 setReadOnly(true); 00100 setWordWrap(TQTextEdit::NoWrap); 00101 } 00102 int scrollBarHeight() const { return horizontalScrollBar()->height(); } 00103 int scrollBarWidth() const { return verticalScrollBar()->width(); } 00104 virtual TQSize sizeHint() const { return TQSize(contentsWidth() + scrollBarWidth(), contentsHeight() + scrollBarHeight()); } 00105 }; 00106 00107 00108 class MWMimeSourceFactory : public TQMimeSourceFactory 00109 { 00110 public: 00111 MWMimeSourceFactory(const TQString& absPath, KTextBrowser*); 00112 virtual ~MWMimeSourceFactory(); 00113 virtual const TQMimeSource* data(const TQString& abs_name) const; 00114 private: 00115 // Prohibit the following methods 00116 virtual void setData(const TQString&, TQMimeSource*) {} 00117 virtual void setExtensionType(const TQString&, const char*) {} 00118 00119 TQString mTextFile; 00120 TQCString mMimeType; 00121 mutable const TQMimeSource* mLast; 00122 }; 00123 00124 00125 // Basic flags for the window 00126 static const TQt::WFlags WFLAGS = TQt::WStyle_StaysOnTop | TQt::WDestructiveClose; 00127 00128 // Error message bit masks 00129 enum { 00130 ErrMsg_Speak = 0x01, 00131 ErrMsg_AudioFile = 0x02, 00132 ErrMsg_Volume = 0x04 00133 }; 00134 00135 00136 TQValueList<MessageWin*> MessageWin::mWindowList; 00137 TQMap<TQString, unsigned> MessageWin::mErrorMessages; 00138 00139 00140 /****************************************************************************** 00141 * Construct the message window for the specified alarm. 00142 * Other alarms in the supplied event may have been updated by the caller, so 00143 * the whole event needs to be stored for updating the calendar file when it is 00144 * displayed. 00145 */ 00146 MessageWin::MessageWin(const KAEvent& event, const KAAlarm& alarm, bool reschedule_event, bool allowDefer) 00147 : MainWindowBase(0, "MessageWin", WFLAGS | TQt::WGroupLeader | TQt::WStyle_ContextHelp 00148 | (wantModal() ? 0 : TQt::WX11BypassWM)), 00149 mMessage(event.cleanText()), 00150 mFont(event.font()), 00151 mBgColour(event.bgColour()), 00152 mFgColour(event.fgColour()), 00153 mDateTime((alarm.type() & KAAlarm::REMINDER_ALARM) ? event.mainDateTime(true) : alarm.dateTime(true)), 00154 mEventID(event.id()), 00155 mAudioFile(event.audioFile()), 00156 mVolume(event.soundVolume()), 00157 mFadeVolume(event.fadeVolume()), 00158 mFadeSeconds(TQMIN(event.fadeSeconds(), 86400)), 00159 mDefaultDeferMinutes(event.deferDefaultMinutes()), 00160 mAlarmType(alarm.type()), 00161 mAction(event.action()), 00162 mKMailSerialNumber(event.kmailSerialNumber()), 00163 mRestoreHeight(0), 00164 mAudioRepeat(event.repeatSound()), 00165 mConfirmAck(event.confirmAck()), 00166 mShowEdit(!mEventID.isEmpty()), 00167 mNoDefer(!allowDefer || alarm.repeatAtLogin()), 00168 mInvalid(false), 00169 mArtsDispatcher(0), 00170 mPlayObject(0), 00171 mOldVolume(-1), 00172 mEvent(event), 00173 mEditButton(0), 00174 mDeferButton(0), 00175 mSilenceButton(0), 00176 mDeferDlg(0), 00177 mWinModule(0), 00178 mFlags(event.flags()), 00179 mLateCancel(event.lateCancel()), 00180 mErrorWindow(false), 00181 mNoPostAction(alarm.type() & KAAlarm::REMINDER_ALARM), 00182 mRecreating(false), 00183 mBeep(event.beep()), 00184 mSpeak(event.speak()), 00185 mRescheduleEvent(reschedule_event), 00186 mShown(false), 00187 mPositioning(false), 00188 mNoCloseConfirm(false), 00189 mDisableDeferral(false) 00190 { 00191 kdDebug(5950) << "MessageWin::MessageWin(event)" << endl; 00192 // Set to save settings automatically, but don't save window size. 00193 // File alarm window size is saved elsewhere. 00194 setAutoSaveSettings(TQString::fromLatin1("MessageWin"), false); 00195 initView(); 00196 mWindowList.append(this); 00197 if (event.autoClose()) 00198 mCloseTime = alarm.dateTime().dateTime().addSecs(event.lateCancel() * 60); 00199 } 00200 00201 /****************************************************************************** 00202 * Construct the message window for a specified error message. 00203 */ 00204 MessageWin::MessageWin(const KAEvent& event, const DateTime& alarmDateTime, const TQStringList& errmsgs) 00205 : MainWindowBase(0, "MessageWin", WFLAGS | TQt::WGroupLeader | TQt::WStyle_ContextHelp), 00206 mMessage(event.cleanText()), 00207 mDateTime(alarmDateTime), 00208 mEventID(event.id()), 00209 mAlarmType(KAAlarm::MAIN_ALARM), 00210 mAction(event.action()), 00211 mKMailSerialNumber(0), 00212 mErrorMsgs(errmsgs), 00213 mRestoreHeight(0), 00214 mConfirmAck(false), 00215 mShowEdit(false), 00216 mNoDefer(true), 00217 mInvalid(false), 00218 mArtsDispatcher(0), 00219 mPlayObject(0), 00220 mEvent(event), 00221 mEditButton(0), 00222 mDeferButton(0), 00223 mSilenceButton(0), 00224 mDeferDlg(0), 00225 mWinModule(0), 00226 mErrorWindow(true), 00227 mNoPostAction(true), 00228 mRecreating(false), 00229 mRescheduleEvent(false), 00230 mShown(false), 00231 mPositioning(false), 00232 mNoCloseConfirm(false), 00233 mDisableDeferral(false) 00234 { 00235 kdDebug(5950) << "MessageWin::MessageWin(errmsg)" << endl; 00236 initView(); 00237 mWindowList.append(this); 00238 } 00239 00240 /****************************************************************************** 00241 * Construct the message window for restoration by session management. 00242 * The window is initialised by readProperties(). 00243 */ 00244 MessageWin::MessageWin() 00245 : MainWindowBase(0, "MessageWin", WFLAGS), 00246 mArtsDispatcher(0), 00247 mPlayObject(0), 00248 mEditButton(0), 00249 mDeferButton(0), 00250 mSilenceButton(0), 00251 mDeferDlg(0), 00252 mWinModule(0), 00253 mErrorWindow(false), 00254 mRecreating(false), 00255 mRescheduleEvent(false), 00256 mShown(false), 00257 mPositioning(false), 00258 mNoCloseConfirm(false), 00259 mDisableDeferral(false) 00260 { 00261 kdDebug(5950) << "MessageWin::MessageWin(restore)\n"; 00262 mWindowList.append(this); 00263 } 00264 00265 /****************************************************************************** 00266 * Destructor. Perform any post-alarm actions before tidying up. 00267 */ 00268 MessageWin::~MessageWin() 00269 { 00270 kdDebug(5950) << "MessageWin::~MessageWin(" << mEventID << ")" << endl; 00271 stopPlay(); 00272 delete mWinModule; 00273 mWinModule = 0; 00274 mErrorMessages.remove(mEventID); 00275 mWindowList.remove(this); 00276 if (!mRecreating) 00277 { 00278 if (!mNoPostAction && !mEvent.postAction().isEmpty()) 00279 theApp()->alarmCompleted(mEvent); 00280 if (!mWindowList.count()) 00281 theApp()->quitIf(); 00282 } 00283 } 00284 00285 /****************************************************************************** 00286 * Construct the message window. 00287 */ 00288 void MessageWin::initView() 00289 { 00290 bool reminder = (!mErrorWindow && (mAlarmType & KAAlarm::REMINDER_ALARM)); 00291 int leading = fontMetrics().leading(); 00292 setCaption((mAlarmType & KAAlarm::REMINDER_ALARM) ? i18n("Reminder") : i18n("Message")); 00293 TQWidget* topWidget = new TQWidget(this, "messageWinTop"); 00294 setCentralWidget(topWidget); 00295 TQVBoxLayout* topLayout = new TQVBoxLayout(topWidget, KDialog::marginHint(), KDialog::spacingHint()); 00296 00297 if (mDateTime.isValid()) 00298 { 00299 // Show the alarm date/time, together with an "Advance reminder" text where appropriate 00300 TQFrame* frame = 0; 00301 TQVBoxLayout* layout = topLayout; 00302 if (reminder) 00303 { 00304 frame = new TQFrame(topWidget); 00305 frame->setFrameStyle(TQFrame::Box | TQFrame::Raised); 00306 topLayout->addWidget(frame, 0, TQt::AlignHCenter); 00307 layout = new TQVBoxLayout(frame, leading + frame->frameWidth(), leading); 00308 } 00309 00310 // Alarm date/time 00311 TQLabel* label = new TQLabel(frame ? frame : topWidget); 00312 label->setText(mDateTime.isDateOnly() 00313 ? KGlobal::locale()->formatDate(mDateTime.date(), true) 00314 : KGlobal::locale()->formatDateTime(mDateTime.dateTime())); 00315 if (!frame) 00316 label->setFrameStyle(TQFrame::Box | TQFrame::Raised); 00317 label->setFixedSize(label->sizeHint()); 00318 layout->addWidget(label, 0, TQt::AlignHCenter); 00319 TQWhatsThis::add(label, 00320 i18n("The scheduled date/time for the message (as opposed to the actual time of display).")); 00321 00322 if (frame) 00323 { 00324 label = new TQLabel(frame); 00325 label->setText(i18n("Reminder")); 00326 label->setFixedSize(label->sizeHint()); 00327 layout->addWidget(label, 0, TQt::AlignHCenter); 00328 frame->setFixedSize(frame->sizeHint()); 00329 } 00330 } 00331 00332 if (!mErrorWindow) 00333 { 00334 // It's a normal alarm message window 00335 switch (mAction) 00336 { 00337 case KAEvent::FILE: 00338 { 00339 // Display the file name 00340 TQLabel* label = new TQLabel(mMessage, topWidget); 00341 label->setFrameStyle(TQFrame::Box | TQFrame::Raised); 00342 label->setFixedSize(label->sizeHint()); 00343 TQWhatsThis::add(label, i18n("The file whose contents are displayed below")); 00344 topLayout->addWidget(label, 0, TQt::AlignHCenter); 00345 00346 // Display contents of file 00347 bool opened = false; 00348 bool dir = false; 00349 TQString tmpFile; 00350 KURL url(mMessage); 00351 if (KIO::NetAccess::download(url, tmpFile, MainWindow::mainMainWindow())) 00352 { 00353 TQFile qfile(tmpFile); 00354 TQFileInfo info(qfile); 00355 if (!(dir = info.isDir())) 00356 { 00357 opened = true; 00358 KTextBrowser* view = new KTextBrowser(topWidget, "fileContents"); 00359 MWMimeSourceFactory msf(tmpFile, view); 00360 view->setMinimumSize(view->sizeHint()); 00361 topLayout->addWidget(view); 00362 00363 // Set the default size to 20 lines square. 00364 // Note that after the first file has been displayed, this size 00365 // is overridden by the user-set default stored in the config file. 00366 // So there is no need to calculate an accurate size. 00367 int h = 20*view->fontMetrics().lineSpacing() + 2*view->frameWidth(); 00368 view->resize(TQSize(h, h).expandedTo(view->sizeHint())); 00369 TQWhatsThis::add(view, i18n("The contents of the file to be displayed")); 00370 } 00371 KIO::NetAccess::removeTempFile(tmpFile); 00372 } 00373 if (!opened) 00374 { 00375 // File couldn't be opened 00376 bool exists = KIO::NetAccess::exists(url, true, MainWindow::mainMainWindow()); 00377 mErrorMsgs += dir ? i18n("File is a folder") : exists ? i18n("Failed to open file") : i18n("File not found"); 00378 } 00379 break; 00380 } 00381 case KAEvent::MESSAGE: 00382 { 00383 // Message label 00384 // Using MessageText instead of TQLabel allows scrolling and mouse copying 00385 MessageText* text = new MessageText(mMessage, TQString(), topWidget); 00386 text->setFrameStyle(TQFrame::NoFrame); 00387 text->setPaper(mBgColour); 00388 text->setPaletteForegroundColor(mFgColour); 00389 text->setFont(mFont); 00390 int lineSpacing = text->fontMetrics().lineSpacing(); 00391 TQSize s = text->sizeHint(); 00392 int h = s.height(); 00393 text->setMaximumHeight(h + text->scrollBarHeight()); 00394 text->setMinimumHeight(TQMIN(h, lineSpacing*4)); 00395 text->setMaximumWidth(s.width() + text->scrollBarWidth()); 00396 TQWhatsThis::add(text, i18n("The alarm message")); 00397 int vspace = lineSpacing/2; 00398 int hspace = lineSpacing - KDialog::marginHint(); 00399 topLayout->addSpacing(vspace); 00400 topLayout->addStretch(); 00401 // Don't include any horizontal margins if message is 2/3 screen width 00402 if (!mWinModule) 00403 mWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP); 00404 if (text->sizeHint().width() >= mWinModule->workArea().width()*2/3) 00405 topLayout->addWidget(text, 1, TQt::AlignHCenter); 00406 else 00407 { 00408 TQBoxLayout* layout = new TQHBoxLayout(topLayout); 00409 layout->addSpacing(hspace); 00410 layout->addWidget(text, 1, TQt::AlignHCenter); 00411 layout->addSpacing(hspace); 00412 } 00413 if (!reminder) 00414 topLayout->addStretch(); 00415 break; 00416 } 00417 case KAEvent::COMMAND: 00418 case KAEvent::EMAIL: 00419 default: 00420 break; 00421 } 00422 00423 if (reminder) 00424 { 00425 // Reminder: show remaining time until the actual alarm 00426 mRemainingText = new TQLabel(topWidget); 00427 mRemainingText->setFrameStyle(TQFrame::Box | TQFrame::Raised); 00428 mRemainingText->setMargin(leading); 00429 if (mDateTime.isDateOnly() || TQDate::currentDate().daysTo(mDateTime.date()) > 0) 00430 { 00431 setRemainingTextDay(); 00432 MidnightTimer::connect(TQT_TQOBJECT(this), TQT_SLOT(setRemainingTextDay())); // update every day 00433 } 00434 else 00435 { 00436 setRemainingTextMinute(); 00437 MinuteTimer::connect(TQT_TQOBJECT(this), TQT_SLOT(setRemainingTextMinute())); // update every minute 00438 } 00439 topLayout->addWidget(mRemainingText, 0, TQt::AlignHCenter); 00440 topLayout->addSpacing(KDialog::spacingHint()); 00441 topLayout->addStretch(); 00442 } 00443 } 00444 else 00445 { 00446 // It's an error message 00447 switch (mAction) 00448 { 00449 case KAEvent::EMAIL: 00450 { 00451 // Display the email addresses and subject. 00452 TQFrame* frame = new TQFrame(topWidget); 00453 frame->setFrameStyle(TQFrame::Box | TQFrame::Raised); 00454 TQWhatsThis::add(frame, i18n("The email to send")); 00455 topLayout->addWidget(frame, 0, TQt::AlignHCenter); 00456 TQGridLayout* grid = new TQGridLayout(frame, 2, 2, KDialog::marginHint(), KDialog::spacingHint()); 00457 00458 TQLabel* label = new TQLabel(i18n("Email addressee", "To:"), frame); 00459 label->setFixedSize(label->sizeHint()); 00460 grid->addWidget(label, 0, 0, TQt::AlignLeft); 00461 label = new TQLabel(mEvent.emailAddresses("\n"), frame); 00462 label->setFixedSize(label->sizeHint()); 00463 grid->addWidget(label, 0, 1, TQt::AlignLeft); 00464 00465 label = new TQLabel(i18n("Email subject", "Subject:"), frame); 00466 label->setFixedSize(label->sizeHint()); 00467 grid->addWidget(label, 1, 0, TQt::AlignLeft); 00468 label = new TQLabel(mEvent.emailSubject(), frame); 00469 label->setFixedSize(label->sizeHint()); 00470 grid->addWidget(label, 1, 1, TQt::AlignLeft); 00471 break; 00472 } 00473 case KAEvent::COMMAND: 00474 case KAEvent::FILE: 00475 case KAEvent::MESSAGE: 00476 default: 00477 // Just display the error message strings 00478 break; 00479 } 00480 } 00481 00482 if (!mErrorMsgs.count()) 00483 topWidget->setBackgroundColor(mBgColour); 00484 else 00485 { 00486 setCaption(i18n("Error")); 00487 TQBoxLayout* layout = new TQHBoxLayout(topLayout); 00488 layout->setMargin(2*KDialog::marginHint()); 00489 layout->addStretch(); 00490 TQLabel* label = new TQLabel(topWidget); 00491 label->setPixmap(DesktopIcon("error")); 00492 label->setFixedSize(label->sizeHint()); 00493 layout->addWidget(label, 0, TQt::AlignRight); 00494 TQBoxLayout* vlayout = new TQVBoxLayout(layout); 00495 for (TQStringList::Iterator it = mErrorMsgs.begin(); it != mErrorMsgs.end(); ++it) 00496 { 00497 label = new TQLabel(*it, topWidget); 00498 label->setFixedSize(label->sizeHint()); 00499 vlayout->addWidget(label, 0, TQt::AlignLeft); 00500 } 00501 layout->addStretch(); 00502 } 00503 00504 TQGridLayout* grid = new TQGridLayout(1, 4); 00505 topLayout->addLayout(grid); 00506 grid->setColStretch(0, 1); // keep the buttons right-adjusted in the window 00507 int gridIndex = 1; 00508 00509 // Close button 00510 mOkButton = new KPushButton(KStdGuiItem::close(), topWidget); 00511 // Prevent accidental acknowledgement of the message if the user is typing when the window appears 00512 mOkButton->clearFocus(); 00513 mOkButton->setFocusPolicy(TQ_ClickFocus); // don't allow keyboard selection 00514 mOkButton->setFixedSize(mOkButton->sizeHint()); 00515 connect(mOkButton, TQT_SIGNAL(clicked()), TQT_SLOT(close())); 00516 grid->addWidget(mOkButton, 0, gridIndex++, AlignHCenter); 00517 TQWhatsThis::add(mOkButton, i18n("Acknowledge the alarm")); 00518 00519 if (mShowEdit) 00520 { 00521 // Edit button 00522 mEditButton = new TQPushButton(i18n("&Edit..."), topWidget); 00523 mEditButton->setFocusPolicy(TQ_ClickFocus); // don't allow keyboard selection 00524 mEditButton->setFixedSize(mEditButton->sizeHint()); 00525 connect(mEditButton, TQT_SIGNAL(clicked()), TQT_SLOT(slotEdit())); 00526 grid->addWidget(mEditButton, 0, gridIndex++, AlignHCenter); 00527 TQWhatsThis::add(mEditButton, i18n("Edit the alarm.")); 00528 } 00529 00530 if (!mNoDefer) 00531 { 00532 // Defer button 00533 mDeferButton = new TQPushButton(i18n("&Defer..."), topWidget); 00534 mDeferButton->setFocusPolicy(TQ_ClickFocus); // don't allow keyboard selection 00535 mDeferButton->setFixedSize(mDeferButton->sizeHint()); 00536 connect(mDeferButton, TQT_SIGNAL(clicked()), TQT_SLOT(slotDefer())); 00537 grid->addWidget(mDeferButton, 0, gridIndex++, AlignHCenter); 00538 TQWhatsThis::add(mDeferButton, 00539 i18n("Defer the alarm until later.\n" 00540 "You will be prompted to specify when the alarm should be redisplayed.")); 00541 00542 setDeferralLimit(mEvent); // ensure that button is disabled when alarm can't be deferred any more 00543 } 00544 00545 #ifndef WITHOUT_ARTS 00546 if (!mAudioFile.isEmpty() && (mVolume || mFadeVolume > 0)) 00547 { 00548 // Silence button to stop sound repetition 00549 TQPixmap pixmap = MainBarIcon("player_stop"); 00550 mSilenceButton = new TQPushButton(topWidget); 00551 mSilenceButton->setPixmap(pixmap); 00552 mSilenceButton->setFixedSize(mSilenceButton->sizeHint()); 00553 connect(mSilenceButton, TQT_SIGNAL(clicked()), TQT_SLOT(stopPlay())); 00554 grid->addWidget(mSilenceButton, 0, gridIndex++, AlignHCenter); 00555 TQToolTip::add(mSilenceButton, i18n("Stop sound")); 00556 TQWhatsThis::add(mSilenceButton, i18n("Stop playing the sound")); 00557 // To avoid getting in a mess, disable the button until sound playing has been set up 00558 mSilenceButton->setEnabled(false); 00559 } 00560 #endif 00561 00562 KIconLoader iconLoader; 00563 if (mKMailSerialNumber) 00564 { 00565 // KMail button 00566 TQPixmap pixmap = iconLoader.loadIcon(TQString::fromLatin1("kmail"), KIcon::MainToolbar); 00567 mKMailButton = new TQPushButton(topWidget); 00568 mKMailButton->setPixmap(pixmap); 00569 mKMailButton->setFixedSize(mKMailButton->sizeHint()); 00570 connect(mKMailButton, TQT_SIGNAL(clicked()), TQT_SLOT(slotShowKMailMessage())); 00571 grid->addWidget(mKMailButton, 0, gridIndex++, AlignHCenter); 00572 TQToolTip::add(mKMailButton, i18n("Locate this email in KMail", "Locate in KMail")); 00573 TQWhatsThis::add(mKMailButton, i18n("Locate and highlight this email in KMail")); 00574 } 00575 else 00576 mKMailButton = 0; 00577 00578 // KAlarm button 00579 TQPixmap pixmap = iconLoader.loadIcon(TQString::fromLatin1(kapp->aboutData()->appName()), KIcon::MainToolbar); 00580 mKAlarmButton = new TQPushButton(topWidget); 00581 mKAlarmButton->setPixmap(pixmap); 00582 mKAlarmButton->setFixedSize(mKAlarmButton->sizeHint()); 00583 connect(mKAlarmButton, TQT_SIGNAL(clicked()), TQT_SLOT(displayMainWindow())); 00584 grid->addWidget(mKAlarmButton, 0, gridIndex++, AlignHCenter); 00585 TQString actKAlarm = i18n("Activate KAlarm"); 00586 TQToolTip::add(mKAlarmButton, actKAlarm); 00587 TQWhatsThis::add(mKAlarmButton, actKAlarm); 00588 00589 // Disable all buttons initially, to prevent accidental clicking on if they happen to be 00590 // under the mouse just as the window appears. 00591 mOkButton->setEnabled(false); 00592 if (mDeferButton) 00593 mDeferButton->setEnabled(false); 00594 if (mEditButton) 00595 mEditButton->setEnabled(false); 00596 if (mKMailButton) 00597 mKMailButton->setEnabled(false); 00598 mKAlarmButton->setEnabled(false); 00599 00600 topLayout->activate(); 00601 setMinimumSize(TQSize(grid->sizeHint().width() + 2*KDialog::marginHint(), sizeHint().height())); 00602 00603 bool modal = !(getWFlags() & TQt::WX11BypassWM); 00604 00605 unsigned long wstate = (modal ? NET::Modal : 0) | NET::Sticky | NET::KeepAbove; 00606 WId winid = winId(); 00607 KWin::setState(winid, wstate); 00608 KWin::setOnAllDesktops(winid, true); 00609 } 00610 00611 /****************************************************************************** 00612 * Set the remaining time text in a reminder window. 00613 * Called at the start of every day (at the user-defined start-of-day time). 00614 */ 00615 void MessageWin::setRemainingTextDay() 00616 { 00617 TQString text; 00618 int days = TQDate::currentDate().daysTo(mDateTime.date()); 00619 if (days <= 0 && !mDateTime.isDateOnly()) 00620 { 00621 // The alarm is due today, so start refreshing every minute 00622 MidnightTimer::disconnect(TQT_TQOBJECT(this), TQT_SLOT(setRemainingTextDay())); 00623 setRemainingTextMinute(); 00624 MinuteTimer::connect(TQT_TQOBJECT(this), TQT_SLOT(setRemainingTextMinute())); // update every minute 00625 } 00626 else 00627 { 00628 if (days <= 0) 00629 text = i18n("Today"); 00630 else if (days % 7) 00631 text = i18n("Tomorrow", "in %n days' time", days); 00632 else 00633 text = i18n("in 1 week's time", "in %n weeks' time", days/7); 00634 } 00635 mRemainingText->setText(text); 00636 } 00637 00638 /****************************************************************************** 00639 * Set the remaining time text in a reminder window. 00640 * Called on every minute boundary. 00641 */ 00642 void MessageWin::setRemainingTextMinute() 00643 { 00644 TQString text; 00645 int mins = (TQDateTime::currentDateTime().secsTo(mDateTime.dateTime()) + 59) / 60; 00646 if (mins < 60) 00647 text = i18n("in 1 minute's time", "in %n minutes' time", (mins > 0 ? mins : 0)); 00648 else if (mins % 60 == 0) 00649 text = i18n("in 1 hour's time", "in %n hours' time", mins/60); 00650 else if (mins % 60 == 1) 00651 text = i18n("in 1 hour 1 minute's time", "in %n hours 1 minute's time", mins/60); 00652 else 00653 text = i18n("in 1 hour %1 minutes' time", "in %n hours %1 minutes' time", mins/60).arg(mins%60); 00654 mRemainingText->setText(text); 00655 } 00656 00657 /****************************************************************************** 00658 * Save settings to the session managed config file, for restoration 00659 * when the program is restored. 00660 */ 00661 void MessageWin::saveProperties(KConfig* config) 00662 { 00663 if (mShown && !mErrorWindow) 00664 { 00665 config->writeEntry(TQString::fromLatin1("EventID"), mEventID); 00666 config->writeEntry(TQString::fromLatin1("AlarmType"), mAlarmType); 00667 config->writeEntry(TQString::fromLatin1("Message"), mMessage); 00668 config->writeEntry(TQString::fromLatin1("Type"), mAction); 00669 config->writeEntry(TQString::fromLatin1("Font"), mFont); 00670 config->writeEntry(TQString::fromLatin1("BgColour"), mBgColour); 00671 config->writeEntry(TQString::fromLatin1("FgColour"), mFgColour); 00672 config->writeEntry(TQString::fromLatin1("ConfirmAck"), mConfirmAck); 00673 if (mDateTime.isValid()) 00674 { 00675 config->writeEntry(TQString::fromLatin1("Time"), mDateTime.dateTime()); 00676 config->writeEntry(TQString::fromLatin1("DateOnly"), mDateTime.isDateOnly()); 00677 } 00678 if (mCloseTime.isValid()) 00679 config->writeEntry(TQString::fromLatin1("Expiry"), mCloseTime); 00680 #ifndef WITHOUT_ARTS 00681 if (mAudioRepeat && mSilenceButton && mSilenceButton->isEnabled()) 00682 { 00683 // Only need to restart sound file playing if it's being repeated 00684 config->writePathEntry(TQString::fromLatin1("AudioFile"), mAudioFile); 00685 config->writeEntry(TQString::fromLatin1("Volume"), static_cast<int>(mVolume * 100)); 00686 } 00687 #endif 00688 config->writeEntry(TQString::fromLatin1("Speak"), mSpeak); 00689 config->writeEntry(TQString::fromLatin1("Height"), height()); 00690 config->writeEntry(TQString::fromLatin1("DeferMins"), mDefaultDeferMinutes); 00691 config->writeEntry(TQString::fromLatin1("NoDefer"), mNoDefer); 00692 config->writeEntry(TQString::fromLatin1("NoPostAction"), mNoPostAction); 00693 config->writeEntry(TQString::fromLatin1("KMailSerial"), mKMailSerialNumber); 00694 } 00695 else 00696 config->writeEntry(TQString::fromLatin1("Invalid"), true); 00697 } 00698 00699 /****************************************************************************** 00700 * Read settings from the session managed config file. 00701 * This function is automatically called whenever the app is being restored. 00702 * Read in whatever was saved in saveProperties(). 00703 */ 00704 void MessageWin::readProperties(KConfig* config) 00705 { 00706 mInvalid = config->readBoolEntry(TQString::fromLatin1("Invalid"), false); 00707 mEventID = config->readEntry(TQString::fromLatin1("EventID")); 00708 mAlarmType = KAAlarm::Type(config->readNumEntry(TQString::fromLatin1("AlarmType"))); 00709 mMessage = config->readEntry(TQString::fromLatin1("Message")); 00710 mAction = KAEvent::Action(config->readNumEntry(TQString::fromLatin1("Type"))); 00711 mFont = config->readFontEntry(TQString::fromLatin1("Font")); 00712 mBgColour = config->readColorEntry(TQString::fromLatin1("BgColour")); 00713 mFgColour = config->readColorEntry(TQString::fromLatin1("FgColour")); 00714 mConfirmAck = config->readBoolEntry(TQString::fromLatin1("ConfirmAck")); 00715 TQDateTime invalidDateTime; 00716 TQDateTime dt = config->readDateTimeEntry(TQString::fromLatin1("Time"), &invalidDateTime); 00717 bool dateOnly = config->readBoolEntry(TQString::fromLatin1("DateOnly")); 00718 mDateTime.set(dt, dateOnly); 00719 mCloseTime = config->readDateTimeEntry(TQString::fromLatin1("Expiry"), &invalidDateTime); 00720 #ifndef WITHOUT_ARTS 00721 mAudioFile = config->readPathEntry(TQString::fromLatin1("AudioFile")); 00722 mVolume = static_cast<float>(config->readNumEntry(TQString::fromLatin1("Volume"))) / 100; 00723 mFadeVolume = -1; 00724 mFadeSeconds = 0; 00725 if (!mAudioFile.isEmpty()) 00726 mAudioRepeat = true; 00727 #endif 00728 mSpeak = config->readBoolEntry(TQString::fromLatin1("Speak")); 00729 mRestoreHeight = config->readNumEntry(TQString::fromLatin1("Height")); 00730 mDefaultDeferMinutes = config->readNumEntry(TQString::fromLatin1("DeferMins")); 00731 mNoDefer = config->readBoolEntry(TQString::fromLatin1("NoDefer")); 00732 mNoPostAction = config->readBoolEntry(TQString::fromLatin1("NoPostAction")); 00733 mKMailSerialNumber = config->readUnsignedLongNumEntry(TQString::fromLatin1("KMailSerial")); 00734 mShowEdit = false; 00735 kdDebug(5950) << "MessageWin::readProperties(" << mEventID << ")" << endl; 00736 if (mAlarmType != KAAlarm::INVALID_ALARM) 00737 { 00738 // Recreate the event from the calendar file (if possible) 00739 if (!mEventID.isEmpty()) 00740 { 00741 const Event* kcalEvent = AlarmCalendar::activeCalendar()->event(mEventID); 00742 if (!kcalEvent) 00743 { 00744 // It's not in the active calendar, so try the displaying calendar 00745 AlarmCalendar* cal = AlarmCalendar::displayCalendar(); 00746 if (cal->isOpen()) 00747 kcalEvent = cal->event(KAEvent::uid(mEventID, KAEvent::DISPLAYING)); 00748 } 00749 if (kcalEvent) 00750 { 00751 mEvent.set(*kcalEvent); 00752 mEvent.setUid(KAEvent::ACTIVE); // in case it came from the display calendar 00753 mShowEdit = true; 00754 } 00755 } 00756 initView(); 00757 } 00758 } 00759 00760 /****************************************************************************** 00761 * Returns the existing message window (if any) which is displaying the event 00762 * with the specified ID. 00763 */ 00764 MessageWin* MessageWin::findEvent(const TQString& eventID) 00765 { 00766 for (TQValueList<MessageWin*>::Iterator it = mWindowList.begin(); it != mWindowList.end(); ++it) 00767 { 00768 MessageWin* w = *it; 00769 if (w->mEventID == eventID && !w->mErrorWindow) 00770 return w; 00771 } 00772 return 0; 00773 } 00774 00775 /****************************************************************************** 00776 * Beep and play the audio file, as appropriate. 00777 */ 00778 void MessageWin::playAudio() 00779 { 00780 if (mBeep) 00781 { 00782 // Beep using two methods, in case the sound card/speakers are switched off or not working 00783 KNotifyClient::beep(); // beep through the sound card & speakers 00784 TQApplication::beep(); // beep through the internal speaker 00785 } 00786 if (!mAudioFile.isEmpty()) 00787 { 00788 if (!mVolume && mFadeVolume <= 0) 00789 return; // ensure zero volume doesn't play anything 00790 #ifdef WITHOUT_ARTS 00791 TQString play = mAudioFile; 00792 TQString file = TQString::fromLatin1("file:"); 00793 if (mAudioFile.startsWith(file)) 00794 play = mAudioFile.mid(file.length()); 00795 KAudioPlayer::play(TQFile::encodeName(play)); 00796 #else 00797 // An audio file is specified. Because loading it may take some time, 00798 // call it on a timer to allow the window to display first. 00799 TQTimer::singleShot(0, this, TQT_SLOT(slotPlayAudio())); 00800 #endif 00801 } 00802 else if (mSpeak) 00803 { 00804 // The message is to be spoken. In case of error messges, 00805 // call it on a timer to allow the window to display first. 00806 TQTimer::singleShot(0, this, TQT_SLOT(slotSpeak())); 00807 } 00808 } 00809 00810 /****************************************************************************** 00811 * Speak the message. 00812 * Called asynchronously to avoid delaying the display of the message. 00813 */ 00814 void MessageWin::slotSpeak() 00815 { 00816 DCOPClient* client = kapp->dcopClient(); 00817 if (!client->isApplicationRegistered("kttsd")) 00818 { 00819 // kttsd is not running, so start it 00820 TQString error; 00821 if (kapp->startServiceByDesktopName("kttsd", TQStringList(), &error)) 00822 { 00823 kdDebug(5950) << "MessageWin::slotSpeak(): failed to start kttsd: " << error << endl; 00824 if (!haveErrorMessage(ErrMsg_Speak)) 00825 { 00826 KMessageBox::detailedError(0, i18n("Unable to speak message"), error); 00827 clearErrorMessage(ErrMsg_Speak); 00828 } 00829 return; 00830 } 00831 } 00832 TQByteArray data; 00833 TQDataStream arg(data, IO_WriteOnly); 00834 arg << mMessage << ""; 00835 if (!client->send("kttsd", "KSpeech", "sayMessage(TQString,TQString)", data)) 00836 { 00837 kdDebug(5950) << "MessageWin::slotSpeak(): sayMessage() DCOP error" << endl; 00838 if (!haveErrorMessage(ErrMsg_Speak)) 00839 { 00840 KMessageBox::detailedError(0, i18n("Unable to speak message"), i18n("DCOP Call sayMessage failed")); 00841 clearErrorMessage(ErrMsg_Speak); 00842 } 00843 } 00844 } 00845 00846 /****************************************************************************** 00847 * Play the audio file. 00848 * Called asynchronously to avoid delaying the display of the message. 00849 */ 00850 void MessageWin::slotPlayAudio() 00851 { 00852 #ifndef WITHOUT_ARTS 00853 // First check that it exists, to avoid possible crashes if the filename is badly specified 00854 MainWindow* mmw = MainWindow::mainMainWindow(); 00855 KURL url(mAudioFile); 00856 if (!url.isValid() || !KIO::NetAccess::exists(url, true, mmw) 00857 || !KIO::NetAccess::download(url, mLocalAudioFile, mmw)) 00858 { 00859 kdError(5950) << "MessageWin::playAudio(): Open failure: " << mAudioFile << endl; 00860 if (!haveErrorMessage(ErrMsg_AudioFile)) 00861 { 00862 KMessageBox::error(this, i18n("Cannot open audio file:\n%1").arg(mAudioFile)); 00863 clearErrorMessage(ErrMsg_AudioFile); 00864 } 00865 return; 00866 } 00867 if (!mArtsDispatcher) 00868 { 00869 mFadeTimer = 0; 00870 mPlayTimer = new TQTimer(this); 00871 connect(mPlayTimer, TQT_SIGNAL(timeout()), TQT_SLOT(checkAudioPlay())); 00872 mArtsDispatcher = new KArtsDispatcher; 00873 mPlayedOnce = false; 00874 mAudioFileStart = TQTime::currentTime(); 00875 initAudio(true); 00876 if (!mPlayObject->object().isNull()) 00877 checkAudioPlay(); 00878 if (!mUsingKMix && mVolume >= 0) 00879 { 00880 // Output error message now that everything else has been done. 00881 // (Outputting it earlier would delay things until it is acknowledged.) 00882 kdWarning(5950) << "Unable to set master volume (KMix: " << mKMixError << ")\n"; 00883 if (!haveErrorMessage(ErrMsg_Volume)) 00884 { 00885 KMessageBox::information(this, i18n("Unable to set master volume\n(Error accessing KMix:\n%1)").arg(mKMixError), 00886 TQString(), TQString::fromLatin1("KMixError")); 00887 clearErrorMessage(ErrMsg_Volume); 00888 } 00889 } 00890 } 00891 #endif 00892 } 00893 00894 #ifndef WITHOUT_ARTS 00895 /****************************************************************************** 00896 * Set up the audio file for playing. 00897 */ 00898 void MessageWin::initAudio(bool firstTime) 00899 { 00900 KArtsServer aserver; 00901 Arts::SoundServerV2 sserver = aserver.server(); 00902 KDE::PlayObjectFactory factory(sserver); 00903 mPlayObject = factory.createPlayObject(mLocalAudioFile, true); 00904 if (firstTime) 00905 { 00906 // Save the existing sound volume setting for restoration afterwards, 00907 // and set the desired volume for the alarm. 00908 mUsingKMix = false; 00909 float volume = mVolume; // initial volume 00910 if (volume >= 0) 00911 { 00912 // The volume has been specified 00913 if (mFadeVolume >= 0) 00914 volume = mFadeVolume; // fading, so adjust the initial volume 00915 00916 // Get the current master volume from KMix 00917 int vol = getKMixVolume(); 00918 if (vol >= 0) 00919 { 00920 mOldVolume = vol; // success 00921 mUsingKMix = true; 00922 setKMixVolume(static_cast<int>(volume * 100)); 00923 } 00924 } 00925 if (!mUsingKMix) 00926 { 00927 /* Adjust within the current master volume, because either 00928 * a) the volume is not specified, in which case we want to play 00929 * at 100% of the current master volume setting, or 00930 * b) KMix is not available to set the master volume. 00931 */ 00932 mOldVolume = sserver.outVolume().scaleFactor(); // save volume for restoration afterwards 00933 sserver.outVolume().scaleFactor(volume >= 0 ? volume : 1); 00934 } 00935 } 00936 mSilenceButton->setEnabled(true); 00937 mPlayed = false; 00938 connect(mPlayObject, TQT_SIGNAL(playObjectCreated()), TQT_SLOT(checkAudioPlay())); 00939 if (!mPlayObject->object().isNull()) 00940 checkAudioPlay(); 00941 } 00942 #endif 00943 00944 /****************************************************************************** 00945 * Called when the audio file has loaded and is ready to play, or on a timer 00946 * when play is expected to have completed. 00947 * If it is ready to play, start playing it (for the first time or repeated). 00948 * If play has not yet completed, wait a bit longer. 00949 */ 00950 void MessageWin::checkAudioPlay() 00951 { 00952 #ifndef WITHOUT_ARTS 00953 if (!mPlayObject) 00954 return; 00955 if (mPlayObject->state() == Arts::posIdle) 00956 { 00957 // The file has loaded and is ready to play, or play has completed 00958 if (mPlayedOnce && !mAudioRepeat) 00959 { 00960 // Play has completed 00961 stopPlay(); 00962 return; 00963 } 00964 00965 // Start playing the file, either for the first time or again 00966 kdDebug(5950) << "MessageWin::checkAudioPlay(): start\n"; 00967 if (!mPlayedOnce) 00968 { 00969 // Start playing the file for the first time 00970 TQTime now = TQTime::currentTime(); 00971 mAudioFileLoadSecs = mAudioFileStart.secsTo(now); 00972 if (mAudioFileLoadSecs < 0) 00973 mAudioFileLoadSecs += 86400; 00974 if (mVolume >= 0 && mFadeVolume >= 0 && mFadeSeconds > 0) 00975 { 00976 // Set up volume fade 00977 mAudioFileStart = now; 00978 mFadeTimer = new TQTimer(this); 00979 connect(mFadeTimer, TQT_SIGNAL(timeout()), TQT_SLOT(slotFade())); 00980 mFadeTimer->start(1000); // adjust volume every second 00981 } 00982 mPlayedOnce = true; 00983 } 00984 if (mAudioFileLoadSecs < 3) 00985 { 00986 /* The aRts library takes several attempts before a PlayObject can 00987 * be replayed, leaving a gap of perhaps 5 seconds between plays. 00988 * So if loading the file takes a short time, it's better to reload 00989 * the PlayObject rather than try to replay the same PlayObject. 00990 */ 00991 if (mPlayed) 00992 { 00993 // Playing has completed. Start playing again. 00994 delete mPlayObject; 00995 initAudio(false); 00996 if (mPlayObject->object().isNull()) 00997 return; 00998 } 00999 mPlayed = true; 01000 mPlayObject->play(); 01001 } 01002 else 01003 { 01004 // The file is slow to load, so attempt to replay the PlayObject 01005 static Arts::poTime t0((long)0, (long)0, 0, std::string()); 01006 Arts::poTime current = mPlayObject->currentTime(); 01007 if (current.seconds || current.ms) 01008 mPlayObject->seek(t0); 01009 else 01010 mPlayObject->play(); 01011 } 01012 } 01013 01014 // The sound file is still playing 01015 Arts::poTime overall = mPlayObject->overallTime(); 01016 Arts::poTime current = mPlayObject->currentTime(); 01017 int time = 1000*(overall.seconds - current.seconds) + overall.ms - current.ms; 01018 if (time < 0) 01019 time = 0; 01020 kdDebug(5950) << "MessageWin::checkAudioPlay(): wait for " << (time+100) << "ms\n"; 01021 mPlayTimer->start(time + 100, true); 01022 #endif 01023 } 01024 01025 /****************************************************************************** 01026 * Called when play completes, the Silence button is clicked, or the window is 01027 * closed, to reset the sound volume and terminate audio access. 01028 */ 01029 void MessageWin::stopPlay() 01030 { 01031 #ifndef WITHOUT_ARTS 01032 if (mArtsDispatcher) 01033 { 01034 // Restore the sound volume to what it was before the sound file 01035 // was played, provided that nothing else has modified it since. 01036 if (!mUsingKMix) 01037 { 01038 KArtsServer aserver; 01039 Arts::StereoVolumeControl svc = aserver.server().outVolume(); 01040 float currentVolume = svc.scaleFactor(); 01041 float eventVolume = mVolume; 01042 if (eventVolume < 0) 01043 eventVolume = 1; 01044 if (currentVolume == eventVolume) 01045 svc.scaleFactor(mOldVolume); 01046 } 01047 else if (mVolume >= 0) 01048 { 01049 int eventVolume = static_cast<int>(mVolume * 100); 01050 int currentVolume = getKMixVolume(); 01051 // Volume returned isn't always exactly equal to volume set 01052 if (currentVolume < 0 || abs(currentVolume - eventVolume) < 5) 01053 setKMixVolume(static_cast<int>(mOldVolume)); 01054 } 01055 } 01056 delete mPlayObject; mPlayObject = 0; 01057 delete mArtsDispatcher; mArtsDispatcher = 0; 01058 if (!mLocalAudioFile.isEmpty()) 01059 { 01060 KIO::NetAccess::removeTempFile(mLocalAudioFile); // removes it only if it IS a temporary file 01061 mLocalAudioFile = TQString(); 01062 } 01063 if (mSilenceButton) 01064 mSilenceButton->setEnabled(false); 01065 #endif 01066 } 01067 01068 /****************************************************************************** 01069 * Called every second to fade the volume when the audio file starts playing. 01070 */ 01071 void MessageWin::slotFade() 01072 { 01073 #ifndef WITHOUT_ARTS 01074 TQTime now = TQTime::currentTime(); 01075 int elapsed = mAudioFileStart.secsTo(now); 01076 if (elapsed < 0) 01077 elapsed += 86400; // it's the next day 01078 float volume; 01079 if (elapsed >= mFadeSeconds) 01080 { 01081 // The fade has finished. Set to normal volume. 01082 volume = mVolume; 01083 delete mFadeTimer; 01084 mFadeTimer = 0; 01085 if (!mVolume) 01086 { 01087 kdDebug(5950) << "MessageWin::slotFade(0)\n"; 01088 stopPlay(); 01089 return; 01090 } 01091 } 01092 else 01093 volume = mFadeVolume + ((mVolume - mFadeVolume) * elapsed) / mFadeSeconds; 01094 kdDebug(5950) << "MessageWin::slotFade(" << volume << ")\n"; 01095 if (mArtsDispatcher) 01096 { 01097 if (mUsingKMix) 01098 setKMixVolume(static_cast<int>(volume * 100)); 01099 else if (mArtsDispatcher) 01100 { 01101 KArtsServer aserver; 01102 aserver.server().outVolume().scaleFactor(volume); 01103 } 01104 } 01105 #endif 01106 } 01107 01108 #ifndef WITHOUT_ARTS 01109 /****************************************************************************** 01110 * Get the master volume from KMix. 01111 * Reply < 0 if failure. 01112 */ 01113 int MessageWin::getKMixVolume() 01114 { 01115 if (!KAlarm::runProgram(KMIX_APP_NAME, KMIX_DCOP_WINDOW, mKMixName, mKMixError)) // start KMix if it isn't already running 01116 return -1; 01117 TQByteArray data, replyData; 01118 TQCString replyType; 01119 TQDataStream arg(data, IO_WriteOnly); 01120 if (!kapp->dcopClient()->call(mKMixName, KMIX_DCOP_OBJECT, "masterVolume()", data, replyType, replyData) 01121 || replyType != "int") 01122 return -1; 01123 int result; 01124 TQDataStream reply(replyData, IO_ReadOnly); 01125 reply >> result; 01126 return (result >= 0) ? result : 0; 01127 } 01128 01129 /****************************************************************************** 01130 * Set the master volume using KMix. 01131 */ 01132 void MessageWin::setKMixVolume(int percent) 01133 { 01134 if (!mUsingKMix) 01135 return; 01136 if (!KAlarm::runProgram(KMIX_APP_NAME, KMIX_DCOP_WINDOW, mKMixName, mKMixError)) // start KMix if it isn't already running 01137 return; 01138 TQByteArray data; 01139 TQDataStream arg(data, IO_WriteOnly); 01140 arg << percent; 01141 if (!kapp->dcopClient()->send(mKMixName, KMIX_DCOP_OBJECT, "setMasterVolume(int)", data)) 01142 kdError(5950) << "MessageWin::setKMixVolume(): kmix dcop call failed\n"; 01143 } 01144 #endif 01145 01146 /****************************************************************************** 01147 * Raise the alarm window, re-output any required audio notification, and 01148 * reschedule the alarm in the calendar file. 01149 */ 01150 void MessageWin::repeat(const KAAlarm& alarm) 01151 { 01152 if (mDeferDlg) 01153 { 01154 // Cancel any deferral dialogue so that the user notices something's going on, 01155 // and also because the deferral time limit will have changed. 01156 delete mDeferDlg; 01157 mDeferDlg = 0; 01158 } 01159 const Event* kcalEvent = mEventID.isNull() ? 0 : AlarmCalendar::activeCalendar()->event(mEventID); 01160 if (kcalEvent) 01161 { 01162 mAlarmType = alarm.type(); // store new alarm type for use if it is later deferred 01163 if (!mDeferDlg || Preferences::modalMessages()) 01164 { 01165 raise(); 01166 playAudio(); 01167 } 01168 KAEvent event(*kcalEvent); 01169 mDeferButton->setEnabled(true); 01170 setDeferralLimit(event); // ensure that button is disabled when alarm can't be deferred any more 01171 theApp()->alarmShowing(event, mAlarmType, mDateTime); 01172 } 01173 } 01174 01175 /****************************************************************************** 01176 * Display the window. 01177 * If windows are being positioned away from the mouse cursor, it is initially 01178 * positioned at the top left to slightly reduce the number of times the 01179 * windows need to be moved in showEvent(). 01180 */ 01181 void MessageWin::show() 01182 { 01183 if (mCloseTime.isValid()) 01184 { 01185 // Set a timer to auto-close the window 01186 int delay = TQDateTime::currentDateTime().secsTo(mCloseTime); 01187 if (delay < 0) 01188 delay = 0; 01189 TQTimer::singleShot(delay * 1000, this, TQT_SLOT(close())); 01190 if (!delay) 01191 return; // don't show the window if auto-closing is already due 01192 } 01193 if (Preferences::messageButtonDelay() == 0) 01194 move(0, 0); 01195 MainWindowBase::show(); 01196 } 01197 01198 /****************************************************************************** 01199 * Returns the window's recommended size exclusive of its frame. 01200 * For message windows, the size if limited to fit inside the working area of 01201 * the desktop. 01202 */ 01203 TQSize MessageWin::sizeHint() const 01204 { 01205 if (mAction != KAEvent::MESSAGE) 01206 return MainWindowBase::sizeHint(); 01207 if (!mWinModule) 01208 mWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP); 01209 TQSize frame = frameGeometry().size(); 01210 TQSize contents = geometry().size(); 01211 TQSize desktop = mWinModule->workArea().size(); 01212 TQSize maxSize(desktop.width() - (frame.width() - contents.width()), 01213 desktop.height() - (frame.height() - contents.height())); 01214 return MainWindowBase::sizeHint().boundedTo(maxSize); 01215 } 01216 01217 /****************************************************************************** 01218 * Called when the window is shown. 01219 * The first time, output any required audio notification, and reschedule or 01220 * delete the event from the calendar file. 01221 */ 01222 void MessageWin::showEvent(TQShowEvent* se) 01223 { 01224 MainWindowBase::showEvent(se); 01225 if (!mShown) 01226 { 01227 if (mErrorWindow) 01228 enableButtons(); // don't bother repositioning error messages 01229 else 01230 { 01231 /* Set the window size. 01232 * Note that the frame thickness is not yet known when this 01233 * method is called, so for large windows the size needs to be 01234 * set again later. 01235 */ 01236 TQSize s = sizeHint(); // fit the window round the message 01237 if (mAction == KAEvent::FILE && !mErrorMsgs.count()) 01238 KAlarm::readConfigWindowSize("FileMessage", s); 01239 resize(s); 01240 01241 mButtonDelay = Preferences::messageButtonDelay() * 1000; 01242 if (!mButtonDelay) 01243 { 01244 /* Try to ensure that the window can't accidentally be acknowledged 01245 * by the user clicking the mouse just as it appears. 01246 * To achieve this, move the window so that the OK button is as far away 01247 * from the cursor as possible. If the buttons are still too close to the 01248 * cursor, disable the buttons for a short time. 01249 * N.B. This can't be done in show(), since the geometry of the window 01250 * is not known until it is displayed. Unfortunately by moving the 01251 * window in showEvent(), a flicker is unavoidable. 01252 * See the TQt documentation on window geometry for more details. 01253 */ 01254 // PROBLEM: The frame size is not known yet! 01255 01256 /* Find the usable area of the desktop or, if the desktop comprises 01257 * multiple screens, the usable area of the current screen. (If the 01258 * message is displayed on a screen other than that currently being 01259 * worked with, it might not be noticed.) 01260 */ 01261 TQPoint cursor = TQCursor::pos(); 01262 if (!mWinModule) 01263 mWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP); 01264 TQRect desk = mWinModule->workArea(); 01265 TQDesktopWidget* dw = TQApplication::desktop(); 01266 if (dw->numScreens() > 1) 01267 desk &= dw->screenGeometry(dw->screenNumber(cursor)); 01268 01269 TQRect frame = frameGeometry(); 01270 TQRect rect = geometry(); 01271 // Find the offsets from the outside of the frame to the edges of the OK button 01272 TQRect button(mOkButton->mapToParent(TQPoint(0, 0)), mOkButton->mapToParent(mOkButton->rect().bottomRight())); 01273 int buttonLeft = button.left() + rect.left() - frame.left(); 01274 int buttonRight = width() - button.right() + frame.right() - rect.right(); 01275 int buttonTop = button.top() + rect.top() - frame.top(); 01276 int buttonBottom = height() - button.bottom() + frame.bottom() - rect.bottom(); 01277 01278 int centrex = (desk.width() + buttonLeft - buttonRight) / 2; 01279 int centrey = (desk.height() + buttonTop - buttonBottom) / 2; 01280 int x = (cursor.x() < centrex) ? desk.right() - frame.width() : desk.left(); 01281 int y = (cursor.y() < centrey) ? desk.bottom() - frame.height() : desk.top(); 01282 01283 // Find the enclosing rectangle for the new button positions 01284 // and check if the cursor is too near 01285 TQRect buttons = mOkButton->geometry().unite(mKAlarmButton->geometry()); 01286 buttons.moveBy(rect.left() + x - frame.left(), rect.top() + y - frame.top()); 01287 int minDistance = proximityMultiple * mOkButton->height(); 01288 if ((abs(cursor.x() - buttons.left()) < minDistance 01289 || abs(cursor.x() - buttons.right()) < minDistance) 01290 && (abs(cursor.y() - buttons.top()) < minDistance 01291 || abs(cursor.y() - buttons.bottom()) < minDistance)) 01292 mButtonDelay = proximityButtonDelay; // too near - disable buttons initially 01293 01294 if (x != frame.left() || y != frame.top()) 01295 { 01296 mPositioning = true; 01297 move(x, y); 01298 } 01299 } 01300 if (!mPositioning) 01301 displayComplete(); // play audio, etc. 01302 if (mAction == KAEvent::MESSAGE) 01303 { 01304 // Set the window size once the frame size is known 01305 TQTimer::singleShot(0, this, TQT_SLOT(setMaxSize())); 01306 } 01307 } 01308 mShown = true; 01309 } 01310 } 01311 01312 /****************************************************************************** 01313 * Called when the window has been moved. 01314 */ 01315 void MessageWin::moveEvent(TQMoveEvent* e) 01316 { 01317 MainWindowBase::moveEvent(e); 01318 if (mPositioning) 01319 { 01320 // The window has just been initially positioned 01321 mPositioning = false; 01322 displayComplete(); // play audio, etc. 01323 } 01324 } 01325 01326 /****************************************************************************** 01327 * Reset the iniital window size if it exceeds the working area of the desktop. 01328 */ 01329 void MessageWin::setMaxSize() 01330 { 01331 TQSize s = sizeHint(); 01332 if (width() > s.width() || height() > s.height()) 01333 resize(s); 01334 } 01335 01336 /****************************************************************************** 01337 * Called when the window has been displayed properly (in its correct position), 01338 * to play sounds and reschedule the event. 01339 */ 01340 void MessageWin::displayComplete() 01341 { 01342 playAudio(); 01343 if (mRescheduleEvent) 01344 theApp()->alarmShowing(mEvent, mAlarmType, mDateTime); 01345 01346 // Enable the window's buttons either now or after the configured delay 01347 if (mButtonDelay > 0) 01348 TQTimer::singleShot(mButtonDelay, this, TQT_SLOT(enableButtons())); 01349 else 01350 enableButtons(); 01351 } 01352 01353 /****************************************************************************** 01354 * Enable the window's buttons. 01355 */ 01356 void MessageWin::enableButtons() 01357 { 01358 mOkButton->setEnabled(true); 01359 mKAlarmButton->setEnabled(true); 01360 if (mDeferButton && !mDisableDeferral) 01361 mDeferButton->setEnabled(true); 01362 if (mEditButton) 01363 mEditButton->setEnabled(true); 01364 if (mKMailButton) 01365 mKMailButton->setEnabled(true); 01366 } 01367 01368 /****************************************************************************** 01369 * Called when the window's size has changed (before it is painted). 01370 */ 01371 void MessageWin::resizeEvent(TQResizeEvent* re) 01372 { 01373 if (mRestoreHeight) 01374 { 01375 // Restore the window height on session restoration 01376 if (mRestoreHeight != re->size().height()) 01377 { 01378 TQSize size = re->size(); 01379 size.setHeight(mRestoreHeight); 01380 resize(size); 01381 } 01382 else if (isVisible()) 01383 mRestoreHeight = 0; 01384 } 01385 else 01386 { 01387 if (mShown && mAction == KAEvent::FILE && !mErrorMsgs.count()) 01388 KAlarm::writeConfigWindowSize("FileMessage", re->size()); 01389 MainWindowBase::resizeEvent(re); 01390 } 01391 } 01392 01393 /****************************************************************************** 01394 * Called when a close event is received. 01395 * Only quits the application if there is no system tray icon displayed. 01396 */ 01397 void MessageWin::closeEvent(TQCloseEvent* ce) 01398 { 01399 // Don't prompt or delete the alarm from the display calendar if the session is closing 01400 if (!mErrorWindow && !theApp()->sessionClosingDown()) 01401 { 01402 if (mConfirmAck && !mNoCloseConfirm) 01403 { 01404 // Ask for confirmation of acknowledgement. Use warningYesNo() because its default is No. 01405 if (KMessageBox::warningYesNo(this, i18n("Do you really want to acknowledge this alarm?"), 01406 i18n("Acknowledge Alarm"), i18n("&Acknowledge"), KStdGuiItem::cancel()) 01407 != KMessageBox::Yes) 01408 { 01409 ce->ignore(); 01410 return; 01411 } 01412 } 01413 if (!mEventID.isNull()) 01414 { 01415 // Delete from the display calendar 01416 KAlarm::deleteDisplayEvent(KAEvent::uid(mEventID, KAEvent::DISPLAYING)); 01417 } 01418 } 01419 MainWindowBase::closeEvent(ce); 01420 } 01421 01422 /****************************************************************************** 01423 * Called when the KMail button is clicked. 01424 * Tells KMail to display the email message displayed in this message window. 01425 */ 01426 void MessageWin::slotShowKMailMessage() 01427 { 01428 kdDebug(5950) << "MessageWin::slotShowKMailMessage()\n"; 01429 if (!mKMailSerialNumber) 01430 return; 01431 TQString err = KAlarm::runKMail(false); 01432 if (!err.isNull()) 01433 { 01434 KMessageBox::sorry(this, err); 01435 return; 01436 } 01437 TQCString replyType; 01438 TQByteArray data, replyData; 01439 TQDataStream arg(data, IO_WriteOnly); 01440 arg << (TQ_UINT32)mKMailSerialNumber << TQString(); 01441 if (kapp->dcopClient()->call("kmail", KMAIL_DCOP_OBJECT, "showMail(TQ_UINT32,TQString)", data, replyType, replyData) 01442 && replyType == "bool") 01443 { 01444 bool result; 01445 TQDataStream replyStream(replyData, IO_ReadOnly); 01446 replyStream >> result; 01447 if (result) 01448 return; // success 01449 } 01450 kdError(5950) << "MessageWin::slotShowKMailMessage(): kmail dcop call failed\n"; 01451 KMessageBox::sorry(this, i18n("Unable to locate this email in KMail")); 01452 } 01453 01454 /****************************************************************************** 01455 * Called when the Edit... button is clicked. 01456 * Displays the alarm edit dialog. 01457 */ 01458 void MessageWin::slotEdit() 01459 { 01460 kdDebug(5950) << "MessageWin::slotEdit()" << endl; 01461 EditAlarmDlg editDlg(false, i18n("Edit Alarm"), this, "editDlg", &mEvent); 01462 if (editDlg.exec() == TQDialog::Accepted) 01463 { 01464 KAEvent event; 01465 editDlg.getEvent(event); 01466 01467 // Update the displayed lists and the calendar file 01468 KAlarm::UpdateStatus status; 01469 if (AlarmCalendar::activeCalendar()->event(mEventID)) 01470 { 01471 // The old alarm hasn't expired yet, so replace it 01472 status = KAlarm::modifyEvent(mEvent, event, 0, &editDlg); 01473 Undo::saveEdit(mEvent, event); 01474 } 01475 else 01476 { 01477 // The old event has expired, so simply create a new one 01478 status = KAlarm::addEvent(event, 0, &editDlg); 01479 Undo::saveAdd(event); 01480 } 01481 01482 if (status == KAlarm::UPDATE_KORG_ERR) 01483 KAlarm::displayKOrgUpdateError(&editDlg, KAlarm::KORG_ERR_MODIFY, 1); 01484 KAlarm::outputAlarmWarnings(&editDlg, &event); 01485 01486 // Close the alarm window 01487 mNoCloseConfirm = true; // allow window to close without confirmation prompt 01488 close(); 01489 } 01490 } 01491 01492 /****************************************************************************** 01493 * Set up to disable the defer button when the deferral limit is reached. 01494 */ 01495 void MessageWin::setDeferralLimit(const KAEvent& event) 01496 { 01497 if (mDeferButton) 01498 { 01499 mDeferLimit = event.deferralLimit().dateTime(); 01500 MidnightTimer::connect(TQT_TQOBJECT(this), TQT_SLOT(checkDeferralLimit())); // check every day 01501 mDisableDeferral = false; 01502 checkDeferralLimit(); 01503 } 01504 } 01505 01506 /****************************************************************************** 01507 * Check whether the deferral limit has been reached. 01508 * If so, disable the Defer button. 01509 * N.B. Ideally, just a single TQTimer::singleShot() call would be made to disable 01510 * the defer button at the corret time. But for a 32-bit integer, the 01511 * milliseconds parameter overflows in about 25 days, so instead a daily 01512 * check is done until the day when the deferral limit is reached, followed 01513 * by a non-overflowing TQTimer::singleShot() call. 01514 */ 01515 void MessageWin::checkDeferralLimit() 01516 { 01517 if (!mDeferButton || !mDeferLimit.isValid()) 01518 return; 01519 int n = TQDate::currentDate().daysTo(mDeferLimit.date()); 01520 if (n > 0) 01521 return; 01522 MidnightTimer::disconnect(TQT_TQOBJECT(this), TQT_SLOT(checkDeferralLimit())); 01523 if (n == 0) 01524 { 01525 // The deferral limit will be reached today 01526 n = TQTime::currentTime().secsTo(mDeferLimit.time()); 01527 if (n > 0) 01528 { 01529 TQTimer::singleShot(n * 1000, this, TQT_SLOT(checkDeferralLimit())); 01530 return; 01531 } 01532 } 01533 mDeferButton->setEnabled(false); 01534 mDisableDeferral = true; 01535 } 01536 01537 /****************************************************************************** 01538 * Called when the Defer... button is clicked. 01539 * Displays the defer message dialog. 01540 */ 01541 void MessageWin::slotDefer() 01542 { 01543 mDeferDlg = new DeferAlarmDlg(i18n("Defer Alarm"), TQDateTime(TQDateTime::currentDateTime().addSecs(60)), 01544 false, this, "deferDlg"); 01545 if (mDefaultDeferMinutes > 0) 01546 mDeferDlg->setDeferMinutes(mDefaultDeferMinutes); 01547 mDeferDlg->setLimit(mEventID); 01548 if (!Preferences::modalMessages()) 01549 lower(); 01550 if (mDeferDlg->exec() == TQDialog::Accepted) 01551 { 01552 DateTime dateTime = mDeferDlg->getDateTime(); 01553 int delayMins = mDeferDlg->deferMinutes(); 01554 const Event* kcalEvent = mEventID.isNull() ? 0 : AlarmCalendar::activeCalendar()->event(mEventID); 01555 if (kcalEvent) 01556 { 01557 // The event still exists in the calendar file. 01558 KAEvent event(*kcalEvent); 01559 bool repeat = event.defer(dateTime, (mAlarmType & KAAlarm::REMINDER_ALARM), true); 01560 event.setDeferDefaultMinutes(delayMins); 01561 KAlarm::updateEvent(event, 0, mDeferDlg, true, !repeat); 01562 if (event.deferred()) 01563 mNoPostAction = true; 01564 } 01565 else 01566 { 01567 KAEvent event; 01568 kcalEvent = AlarmCalendar::displayCalendar()->event(KAEvent::uid(mEventID, KAEvent::DISPLAYING)); 01569 if (kcalEvent) 01570 { 01571 event.reinstateFromDisplaying(KAEvent(*kcalEvent)); 01572 event.defer(dateTime, (mAlarmType & KAAlarm::REMINDER_ALARM), true); 01573 } 01574 else 01575 { 01576 // The event doesn't exist any more !?!, so create a new one 01577 event.set(dateTime.dateTime(), mMessage, mBgColour, mFgColour, mFont, mAction, mLateCancel, mFlags); 01578 event.setAudioFile(mAudioFile, mVolume, mFadeVolume, mFadeSeconds); 01579 event.setArchive(); 01580 event.setEventID(mEventID); 01581 } 01582 event.setDeferDefaultMinutes(delayMins); 01583 // Add the event back into the calendar file, retaining its ID 01584 // and not updating KOrganizer 01585 KAlarm::addEvent(event, 0, mDeferDlg, true, false); 01586 if (event.deferred()) 01587 mNoPostAction = true; 01588 if (kcalEvent) 01589 { 01590 event.setUid(KAEvent::EXPIRED); 01591 KAlarm::deleteEvent(event, false); 01592 } 01593 } 01594 if (theApp()->wantRunInSystemTray()) 01595 { 01596 // Alarms are to be displayed only if the system tray icon is running, 01597 // so start it if necessary so that the deferred alarm will be shown. 01598 theApp()->displayTrayIcon(true); 01599 } 01600 mNoCloseConfirm = true; // allow window to close without confirmation prompt 01601 close(); 01602 } 01603 else 01604 raise(); 01605 delete mDeferDlg; 01606 mDeferDlg = 0; 01607 } 01608 01609 /****************************************************************************** 01610 * Called when the KAlarm icon button in the message window is clicked. 01611 * Displays the main window, with the appropriate alarm selected. 01612 */ 01613 void MessageWin::displayMainWindow() 01614 { 01615 KAlarm::displayMainWindowSelected(mEventID); 01616 } 01617 01618 /****************************************************************************** 01619 * Check whether the specified error message is already displayed for this 01620 * alarm, and note that it will now be displayed. 01621 * Reply = true if message is already displayed. 01622 */ 01623 bool MessageWin::haveErrorMessage(unsigned msg) const 01624 { 01625 if (!mErrorMessages.contains(mEventID)) 01626 mErrorMessages.insert(mEventID, 0); 01627 bool result = (mErrorMessages[mEventID] & msg); 01628 mErrorMessages[mEventID] |= msg; 01629 return result; 01630 } 01631 01632 void MessageWin::clearErrorMessage(unsigned msg) const 01633 { 01634 if (mErrorMessages.contains(mEventID)) 01635 { 01636 if (mErrorMessages[mEventID] == msg) 01637 mErrorMessages.remove(mEventID); 01638 else 01639 mErrorMessages[mEventID] &= ~msg; 01640 } 01641 } 01642 01643 01644 /****************************************************************************** 01645 * Check whether the message window should be modal, i.e. with title bar etc. 01646 * Normally this follows the Preferences setting, but if there is a full screen 01647 * window displayed, on X11 the message window has to bypass the window manager 01648 * in order to display on top of it (which has the side effect that it will have 01649 * no window decoration). 01650 */ 01651 bool wantModal() 01652 { 01653 bool modal = Preferences::modalMessages(); 01654 if (modal) 01655 { 01656 KWinModule wm(0, KWinModule::INFO_DESKTOP); 01657 KWin::WindowInfo wi = KWin::windowInfo(wm.activeWindow(), NET::WMState); 01658 modal = !(wi.valid() && wi.hasState(NET::FullScreen)); 01659 } 01660 return modal; 01661 } 01662 01663 01664 /*============================================================================= 01665 = Class MWMimeSourceFactory 01666 * Gets the mime type of a text file from not only its extension (as per 01667 * TQMimeSourceFactory), but also from its contents. This allows the detection 01668 * of plain text files without file name extensions. 01669 =============================================================================*/ 01670 MWMimeSourceFactory::MWMimeSourceFactory(const TQString& absPath, KTextBrowser* view) 01671 : TQMimeSourceFactory(), 01672 mMimeType("text/plain"), 01673 mLast(0) 01674 { 01675 view->setMimeSourceFactory(this); 01676 TQString type = KMimeType::findByPath(absPath)->name(); 01677 switch (KAlarm::fileType(type)) 01678 { 01679 case KAlarm::TextPlain: 01680 case KAlarm::TextFormatted: 01681 mMimeType = type.latin1(); 01682 // fall through to 'TextApplication' 01683 case KAlarm::TextApplication: 01684 default: 01685 // It's assumed to be a text file 01686 mTextFile = absPath; 01687 view->TQTextBrowser::setSource(absPath); 01688 break; 01689 01690 case KAlarm::Image: 01691 // It's an image file 01692 TQString text = "<img source=\""; 01693 text += absPath; 01694 text += "\">"; 01695 view->setText(text); 01696 break; 01697 } 01698 setFilePath(TQFileInfo(absPath).dirPath(true)); 01699 } 01700 01701 MWMimeSourceFactory::~MWMimeSourceFactory() 01702 { 01703 delete mLast; 01704 } 01705 01706 const TQMimeSource* MWMimeSourceFactory::data(const TQString& abs_name) const 01707 { 01708 if (abs_name == mTextFile) 01709 { 01710 TQFileInfo fi(abs_name); 01711 if (fi.isReadable()) 01712 { 01713 TQFile f(abs_name); 01714 if (f.open(IO_ReadOnly) && f.size()) 01715 { 01716 TQByteArray ba(f.size()); 01717 f.readBlock(ba.data(), ba.size()); 01718 TQStoredDrag* sr = new TQStoredDrag(mMimeType); 01719 sr->setEncodedData(ba); 01720 delete mLast; 01721 mLast = sr; 01722 return sr; 01723 } 01724 } 01725 } 01726 return TQMimeSourceFactory::data(abs_name); 01727 }