kalarm

messagewin.cpp

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