kalarm

messagewin.cpp

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