/* * messagewin.cpp - displays an alarm message * Program: kalarm * Copyright © 2001-2009 by David Jarvie <djarvie@kde.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kalarm.h" #include <stdlib.h> #include <string.h> #include <tqfile.h> #include <tqfileinfo.h> #include <tqlayout.h> #include <tqpushbutton.h> #include <tqlabel.h> #include <tqwhatsthis.h> #include <tqtooltip.h> #include <tqdragobject.h> #include <tqtextedit.h> #include <tqtimer.h> #include <kstandarddirs.h> #include <tdeaction.h> #include <kstdguiitem.h> #include <tdeaboutdata.h> #include <tdelocale.h> #include <tdeconfig.h> #include <kiconloader.h> #include <kdialog.h> #include <ktextbrowser.h> #include <tdeglobalsettings.h> #include <kmimetype.h> #include <tdemessagebox.h> #include <twin.h> #include <twinmodule.h> #include <kprocess.h> #include <tdeio/netaccess.h> #include <knotifyclient.h> #include <kpushbutton.h> #ifdef WITHOUT_ARTS #include <kaudioplayer.h> #else #include <arts/kartsdispatcher.h> #include <arts/kartsserver.h> #include <arts/kplayobjectfactory.h> #include <arts/kplayobject.h> #endif #include <dcopclient.h> #include <kdebug.h> #include "alarmcalendar.h" #include "deferdlg.h" #include "editdlg.h" #include "functions.h" #include "kalarmapp.h" #include "mainwindow.h" #include "preferences.h" #include "synchtimer.h" #include "messagewin.moc" using namespace KCal; #ifndef WITHOUT_ARTS static const char* KMIX_APP_NAME = "kmix"; static const char* KMIX_DCOP_OBJECT = "Mixer0"; static const char* KMIX_DCOP_WINDOW = "kmix-mainwindow#1"; #endif static const char* KMAIL_DCOP_OBJECT = "KMailIface"; // The delay for enabling message window buttons if a zero delay is // configured, i.e. the windows are placed far from the cursor. static const int proximityButtonDelay = 1000; // (milliseconds) static const int proximityMultiple = 10; // multiple of button height distance from cursor for proximity static bool wantModal(); // A text label widget which can be scrolled and copied with the mouse class MessageText : public TQTextEdit { public: MessageText(const TQString& text, const TQString& context = TQString(), TQWidget* parent = 0, const char* name = 0) : TQTextEdit(text, context, parent, name) { setReadOnly(true); setWordWrap(TQTextEdit::NoWrap); } int scrollBarHeight() const { return horizontalScrollBar()->height(); } int scrollBarWidth() const { return verticalScrollBar()->width(); } virtual TQSize sizeHint() const { return TQSize(contentsWidth() + scrollBarWidth(), contentsHeight() + scrollBarHeight()); } }; class MWMimeSourceFactory : public TQMimeSourceFactory { public: MWMimeSourceFactory(const TQString& absPath, KTextBrowser*); virtual ~MWMimeSourceFactory(); virtual const TQMimeSource* data(const TQString& abs_name) const; private: // Prohibit the following methods virtual void setData(const TQString&, TQMimeSource*) {} virtual void setExtensionType(const TQString&, const char*) {} TQString mTextFile; TQCString mMimeType; mutable const TQMimeSource* mLast; }; // Basic flags for the window static const TQt::WFlags WFLAGS = TQt::WStyle_StaysOnTop | TQt::WDestructiveClose; // Error message bit masks enum { ErrMsg_Speak = 0x01, ErrMsg_AudioFile = 0x02, ErrMsg_Volume = 0x04 }; TQValueList<MessageWin*> MessageWin::mWindowList; TQMap<TQString, unsigned> MessageWin::mErrorMessages; /****************************************************************************** * Construct the message window for the specified alarm. * Other alarms in the supplied event may have been updated by the caller, so * the whole event needs to be stored for updating the calendar file when it is * displayed. */ MessageWin::MessageWin(const KAEvent& event, const KAAlarm& alarm, bool reschedule_event, bool allowDefer) : MainWindowBase(0, "MessageWin", WFLAGS | TQt::WGroupLeader | TQt::WStyle_ContextHelp | (wantModal() ? 0 : TQt::WX11BypassWM)), mMessage(event.cleanText()), mFont(event.font()), mBgColour(event.bgColour()), mFgColour(event.fgColour()), mDateTime((alarm.type() & KAAlarm::REMINDER_ALARM) ? event.mainDateTime(true) : alarm.dateTime(true)), mEventID(event.id()), mAudioFile(event.audioFile()), mVolume(event.soundVolume()), mFadeVolume(event.fadeVolume()), mFadeSeconds(TQMIN(event.fadeSeconds(), 86400)), mDefaultDeferMinutes(event.deferDefaultMinutes()), mAlarmType(alarm.type()), mAction(event.action()), mKMailSerialNumber(event.kmailSerialNumber()), mRestoreHeight(0), mAudioRepeat(event.repeatSound()), mConfirmAck(event.confirmAck()), mShowEdit(!mEventID.isEmpty()), mNoDefer(!allowDefer || alarm.repeatAtLogin()), mInvalid(false), mArtsDispatcher(0), mPlayObject(0), mOldVolume(-1), mEvent(event), mEditButton(0), mDeferButton(0), mSilenceButton(0), mDeferDlg(0), mWinModule(0), mFlags(event.flags()), mLateCancel(event.lateCancel()), mErrorWindow(false), mNoPostAction(alarm.type() & KAAlarm::REMINDER_ALARM), mRecreating(false), mBeep(event.beep()), mSpeak(event.speak()), mRescheduleEvent(reschedule_event), mShown(false), mPositioning(false), mNoCloseConfirm(false), mDisableDeferral(false) { kdDebug(5950) << "MessageWin::MessageWin(event)" << endl; // Set to save settings automatically, but don't save window size. // File alarm window size is saved elsewhere. setAutoSaveSettings(TQString::fromLatin1("MessageWin"), false); initView(); mWindowList.append(this); if (event.autoClose()) mCloseTime = alarm.dateTime().dateTime().addSecs(event.lateCancel() * 60); } /****************************************************************************** * Construct the message window for a specified error message. */ MessageWin::MessageWin(const KAEvent& event, const DateTime& alarmDateTime, const TQStringList& errmsgs) : MainWindowBase(0, "MessageWin", WFLAGS | TQt::WGroupLeader | TQt::WStyle_ContextHelp), mMessage(event.cleanText()), mDateTime(alarmDateTime), mEventID(event.id()), mAlarmType(KAAlarm::MAIN_ALARM), mAction(event.action()), mKMailSerialNumber(0), mErrorMsgs(errmsgs), mRestoreHeight(0), mConfirmAck(false), mShowEdit(false), mNoDefer(true), mInvalid(false), mArtsDispatcher(0), mPlayObject(0), mEvent(event), mEditButton(0), mDeferButton(0), mSilenceButton(0), mDeferDlg(0), mWinModule(0), mErrorWindow(true), mNoPostAction(true), mRecreating(false), mRescheduleEvent(false), mShown(false), mPositioning(false), mNoCloseConfirm(false), mDisableDeferral(false) { kdDebug(5950) << "MessageWin::MessageWin(errmsg)" << endl; initView(); mWindowList.append(this); } /****************************************************************************** * Construct the message window for restoration by session management. * The window is initialised by readProperties(). */ MessageWin::MessageWin() : MainWindowBase(0, "MessageWin", WFLAGS), mArtsDispatcher(0), mPlayObject(0), mEditButton(0), mDeferButton(0), mSilenceButton(0), mDeferDlg(0), mWinModule(0), mErrorWindow(false), mRecreating(false), mRescheduleEvent(false), mShown(false), mPositioning(false), mNoCloseConfirm(false), mDisableDeferral(false) { kdDebug(5950) << "MessageWin::MessageWin(restore)\n"; mWindowList.append(this); } /****************************************************************************** * Destructor. Perform any post-alarm actions before tidying up. */ MessageWin::~MessageWin() { kdDebug(5950) << "MessageWin::~MessageWin(" << mEventID << ")" << endl; stopPlay(); delete mWinModule; mWinModule = 0; mErrorMessages.remove(mEventID); mWindowList.remove(this); if (!mRecreating) { if (!mNoPostAction && !mEvent.postAction().isEmpty()) theApp()->alarmCompleted(mEvent); if (!mWindowList.count()) theApp()->quitIf(); } } /****************************************************************************** * Construct the message window. */ void MessageWin::initView() { bool reminder = (!mErrorWindow && (mAlarmType & KAAlarm::REMINDER_ALARM)); int leading = fontMetrics().leading(); setCaption((mAlarmType & KAAlarm::REMINDER_ALARM) ? i18n("Reminder") : i18n("Message")); TQWidget* topWidget = new TQWidget(this, "messageWinTop"); setCentralWidget(topWidget); TQVBoxLayout* topLayout = new TQVBoxLayout(topWidget, KDialog::marginHint(), KDialog::spacingHint()); if (mDateTime.isValid()) { // Show the alarm date/time, together with an "Advance reminder" text where appropriate TQFrame* frame = 0; TQVBoxLayout* layout = topLayout; if (reminder) { frame = new TQFrame(topWidget); frame->setFrameStyle(TQFrame::Box | TQFrame::Raised); topLayout->addWidget(frame, 0, TQt::AlignHCenter); layout = new TQVBoxLayout(frame, leading + frame->frameWidth(), leading); } // Alarm date/time TQLabel* label = new TQLabel(frame ? frame : topWidget); label->setText(mDateTime.isDateOnly() ? TDEGlobal::locale()->formatDate(mDateTime.date(), true) : TDEGlobal::locale()->formatDateTime(mDateTime.dateTime())); if (!frame) label->setFrameStyle(TQFrame::Box | TQFrame::Raised); label->setFixedSize(label->sizeHint()); layout->addWidget(label, 0, TQt::AlignHCenter); TQWhatsThis::add(label, i18n("The scheduled date/time for the message (as opposed to the actual time of display).")); if (frame) { label = new TQLabel(frame); label->setText(i18n("Reminder")); label->setFixedSize(label->sizeHint()); layout->addWidget(label, 0, TQt::AlignHCenter); frame->setFixedSize(frame->sizeHint()); } } if (!mErrorWindow) { // It's a normal alarm message window switch (mAction) { case KAEvent::FILE: { // Display the file name TQLabel* label = new TQLabel(mMessage, topWidget); label->setFrameStyle(TQFrame::Box | TQFrame::Raised); label->setFixedSize(label->sizeHint()); TQWhatsThis::add(label, i18n("The file whose contents are displayed below")); topLayout->addWidget(label, 0, TQt::AlignHCenter); // Display contents of file bool opened = false; bool dir = false; TQString tmpFile; KURL url(mMessage); if (TDEIO::NetAccess::download(url, tmpFile, MainWindow::mainMainWindow())) { TQFile qfile(tmpFile); TQFileInfo info(qfile); if (!(dir = info.isDir())) { opened = true; KTextBrowser* view = new KTextBrowser(topWidget, "fileContents"); MWMimeSourceFactory msf(tmpFile, view); view->setMinimumSize(view->sizeHint()); topLayout->addWidget(view); // Set the default size to 20 lines square. // Note that after the first file has been displayed, this size // is overridden by the user-set default stored in the config file. // So there is no need to calculate an accurate size. int h = 20*view->fontMetrics().lineSpacing() + 2*view->frameWidth(); view->resize(TQSize(h, h).expandedTo(view->sizeHint())); TQWhatsThis::add(view, i18n("The contents of the file to be displayed")); } TDEIO::NetAccess::removeTempFile(tmpFile); } if (!opened) { // File couldn't be opened bool exists = TDEIO::NetAccess::exists(url, true, MainWindow::mainMainWindow()); mErrorMsgs += dir ? i18n("File is a folder") : exists ? i18n("Failed to open file") : i18n("File not found"); } break; } case KAEvent::MESSAGE: { // Message label // Using MessageText instead of TQLabel allows scrolling and mouse copying MessageText* text = new MessageText(mMessage, TQString(), topWidget); text->setFrameStyle(TQFrame::NoFrame); text->setPaper(mBgColour); text->setPaletteForegroundColor(mFgColour); text->setFont(mFont); int lineSpacing = text->fontMetrics().lineSpacing(); TQSize s = text->sizeHint(); int h = s.height(); text->setMaximumHeight(h + text->scrollBarHeight()); text->setMinimumHeight(TQMIN(h, lineSpacing*4)); text->setMaximumWidth(s.width() + text->scrollBarWidth()); TQWhatsThis::add(text, i18n("The alarm message")); int vspace = lineSpacing/2; int hspace = lineSpacing - KDialog::marginHint(); topLayout->addSpacing(vspace); topLayout->addStretch(); // Don't include any horizontal margins if message is 2/3 screen width if (!mWinModule) mWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP); if (text->sizeHint().width() >= mWinModule->workArea().width()*2/3) topLayout->addWidget(text, 1, TQt::AlignHCenter); else { TQBoxLayout* layout = new TQHBoxLayout(topLayout); layout->addSpacing(hspace); layout->addWidget(text, 1, TQt::AlignHCenter); layout->addSpacing(hspace); } if (!reminder) topLayout->addStretch(); break; } case KAEvent::COMMAND: case KAEvent::EMAIL: default: break; } if (reminder) { // Reminder: show remaining time until the actual alarm mRemainingText = new TQLabel(topWidget); mRemainingText->setFrameStyle(TQFrame::Box | TQFrame::Raised); mRemainingText->setMargin(leading); if (mDateTime.isDateOnly() || TQDate::currentDate().daysTo(mDateTime.date()) > 0) { setRemainingTextDay(); MidnightTimer::connect(TQT_TQOBJECT(this), TQT_SLOT(setRemainingTextDay())); // update every day } else { setRemainingTextMinute(); MinuteTimer::connect(TQT_TQOBJECT(this), TQT_SLOT(setRemainingTextMinute())); // update every minute } topLayout->addWidget(mRemainingText, 0, TQt::AlignHCenter); topLayout->addSpacing(KDialog::spacingHint()); topLayout->addStretch(); } } else { // It's an error message switch (mAction) { case KAEvent::EMAIL: { // Display the email addresses and subject. TQFrame* frame = new TQFrame(topWidget); frame->setFrameStyle(TQFrame::Box | TQFrame::Raised); TQWhatsThis::add(frame, i18n("The email to send")); topLayout->addWidget(frame, 0, TQt::AlignHCenter); TQGridLayout* grid = new TQGridLayout(frame, 2, 2, KDialog::marginHint(), KDialog::spacingHint()); TQLabel* label = new TQLabel(i18n("Email addressee", "To:"), frame); label->setFixedSize(label->sizeHint()); grid->addWidget(label, 0, 0, TQt::AlignLeft); label = new TQLabel(mEvent.emailAddresses("\n"), frame); label->setFixedSize(label->sizeHint()); grid->addWidget(label, 0, 1, TQt::AlignLeft); label = new TQLabel(i18n("Email subject", "Subject:"), frame); label->setFixedSize(label->sizeHint()); grid->addWidget(label, 1, 0, TQt::AlignLeft); label = new TQLabel(mEvent.emailSubject(), frame); label->setFixedSize(label->sizeHint()); grid->addWidget(label, 1, 1, TQt::AlignLeft); break; } case KAEvent::COMMAND: case KAEvent::FILE: case KAEvent::MESSAGE: default: // Just display the error message strings break; } } if (!mErrorMsgs.count()) topWidget->setBackgroundColor(mBgColour); else { setCaption(i18n("Error")); TQBoxLayout* layout = new TQHBoxLayout(topLayout); layout->setMargin(2*KDialog::marginHint()); layout->addStretch(); TQLabel* label = new TQLabel(topWidget); label->setPixmap(DesktopIcon("error")); label->setFixedSize(label->sizeHint()); layout->addWidget(label, 0, TQt::AlignRight); TQBoxLayout* vlayout = new TQVBoxLayout(layout); for (TQStringList::Iterator it = mErrorMsgs.begin(); it != mErrorMsgs.end(); ++it) { label = new TQLabel(*it, topWidget); label->setFixedSize(label->sizeHint()); vlayout->addWidget(label, 0, TQt::AlignLeft); } layout->addStretch(); } TQGridLayout* grid = new TQGridLayout(1, 4); topLayout->addLayout(grid); grid->setColStretch(0, 1); // keep the buttons right-adjusted in the window int gridIndex = 1; // Close button mOkButton = new KPushButton(KStdGuiItem::close(), topWidget); // Prevent accidental acknowledgement of the message if the user is typing when the window appears mOkButton->clearFocus(); mOkButton->setFocusPolicy(TQ_ClickFocus); // don't allow keyboard selection mOkButton->setFixedSize(mOkButton->sizeHint()); connect(mOkButton, TQT_SIGNAL(clicked()), TQT_SLOT(close())); grid->addWidget(mOkButton, 0, gridIndex++, AlignHCenter); TQWhatsThis::add(mOkButton, i18n("Acknowledge the alarm")); if (mShowEdit) { // Edit button mEditButton = new TQPushButton(i18n("&Edit..."), topWidget); mEditButton->setFocusPolicy(TQ_ClickFocus); // don't allow keyboard selection mEditButton->setFixedSize(mEditButton->sizeHint()); connect(mEditButton, TQT_SIGNAL(clicked()), TQT_SLOT(slotEdit())); grid->addWidget(mEditButton, 0, gridIndex++, AlignHCenter); TQWhatsThis::add(mEditButton, i18n("Edit the alarm.")); } if (!mNoDefer) { // Defer button mDeferButton = new TQPushButton(i18n("&Defer..."), topWidget); mDeferButton->setFocusPolicy(TQ_ClickFocus); // don't allow keyboard selection mDeferButton->setFixedSize(mDeferButton->sizeHint()); connect(mDeferButton, TQT_SIGNAL(clicked()), TQT_SLOT(slotDefer())); grid->addWidget(mDeferButton, 0, gridIndex++, AlignHCenter); TQWhatsThis::add(mDeferButton, i18n("Defer the alarm until later.\n" "You will be prompted to specify when the alarm should be redisplayed.")); setDeferralLimit(mEvent); // ensure that button is disabled when alarm can't be deferred any more } #ifndef WITHOUT_ARTS if (!mAudioFile.isEmpty() && (mVolume || mFadeVolume > 0)) { // Silence button to stop sound repetition TQPixmap pixmap = MainBarIcon("player_stop"); mSilenceButton = new TQPushButton(topWidget); mSilenceButton->setPixmap(pixmap); mSilenceButton->setFixedSize(mSilenceButton->sizeHint()); connect(mSilenceButton, TQT_SIGNAL(clicked()), TQT_SLOT(stopPlay())); grid->addWidget(mSilenceButton, 0, gridIndex++, AlignHCenter); TQToolTip::add(mSilenceButton, i18n("Stop sound")); TQWhatsThis::add(mSilenceButton, i18n("Stop playing the sound")); // To avoid getting in a mess, disable the button until sound playing has been set up mSilenceButton->setEnabled(false); } #endif TDEIconLoader iconLoader; if (mKMailSerialNumber) { // KMail button TQPixmap pixmap = iconLoader.loadIcon(TQString::fromLatin1("kmail"), TDEIcon::MainToolbar); mKMailButton = new TQPushButton(topWidget); mKMailButton->setPixmap(pixmap); mKMailButton->setFixedSize(mKMailButton->sizeHint()); connect(mKMailButton, TQT_SIGNAL(clicked()), TQT_SLOT(slotShowKMailMessage())); grid->addWidget(mKMailButton, 0, gridIndex++, AlignHCenter); TQToolTip::add(mKMailButton, i18n("Locate this email in KMail", "Locate in KMail")); TQWhatsThis::add(mKMailButton, i18n("Locate and highlight this email in KMail")); } else mKMailButton = 0; // KAlarm button TQPixmap pixmap = iconLoader.loadIcon(TQString::fromLatin1(kapp->aboutData()->appName()), TDEIcon::MainToolbar); mKAlarmButton = new TQPushButton(topWidget); mKAlarmButton->setPixmap(pixmap); mKAlarmButton->setFixedSize(mKAlarmButton->sizeHint()); connect(mKAlarmButton, TQT_SIGNAL(clicked()), TQT_SLOT(displayMainWindow())); grid->addWidget(mKAlarmButton, 0, gridIndex++, AlignHCenter); TQString actKAlarm = i18n("Activate KAlarm"); TQToolTip::add(mKAlarmButton, actKAlarm); TQWhatsThis::add(mKAlarmButton, actKAlarm); // Disable all buttons initially, to prevent accidental clicking on if they happen to be // under the mouse just as the window appears. mOkButton->setEnabled(false); if (mDeferButton) mDeferButton->setEnabled(false); if (mEditButton) mEditButton->setEnabled(false); if (mKMailButton) mKMailButton->setEnabled(false); mKAlarmButton->setEnabled(false); topLayout->activate(); setMinimumSize(TQSize(grid->sizeHint().width() + 2*KDialog::marginHint(), sizeHint().height())); bool modal = !(getWFlags() & TQt::WX11BypassWM); unsigned long wstate = (modal ? NET::Modal : 0) | NET::Sticky | NET::KeepAbove; WId winid = winId(); KWin::setState(winid, wstate); KWin::setOnAllDesktops(winid, true); } /****************************************************************************** * Set the remaining time text in a reminder window. * Called at the start of every day (at the user-defined start-of-day time). */ void MessageWin::setRemainingTextDay() { TQString text; int days = TQDate::currentDate().daysTo(mDateTime.date()); if (days <= 0 && !mDateTime.isDateOnly()) { // The alarm is due today, so start refreshing every minute MidnightTimer::disconnect(TQT_TQOBJECT(this), TQT_SLOT(setRemainingTextDay())); setRemainingTextMinute(); MinuteTimer::connect(TQT_TQOBJECT(this), TQT_SLOT(setRemainingTextMinute())); // update every minute } else { if (days <= 0) text = i18n("Today"); else if (days % 7) text = i18n("Tomorrow", "in %n days' time", days); else text = i18n("in 1 week's time", "in %n weeks' time", days/7); } mRemainingText->setText(text); } /****************************************************************************** * Set the remaining time text in a reminder window. * Called on every minute boundary. */ void MessageWin::setRemainingTextMinute() { TQString text; int mins = (TQDateTime::currentDateTime().secsTo(mDateTime.dateTime()) + 59) / 60; if (mins < 60) text = i18n("in 1 minute's time", "in %n minutes' time", (mins > 0 ? mins : 0)); else if (mins % 60 == 0) text = i18n("in 1 hour's time", "in %n hours' time", mins/60); else if (mins % 60 == 1) text = i18n("in 1 hour 1 minute's time", "in %n hours 1 minute's time", mins/60); else text = i18n("in 1 hour %1 minutes' time", "in %n hours %1 minutes' time", mins/60).arg(mins%60); mRemainingText->setText(text); } /****************************************************************************** * Save settings to the session managed config file, for restoration * when the program is restored. */ void MessageWin::saveProperties(TDEConfig* config) { if (mShown && !mErrorWindow) { config->writeEntry(TQString::fromLatin1("EventID"), mEventID); config->writeEntry(TQString::fromLatin1("AlarmType"), mAlarmType); config->writeEntry(TQString::fromLatin1("Message"), mMessage); config->writeEntry(TQString::fromLatin1("Type"), mAction); config->writeEntry(TQString::fromLatin1("Font"), mFont); config->writeEntry(TQString::fromLatin1("BgColour"), mBgColour); config->writeEntry(TQString::fromLatin1("FgColour"), mFgColour); config->writeEntry(TQString::fromLatin1("ConfirmAck"), mConfirmAck); if (mDateTime.isValid()) { config->writeEntry(TQString::fromLatin1("Time"), mDateTime.dateTime()); config->writeEntry(TQString::fromLatin1("DateOnly"), mDateTime.isDateOnly()); } if (mCloseTime.isValid()) config->writeEntry(TQString::fromLatin1("Expiry"), mCloseTime); #ifndef WITHOUT_ARTS if (mAudioRepeat && mSilenceButton && mSilenceButton->isEnabled()) { // Only need to restart sound file playing if it's being repeated config->writePathEntry(TQString::fromLatin1("AudioFile"), mAudioFile); config->writeEntry(TQString::fromLatin1("Volume"), static_cast<int>(mVolume * 100)); } #endif config->writeEntry(TQString::fromLatin1("Speak"), mSpeak); config->writeEntry(TQString::fromLatin1("Height"), height()); config->writeEntry(TQString::fromLatin1("DeferMins"), mDefaultDeferMinutes); config->writeEntry(TQString::fromLatin1("NoDefer"), mNoDefer); config->writeEntry(TQString::fromLatin1("NoPostAction"), mNoPostAction); config->writeEntry(TQString::fromLatin1("KMailSerial"), mKMailSerialNumber); } else config->writeEntry(TQString::fromLatin1("Invalid"), true); } /****************************************************************************** * Read settings from the session managed config file. * This function is automatically called whenever the app is being restored. * Read in whatever was saved in saveProperties(). */ void MessageWin::readProperties(TDEConfig* config) { mInvalid = config->readBoolEntry(TQString::fromLatin1("Invalid"), false); mEventID = config->readEntry(TQString::fromLatin1("EventID")); mAlarmType = KAAlarm::Type(config->readNumEntry(TQString::fromLatin1("AlarmType"))); mMessage = config->readEntry(TQString::fromLatin1("Message")); mAction = KAEvent::Action(config->readNumEntry(TQString::fromLatin1("Type"))); mFont = config->readFontEntry(TQString::fromLatin1("Font")); mBgColour = config->readColorEntry(TQString::fromLatin1("BgColour")); mFgColour = config->readColorEntry(TQString::fromLatin1("FgColour")); mConfirmAck = config->readBoolEntry(TQString::fromLatin1("ConfirmAck")); TQDateTime invalidDateTime; TQDateTime dt = config->readDateTimeEntry(TQString::fromLatin1("Time"), &invalidDateTime); bool dateOnly = config->readBoolEntry(TQString::fromLatin1("DateOnly")); mDateTime.set(dt, dateOnly); mCloseTime = config->readDateTimeEntry(TQString::fromLatin1("Expiry"), &invalidDateTime); #ifndef WITHOUT_ARTS mAudioFile = config->readPathEntry(TQString::fromLatin1("AudioFile")); mVolume = static_cast<float>(config->readNumEntry(TQString::fromLatin1("Volume"))) / 100; mFadeVolume = -1; mFadeSeconds = 0; if (!mAudioFile.isEmpty()) mAudioRepeat = true; #endif mSpeak = config->readBoolEntry(TQString::fromLatin1("Speak")); mRestoreHeight = config->readNumEntry(TQString::fromLatin1("Height")); mDefaultDeferMinutes = config->readNumEntry(TQString::fromLatin1("DeferMins")); mNoDefer = config->readBoolEntry(TQString::fromLatin1("NoDefer")); mNoPostAction = config->readBoolEntry(TQString::fromLatin1("NoPostAction")); mKMailSerialNumber = config->readUnsignedLongNumEntry(TQString::fromLatin1("KMailSerial")); mShowEdit = false; kdDebug(5950) << "MessageWin::readProperties(" << mEventID << ")" << endl; if (mAlarmType != KAAlarm::INVALID_ALARM) { // Recreate the event from the calendar file (if possible) if (!mEventID.isEmpty()) { const Event* kcalEvent = AlarmCalendar::activeCalendar()->event(mEventID); if (!kcalEvent) { // It's not in the active calendar, so try the displaying calendar AlarmCalendar* cal = AlarmCalendar::displayCalendar(); if (cal->isOpen()) kcalEvent = cal->event(KAEvent::uid(mEventID, KAEvent::DISPLAYING)); } if (kcalEvent) { mEvent.set(*kcalEvent); mEvent.setUid(KAEvent::ACTIVE); // in case it came from the display calendar mShowEdit = true; } } initView(); } } /****************************************************************************** * Returns the existing message window (if any) which is displaying the event * with the specified ID. */ MessageWin* MessageWin::findEvent(const TQString& eventID) { for (TQValueList<MessageWin*>::Iterator it = mWindowList.begin(); it != mWindowList.end(); ++it) { MessageWin* w = *it; if (w->mEventID == eventID && !w->mErrorWindow) return w; } return 0; } /****************************************************************************** * Beep and play the audio file, as appropriate. */ void MessageWin::playAudio() { if (mBeep) { // Beep using two methods, in case the sound card/speakers are switched off or not working KNotifyClient::beep(); // beep through the sound card & speakers TQApplication::beep(); // beep through the internal speaker } if (!mAudioFile.isEmpty()) { if (!mVolume && mFadeVolume <= 0) return; // ensure zero volume doesn't play anything #ifdef WITHOUT_ARTS TQString play = mAudioFile; TQString file = TQString::fromLatin1("file:"); if (mAudioFile.startsWith(file)) play = mAudioFile.mid(file.length()); KAudioPlayer::play(TQFile::encodeName(play)); #else // An audio file is specified. Because loading it may take some time, // call it on a timer to allow the window to display first. TQTimer::singleShot(0, this, TQT_SLOT(slotPlayAudio())); #endif } else if (mSpeak) { // The message is to be spoken. In case of error messges, // call it on a timer to allow the window to display first. TQTimer::singleShot(0, this, TQT_SLOT(slotSpeak())); } } /****************************************************************************** * Speak the message. * Called asynchronously to avoid delaying the display of the message. */ void MessageWin::slotSpeak() { DCOPClient* client = kapp->dcopClient(); if (!client->isApplicationRegistered("kttsd")) { // kttsd is not running, so start it TQString error; if (kapp->startServiceByDesktopName("kttsd", TQStringList(), &error)) { kdDebug(5950) << "MessageWin::slotSpeak(): failed to start kttsd: " << error << endl; if (!haveErrorMessage(ErrMsg_Speak)) { KMessageBox::detailedError(0, i18n("Unable to speak message"), error); clearErrorMessage(ErrMsg_Speak); } return; } } TQByteArray data; TQDataStream arg(data, IO_WriteOnly); arg << mMessage << ""; if (!client->send("kttsd", "KSpeech", "sayMessage(TQString,TQString)", data)) { kdDebug(5950) << "MessageWin::slotSpeak(): sayMessage() DCOP error" << endl; if (!haveErrorMessage(ErrMsg_Speak)) { KMessageBox::detailedError(0, i18n("Unable to speak message"), i18n("DCOP Call sayMessage failed")); clearErrorMessage(ErrMsg_Speak); } } } /****************************************************************************** * Play the audio file. * Called asynchronously to avoid delaying the display of the message. */ void MessageWin::slotPlayAudio() { #ifndef WITHOUT_ARTS // First check that it exists, to avoid possible crashes if the filename is badly specified MainWindow* mmw = MainWindow::mainMainWindow(); KURL url(mAudioFile); if (!url.isValid() || !TDEIO::NetAccess::exists(url, true, mmw) || !TDEIO::NetAccess::download(url, mLocalAudioFile, mmw)) { kdError(5950) << "MessageWin::playAudio(): Open failure: " << mAudioFile << endl; if (!haveErrorMessage(ErrMsg_AudioFile)) { KMessageBox::error(this, i18n("Cannot open audio file:\n%1").arg(mAudioFile)); clearErrorMessage(ErrMsg_AudioFile); } return; } if (!mArtsDispatcher) { mFadeTimer = 0; mPlayTimer = new TQTimer(this); connect(mPlayTimer, TQT_SIGNAL(timeout()), TQT_SLOT(checkAudioPlay())); mArtsDispatcher = new KArtsDispatcher; mPlayedOnce = false; mAudioFileStart = TQTime::currentTime(); initAudio(true); if (!mPlayObject->object().isNull()) checkAudioPlay(); if (!mUsingKMix && mVolume >= 0) { // Output error message now that everything else has been done. // (Outputting it earlier would delay things until it is acknowledged.) kdWarning(5950) << "Unable to set master volume (KMix: " << mKMixError << ")\n"; if (!haveErrorMessage(ErrMsg_Volume)) { KMessageBox::information(this, i18n("Unable to set master volume\n(Error accessing KMix:\n%1)").arg(mKMixError), TQString(), TQString::fromLatin1("KMixError")); clearErrorMessage(ErrMsg_Volume); } } } #endif } #ifndef WITHOUT_ARTS /****************************************************************************** * Set up the audio file for playing. */ void MessageWin::initAudio(bool firstTime) { KArtsServer aserver; Arts::SoundServerV2 sserver = aserver.server(); KDE::PlayObjectFactory factory(sserver); mPlayObject = factory.createPlayObject(mLocalAudioFile, true); if (firstTime) { // Save the existing sound volume setting for restoration afterwards, // and set the desired volume for the alarm. mUsingKMix = false; float volume = mVolume; // initial volume if (volume >= 0) { // The volume has been specified if (mFadeVolume >= 0) volume = mFadeVolume; // fading, so adjust the initial volume // Get the current master volume from KMix int vol = getKMixVolume(); if (vol >= 0) { mOldVolume = vol; // success mUsingKMix = true; setKMixVolume(static_cast<int>(volume * 100)); } } if (!mUsingKMix) { /* Adjust within the current master volume, because either * a) the volume is not specified, in which case we want to play * at 100% of the current master volume setting, or * b) KMix is not available to set the master volume. */ mOldVolume = sserver.outVolume().scaleFactor(); // save volume for restoration afterwards sserver.outVolume().scaleFactor(volume >= 0 ? volume : 1); } } mSilenceButton->setEnabled(true); mPlayed = false; connect(mPlayObject, TQT_SIGNAL(playObjectCreated()), TQT_SLOT(checkAudioPlay())); if (!mPlayObject->object().isNull()) checkAudioPlay(); } #endif /****************************************************************************** * Called when the audio file has loaded and is ready to play, or on a timer * when play is expected to have completed. * If it is ready to play, start playing it (for the first time or repeated). * If play has not yet completed, wait a bit longer. */ void MessageWin::checkAudioPlay() { #ifndef WITHOUT_ARTS if (!mPlayObject) return; if (mPlayObject->state() == Arts::posIdle) { // The file has loaded and is ready to play, or play has completed if (mPlayedOnce && !mAudioRepeat) { // Play has completed stopPlay(); return; } // Start playing the file, either for the first time or again kdDebug(5950) << "MessageWin::checkAudioPlay(): start\n"; if (!mPlayedOnce) { // Start playing the file for the first time TQTime now = TQTime::currentTime(); mAudioFileLoadSecs = mAudioFileStart.secsTo(now); if (mAudioFileLoadSecs < 0) mAudioFileLoadSecs += 86400; if (mVolume >= 0 && mFadeVolume >= 0 && mFadeSeconds > 0) { // Set up volume fade mAudioFileStart = now; mFadeTimer = new TQTimer(this); connect(mFadeTimer, TQT_SIGNAL(timeout()), TQT_SLOT(slotFade())); mFadeTimer->start(1000); // adjust volume every second } mPlayedOnce = true; } if (mAudioFileLoadSecs < 3) { /* The aRts library takes several attempts before a PlayObject can * be replayed, leaving a gap of perhaps 5 seconds between plays. * So if loading the file takes a short time, it's better to reload * the PlayObject rather than try to replay the same PlayObject. */ if (mPlayed) { // Playing has completed. Start playing again. delete mPlayObject; initAudio(false); if (mPlayObject->object().isNull()) return; } mPlayed = true; mPlayObject->play(); } else { // The file is slow to load, so attempt to replay the PlayObject static Arts::poTime t0((long)0, (long)0, 0, std::string()); Arts::poTime current = mPlayObject->currentTime(); if (current.seconds || current.ms) mPlayObject->seek(t0); else mPlayObject->play(); } } // The sound file is still playing Arts::poTime overall = mPlayObject->overallTime(); Arts::poTime current = mPlayObject->currentTime(); int time = 1000*(overall.seconds - current.seconds) + overall.ms - current.ms; if (time < 0) time = 0; kdDebug(5950) << "MessageWin::checkAudioPlay(): wait for " << (time+100) << "ms\n"; mPlayTimer->start(time + 100, true); #endif } /****************************************************************************** * Called when play completes, the Silence button is clicked, or the window is * closed, to reset the sound volume and terminate audio access. */ void MessageWin::stopPlay() { #ifndef WITHOUT_ARTS if (mArtsDispatcher) { // Restore the sound volume to what it was before the sound file // was played, provided that nothing else has modified it since. if (!mUsingKMix) { KArtsServer aserver; Arts::StereoVolumeControl svc = aserver.server().outVolume(); float currentVolume = svc.scaleFactor(); float eventVolume = mVolume; if (eventVolume < 0) eventVolume = 1; if (currentVolume == eventVolume) svc.scaleFactor(mOldVolume); } else if (mVolume >= 0) { int eventVolume = static_cast<int>(mVolume * 100); int currentVolume = getKMixVolume(); // Volume returned isn't always exactly equal to volume set if (currentVolume < 0 || abs(currentVolume - eventVolume) < 5) setKMixVolume(static_cast<int>(mOldVolume)); } } delete mPlayObject; mPlayObject = 0; delete mArtsDispatcher; mArtsDispatcher = 0; if (!mLocalAudioFile.isEmpty()) { TDEIO::NetAccess::removeTempFile(mLocalAudioFile); // removes it only if it IS a temporary file mLocalAudioFile = TQString(); } if (mSilenceButton) mSilenceButton->setEnabled(false); #endif } /****************************************************************************** * Called every second to fade the volume when the audio file starts playing. */ void MessageWin::slotFade() { #ifndef WITHOUT_ARTS TQTime now = TQTime::currentTime(); int elapsed = mAudioFileStart.secsTo(now); if (elapsed < 0) elapsed += 86400; // it's the next day float volume; if (elapsed >= mFadeSeconds) { // The fade has finished. Set to normal volume. volume = mVolume; delete mFadeTimer; mFadeTimer = 0; if (!mVolume) { kdDebug(5950) << "MessageWin::slotFade(0)\n"; stopPlay(); return; } } else volume = mFadeVolume + ((mVolume - mFadeVolume) * elapsed) / mFadeSeconds; kdDebug(5950) << "MessageWin::slotFade(" << volume << ")\n"; if (mArtsDispatcher) { if (mUsingKMix) setKMixVolume(static_cast<int>(volume * 100)); else if (mArtsDispatcher) { KArtsServer aserver; aserver.server().outVolume().scaleFactor(volume); } } #endif } #ifndef WITHOUT_ARTS /****************************************************************************** * Get the master volume from KMix. * Reply < 0 if failure. */ int MessageWin::getKMixVolume() { if (!KAlarm::runProgram(KMIX_APP_NAME, KMIX_DCOP_WINDOW, mKMixName, mKMixError)) // start KMix if it isn't already running return -1; TQByteArray data, replyData; TQCString replyType; TQDataStream arg(data, IO_WriteOnly); if (!kapp->dcopClient()->call(mKMixName, KMIX_DCOP_OBJECT, "masterVolume()", data, replyType, replyData) || replyType != "int") return -1; int result; TQDataStream reply(replyData, IO_ReadOnly); reply >> result; return (result >= 0) ? result : 0; } /****************************************************************************** * Set the master volume using KMix. */ void MessageWin::setKMixVolume(int percent) { if (!mUsingKMix) return; if (!KAlarm::runProgram(KMIX_APP_NAME, KMIX_DCOP_WINDOW, mKMixName, mKMixError)) // start KMix if it isn't already running return; TQByteArray data; TQDataStream arg(data, IO_WriteOnly); arg << percent; if (!kapp->dcopClient()->send(mKMixName, KMIX_DCOP_OBJECT, "setMasterVolume(int)", data)) kdError(5950) << "MessageWin::setKMixVolume(): kmix dcop call failed\n"; } #endif /****************************************************************************** * Raise the alarm window, re-output any required audio notification, and * reschedule the alarm in the calendar file. */ void MessageWin::repeat(const KAAlarm& alarm) { if (mDeferDlg) { // Cancel any deferral dialogue so that the user notices something's going on, // and also because the deferral time limit will have changed. delete mDeferDlg; mDeferDlg = 0; } const Event* kcalEvent = mEventID.isNull() ? 0 : AlarmCalendar::activeCalendar()->event(mEventID); if (kcalEvent) { mAlarmType = alarm.type(); // store new alarm type for use if it is later deferred if (!mDeferDlg || Preferences::modalMessages()) { raise(); playAudio(); } KAEvent event(*kcalEvent); mDeferButton->setEnabled(true); setDeferralLimit(event); // ensure that button is disabled when alarm can't be deferred any more theApp()->alarmShowing(event, mAlarmType, mDateTime); } } /****************************************************************************** * Display the window. * If windows are being positioned away from the mouse cursor, it is initially * positioned at the top left to slightly reduce the number of times the * windows need to be moved in showEvent(). */ void MessageWin::show() { if (mCloseTime.isValid()) { // Set a timer to auto-close the window int delay = TQDateTime::currentDateTime().secsTo(mCloseTime); if (delay < 0) delay = 0; TQTimer::singleShot(delay * 1000, this, TQT_SLOT(close())); if (!delay) return; // don't show the window if auto-closing is already due } if (Preferences::messageButtonDelay() == 0) move(0, 0); MainWindowBase::show(); } /****************************************************************************** * Returns the window's recommended size exclusive of its frame. * For message windows, the size if limited to fit inside the working area of * the desktop. */ TQSize MessageWin::sizeHint() const { if (mAction != KAEvent::MESSAGE) return MainWindowBase::sizeHint(); if (!mWinModule) mWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP); TQSize frame = frameGeometry().size(); TQSize contents = geometry().size(); TQSize desktop = mWinModule->workArea().size(); TQSize maxSize(desktop.width() - (frame.width() - contents.width()), desktop.height() - (frame.height() - contents.height())); return MainWindowBase::sizeHint().boundedTo(maxSize); } /****************************************************************************** * Called when the window is shown. * The first time, output any required audio notification, and reschedule or * delete the event from the calendar file. */ void MessageWin::showEvent(TQShowEvent* se) { MainWindowBase::showEvent(se); if (!mShown) { if (mErrorWindow) enableButtons(); // don't bother repositioning error messages else { /* Set the window size. * Note that the frame thickness is not yet known when this * method is called, so for large windows the size needs to be * set again later. */ TQSize s = sizeHint(); // fit the window round the message if (mAction == KAEvent::FILE && !mErrorMsgs.count()) KAlarm::readConfigWindowSize("FileMessage", s); resize(s); mButtonDelay = Preferences::messageButtonDelay() * 1000; if (!mButtonDelay) { /* Try to ensure that the window can't accidentally be acknowledged * by the user clicking the mouse just as it appears. * To achieve this, move the window so that the OK button is as far away * from the cursor as possible. If the buttons are still too close to the * cursor, disable the buttons for a short time. * N.B. This can't be done in show(), since the geometry of the window * is not known until it is displayed. Unfortunately by moving the * window in showEvent(), a flicker is unavoidable. * See the TQt documentation on window geometry for more details. */ // PROBLEM: The frame size is not known yet! /* Find the usable area of the desktop or, if the desktop comprises * multiple screens, the usable area of the current screen. (If the * message is displayed on a screen other than that currently being * worked with, it might not be noticed.) */ TQPoint cursor = TQCursor::pos(); if (!mWinModule) mWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP); TQRect desk = mWinModule->workArea(); TQDesktopWidget* dw = TQApplication::desktop(); if (dw->numScreens() > 1) desk &= dw->screenGeometry(dw->screenNumber(cursor)); TQRect frame = frameGeometry(); TQRect rect = geometry(); // Find the offsets from the outside of the frame to the edges of the OK button TQRect button(mOkButton->mapToParent(TQPoint(0, 0)), mOkButton->mapToParent(mOkButton->rect().bottomRight())); int buttonLeft = button.left() + rect.left() - frame.left(); int buttonRight = width() - button.right() + frame.right() - rect.right(); int buttonTop = button.top() + rect.top() - frame.top(); int buttonBottom = height() - button.bottom() + frame.bottom() - rect.bottom(); int centrex = (desk.width() + buttonLeft - buttonRight) / 2; int centrey = (desk.height() + buttonTop - buttonBottom) / 2; int x = (cursor.x() < centrex) ? desk.right() - frame.width() : desk.left(); int y = (cursor.y() < centrey) ? desk.bottom() - frame.height() : desk.top(); // Find the enclosing rectangle for the new button positions // and check if the cursor is too near TQRect buttons = mOkButton->geometry().unite(mKAlarmButton->geometry()); buttons.moveBy(rect.left() + x - frame.left(), rect.top() + y - frame.top()); int minDistance = proximityMultiple * mOkButton->height(); if ((abs(cursor.x() - buttons.left()) < minDistance || abs(cursor.x() - buttons.right()) < minDistance) && (abs(cursor.y() - buttons.top()) < minDistance || abs(cursor.y() - buttons.bottom()) < minDistance)) mButtonDelay = proximityButtonDelay; // too near - disable buttons initially if (x != frame.left() || y != frame.top()) { mPositioning = true; move(x, y); } } if (!mPositioning) displayComplete(); // play audio, etc. if (mAction == KAEvent::MESSAGE) { // Set the window size once the frame size is known TQTimer::singleShot(0, this, TQT_SLOT(setMaxSize())); } } mShown = true; } } /****************************************************************************** * Called when the window has been moved. */ void MessageWin::moveEvent(TQMoveEvent* e) { MainWindowBase::moveEvent(e); if (mPositioning) { // The window has just been initially positioned mPositioning = false; displayComplete(); // play audio, etc. } } /****************************************************************************** * Reset the iniital window size if it exceeds the working area of the desktop. */ void MessageWin::setMaxSize() { TQSize s = sizeHint(); if (width() > s.width() || height() > s.height()) resize(s); } /****************************************************************************** * Called when the window has been displayed properly (in its correct position), * to play sounds and reschedule the event. */ void MessageWin::displayComplete() { playAudio(); if (mRescheduleEvent) theApp()->alarmShowing(mEvent, mAlarmType, mDateTime); // Enable the window's buttons either now or after the configured delay if (mButtonDelay > 0) TQTimer::singleShot(mButtonDelay, this, TQT_SLOT(enableButtons())); else enableButtons(); } /****************************************************************************** * Enable the window's buttons. */ void MessageWin::enableButtons() { mOkButton->setEnabled(true); mKAlarmButton->setEnabled(true); if (mDeferButton && !mDisableDeferral) mDeferButton->setEnabled(true); if (mEditButton) mEditButton->setEnabled(true); if (mKMailButton) mKMailButton->setEnabled(true); } /****************************************************************************** * Called when the window's size has changed (before it is painted). */ void MessageWin::resizeEvent(TQResizeEvent* re) { if (mRestoreHeight) { // Restore the window height on session restoration if (mRestoreHeight != re->size().height()) { TQSize size = re->size(); size.setHeight(mRestoreHeight); resize(size); } else if (isVisible()) mRestoreHeight = 0; } else { if (mShown && mAction == KAEvent::FILE && !mErrorMsgs.count()) KAlarm::writeConfigWindowSize("FileMessage", re->size()); MainWindowBase::resizeEvent(re); } } /****************************************************************************** * Called when a close event is received. * Only quits the application if there is no system tray icon displayed. */ void MessageWin::closeEvent(TQCloseEvent* ce) { // Don't prompt or delete the alarm from the display calendar if the session is closing if (!mErrorWindow && !theApp()->sessionClosingDown()) { if (mConfirmAck && !mNoCloseConfirm) { // Ask for confirmation of acknowledgement. Use warningYesNo() because its default is No. if (KMessageBox::warningYesNo(this, i18n("Do you really want to acknowledge this alarm?"), i18n("Acknowledge Alarm"), i18n("&Acknowledge"), KStdGuiItem::cancel()) != KMessageBox::Yes) { ce->ignore(); return; } } if (!mEventID.isNull()) { // Delete from the display calendar KAlarm::deleteDisplayEvent(KAEvent::uid(mEventID, KAEvent::DISPLAYING)); } } MainWindowBase::closeEvent(ce); } /****************************************************************************** * Called when the KMail button is clicked. * Tells KMail to display the email message displayed in this message window. */ void MessageWin::slotShowKMailMessage() { kdDebug(5950) << "MessageWin::slotShowKMailMessage()\n"; if (!mKMailSerialNumber) return; TQString err = KAlarm::runKMail(false); if (!err.isNull()) { KMessageBox::sorry(this, err); return; } TQCString replyType; TQByteArray data, replyData; TQDataStream arg(data, IO_WriteOnly); arg << (TQ_UINT32)mKMailSerialNumber << TQString(); if (kapp->dcopClient()->call("kmail", KMAIL_DCOP_OBJECT, "showMail(TQ_UINT32,TQString)", data, replyType, replyData) && replyType == "bool") { bool result; TQDataStream replyStream(replyData, IO_ReadOnly); replyStream >> result; if (result) return; // success } kdError(5950) << "MessageWin::slotShowKMailMessage(): kmail dcop call failed\n"; KMessageBox::sorry(this, i18n("Unable to locate this email in KMail")); } /****************************************************************************** * Called when the Edit... button is clicked. * Displays the alarm edit dialog. */ void MessageWin::slotEdit() { kdDebug(5950) << "MessageWin::slotEdit()" << endl; EditAlarmDlg editDlg(false, i18n("Edit Alarm"), this, "editDlg", &mEvent); if (editDlg.exec() == TQDialog::Accepted) { KAEvent event; editDlg.getEvent(event); // Update the displayed lists and the calendar file KAlarm::UpdateStatus status; if (AlarmCalendar::activeCalendar()->event(mEventID)) { // The old alarm hasn't expired yet, so replace it status = KAlarm::modifyEvent(mEvent, event, 0, &editDlg); Undo::saveEdit(mEvent, event); } else { // The old event has expired, so simply create a new one status = KAlarm::addEvent(event, 0, &editDlg); Undo::saveAdd(event); } if (status == KAlarm::UPDATE_KORG_ERR) KAlarm::displayKOrgUpdateError(&editDlg, KAlarm::KORG_ERR_MODIFY, 1); KAlarm::outputAlarmWarnings(&editDlg, &event); // Close the alarm window mNoCloseConfirm = true; // allow window to close without confirmation prompt close(); } } /****************************************************************************** * Set up to disable the defer button when the deferral limit is reached. */ void MessageWin::setDeferralLimit(const KAEvent& event) { if (mDeferButton) { mDeferLimit = event.deferralLimit().dateTime(); MidnightTimer::connect(TQT_TQOBJECT(this), TQT_SLOT(checkDeferralLimit())); // check every day mDisableDeferral = false; checkDeferralLimit(); } } /****************************************************************************** * Check whether the deferral limit has been reached. * If so, disable the Defer button. * N.B. Ideally, just a single TQTimer::singleShot() call would be made to disable * the defer button at the corret time. But for a 32-bit integer, the * milliseconds parameter overflows in about 25 days, so instead a daily * check is done until the day when the deferral limit is reached, followed * by a non-overflowing TQTimer::singleShot() call. */ void MessageWin::checkDeferralLimit() { if (!mDeferButton || !mDeferLimit.isValid()) return; int n = TQDate::currentDate().daysTo(mDeferLimit.date()); if (n > 0) return; MidnightTimer::disconnect(TQT_TQOBJECT(this), TQT_SLOT(checkDeferralLimit())); if (n == 0) { // The deferral limit will be reached today n = TQTime::currentTime().secsTo(mDeferLimit.time()); if (n > 0) { TQTimer::singleShot(n * 1000, this, TQT_SLOT(checkDeferralLimit())); return; } } mDeferButton->setEnabled(false); mDisableDeferral = true; } /****************************************************************************** * Called when the Defer... button is clicked. * Displays the defer message dialog. */ void MessageWin::slotDefer() { mDeferDlg = new DeferAlarmDlg(i18n("Defer Alarm"), TQDateTime(TQDateTime::currentDateTime().addSecs(60)), false, this, "deferDlg"); if (mDefaultDeferMinutes > 0) mDeferDlg->setDeferMinutes(mDefaultDeferMinutes); mDeferDlg->setLimit(mEventID); if (!Preferences::modalMessages()) lower(); if (mDeferDlg->exec() == TQDialog::Accepted) { DateTime dateTime = mDeferDlg->getDateTime(); int delayMins = mDeferDlg->deferMinutes(); const Event* kcalEvent = mEventID.isNull() ? 0 : AlarmCalendar::activeCalendar()->event(mEventID); if (kcalEvent) { // The event still exists in the calendar file. KAEvent event(*kcalEvent); bool repeat = event.defer(dateTime, (mAlarmType & KAAlarm::REMINDER_ALARM), true); event.setDeferDefaultMinutes(delayMins); KAlarm::updateEvent(event, 0, mDeferDlg, true, !repeat); if (event.deferred()) mNoPostAction = true; } else { KAEvent event; kcalEvent = AlarmCalendar::displayCalendar()->event(KAEvent::uid(mEventID, KAEvent::DISPLAYING)); if (kcalEvent) { event.reinstateFromDisplaying(KAEvent(*kcalEvent)); event.defer(dateTime, (mAlarmType & KAAlarm::REMINDER_ALARM), true); } else { // The event doesn't exist any more !?!, so create a new one event.set(dateTime.dateTime(), mMessage, mBgColour, mFgColour, mFont, mAction, mLateCancel, mFlags); event.setAudioFile(mAudioFile, mVolume, mFadeVolume, mFadeSeconds); event.setArchive(); event.setEventID(mEventID); } event.setDeferDefaultMinutes(delayMins); // Add the event back into the calendar file, retaining its ID // and not updating KOrganizer KAlarm::addEvent(event, 0, mDeferDlg, true, false); if (event.deferred()) mNoPostAction = true; if (kcalEvent) { event.setUid(KAEvent::EXPIRED); KAlarm::deleteEvent(event, false); } } if (theApp()->wantRunInSystemTray()) { // Alarms are to be displayed only if the system tray icon is running, // so start it if necessary so that the deferred alarm will be shown. theApp()->displayTrayIcon(true); } mNoCloseConfirm = true; // allow window to close without confirmation prompt close(); } else raise(); delete mDeferDlg; mDeferDlg = 0; } /****************************************************************************** * Called when the KAlarm icon button in the message window is clicked. * Displays the main window, with the appropriate alarm selected. */ void MessageWin::displayMainWindow() { KAlarm::displayMainWindowSelected(mEventID); } /****************************************************************************** * Check whether the specified error message is already displayed for this * alarm, and note that it will now be displayed. * Reply = true if message is already displayed. */ bool MessageWin::haveErrorMessage(unsigned msg) const { if (!mErrorMessages.contains(mEventID)) mErrorMessages.insert(mEventID, 0); bool result = (mErrorMessages[mEventID] & msg); mErrorMessages[mEventID] |= msg; return result; } void MessageWin::clearErrorMessage(unsigned msg) const { if (mErrorMessages.contains(mEventID)) { if (mErrorMessages[mEventID] == msg) mErrorMessages.remove(mEventID); else mErrorMessages[mEventID] &= ~msg; } } /****************************************************************************** * Check whether the message window should be modal, i.e. with title bar etc. * Normally this follows the Preferences setting, but if there is a full screen * window displayed, on X11 the message window has to bypass the window manager * in order to display on top of it (which has the side effect that it will have * no window decoration). */ bool wantModal() { bool modal = Preferences::modalMessages(); if (modal) { KWinModule wm(0, KWinModule::INFO_DESKTOP); KWin::WindowInfo wi = KWin::windowInfo(wm.activeWindow(), NET::WMState); modal = !(wi.valid() && wi.hasState(NET::FullScreen)); } return modal; } /*============================================================================= = Class MWMimeSourceFactory * Gets the mime type of a text file from not only its extension (as per * TQMimeSourceFactory), but also from its contents. This allows the detection * of plain text files without file name extensions. =============================================================================*/ MWMimeSourceFactory::MWMimeSourceFactory(const TQString& absPath, KTextBrowser* view) : TQMimeSourceFactory(), mMimeType("text/plain"), mLast(0) { view->setMimeSourceFactory(this); TQString type = KMimeType::findByPath(absPath)->name(); switch (KAlarm::fileType(type)) { case KAlarm::TextPlain: case KAlarm::TextFormatted: mMimeType = type.latin1(); // fall through to 'TextApplication' case KAlarm::TextApplication: default: // It's assumed to be a text file mTextFile = absPath; view->TQTextBrowser::setSource(absPath); break; case KAlarm::Image: // It's an image file TQString text = "<img source=\""; text += absPath; text += "\">"; view->setText(text); break; } setFilePath(TQFileInfo(absPath).dirPath(true)); } MWMimeSourceFactory::~MWMimeSourceFactory() { delete mLast; } const TQMimeSource* MWMimeSourceFactory::data(const TQString& abs_name) const { if (abs_name == mTextFile) { TQFileInfo fi(abs_name); if (fi.isReadable()) { TQFile f(abs_name); if (f.open(IO_ReadOnly) && f.size()) { TQByteArray ba(f.size()); f.readBlock(ba.data(), ba.size()); TQStoredDrag* sr = new TQStoredDrag(mMimeType); sr->setEncodedData(ba); delete mLast; mLast = sr; return sr; } } } return TQMimeSourceFactory::data(abs_name); }