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