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