kalarm

messagewin.cpp

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