kalarm

messagewin.cpp

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