korganizer

alarmdialog.cpp

00001 /*
00002     This file is part of the KOrganizer alarm daemon.
00003 
00004     Copyright (c) 2000,2003 Cornelius Schumacher <schumacher@kde.org>
00005     Copyright (c) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
00006 
00007     This program is free software; you can redistribute it and/or modify
00008     it under the terms of the GNU General Public License as published by
00009     the Free Software Foundation; either version 2 of the License, or
00010     (at your option) any later version.
00011 
00012     This program is distributed in the hope that it will be useful,
00013     but WITHOUT ANY WARRANTY; without even the implied warranty of
00014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00015     GNU General Public License for more details.
00016 
00017     You should have received a copy of the GNU General Public License
00018     along with this program; if not, write to the Free Software
00019     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020 
00021     As a special exception, permission is given to link this program
00022     with any edition of Qt, and distribute the resulting executable,
00023     without including the source code for Qt in the source distribution.
00024 */
00025 
00026 #include <qhbox.h>
00027 #include <qvbox.h>
00028 #include <qlabel.h>
00029 #include <qfile.h>
00030 #include <qspinbox.h>
00031 #include <qlayout.h>
00032 #include <qpushbutton.h>
00033 #include <qcstring.h>
00034 #include <qdatastream.h>
00035 
00036 #include <dcopclient.h>
00037 #include <dcopref.h>
00038 #include <kapplication.h>
00039 #include <kconfig.h>
00040 #include <kdcopservicestarter.h>
00041 #include <kiconloader.h>
00042 #include <klocale.h>
00043 #include <kprocess.h>
00044 #include <kaudioplayer.h>
00045 #include <kdebug.h>
00046 #include <kmessagebox.h>
00047 #include <knotifyclient.h>
00048 #include <kcombobox.h>
00049 #include <klistview.h>
00050 #include <kwin.h>
00051 #include <klockfile.h>
00052 
00053 #include <libkcal/event.h>
00054 #include <libkcal/incidenceformatter.h>
00055 
00056 #include "koeventviewer.h"
00057 
00058 #include "alarmdialog.h"
00059 #include "alarmdialog.moc"
00060 
00061 class AlarmListItem : public KListViewItem
00062 {
00063   public:
00064     AlarmListItem( Incidence *incidence, QListView *parent ) :
00065       KListViewItem( parent ),
00066       mIncidence( incidence->clone() ),
00067       mNotified( false )
00068     {}
00069 
00070     ~AlarmListItem()
00071     {
00072       delete mIncidence;
00073     }
00074 
00075     int compare( KListViewItem *item, int iCol, bool bAscending ) const;
00076 
00077     QString mDisplayText;
00078     Incidence *mIncidence;
00079     QDateTime mRemindAt;
00080     QDateTime mHappening;
00081     bool mNotified;
00082 };
00083 
00084 int AlarmListItem::compare( KListViewItem *item, int iCol, bool bAscending ) const
00085 {
00086   if ( iCol == 1 ) {
00087     AlarmListItem *pItem = dynamic_cast<AlarmListItem *>( item );
00088     return mHappening < pItem->mHappening;
00089   } else {
00090     return KListViewItem::compare( item, iCol, bAscending );
00091   }
00092 }
00093 
00094 typedef QValueList<AlarmListItem*> ItemList;
00095 
00096 AlarmDialog::AlarmDialog( KCal::Calendar *calendar, QWidget *parent, const char *name )
00097   : KDialogBase( Plain, WType_TopLevel | WStyle_Customize | WStyle_StaysOnTop |
00098                  WStyle_DialogBorder,
00099                  parent, name, false, i18n("Reminder"),
00100                  Ok | User1 | User2 | User3, NoDefault,
00101                  false, i18n("Edit..."), i18n("Dismiss All"), i18n("Dismiss Reminder") ),
00102                  mCalendar( calendar ), mSuspendTimer(this)
00103 {
00104   // User1 => Edit...
00105   // User2 => Dismiss All
00106   // User3 => Dismiss Selected
00107   //    Ok => Suspend
00108 
00109   KGlobal::iconLoader()->addAppDir( "kdepim" );
00110   setButtonOK( i18n( "Suspend" ) );
00111 
00112   QWidget *topBox = plainPage();
00113   QBoxLayout *topLayout = new QVBoxLayout( topBox );
00114   topLayout->setSpacing( spacingHint() );
00115 
00116   QLabel *label = new QLabel( i18n("The following items triggered reminders:"),
00117                               topBox );
00118   topLayout->addWidget( label );
00119 
00120   mIncidenceListView = new KListView( topBox );
00121   mIncidenceListView->addColumn( i18n( "Summary" ) );
00122   mIncidenceListView->addColumn( i18n( "Due" ) );
00123   mIncidenceListView->setSorting( 0, true );
00124   mIncidenceListView->setSorting( 1, true );
00125   mIncidenceListView->setSortColumn( 1 );
00126   mIncidenceListView->setShowSortIndicator( true );
00127   mIncidenceListView->setAllColumnsShowFocus( true );
00128   mIncidenceListView->setSelectionMode( QListView::Extended );
00129   topLayout->addWidget( mIncidenceListView );
00130   connect( mIncidenceListView, SIGNAL(selectionChanged()), SLOT(updateButtons()) );
00131   connect( mIncidenceListView, SIGNAL(doubleClicked(QListViewItem*)), SLOT(edit()) );
00132   connect( mIncidenceListView, SIGNAL(currentChanged(QListViewItem*)), SLOT(showDetails()) );
00133   connect( mIncidenceListView, SIGNAL(selectionChanged()), SLOT(showDetails()) );
00134 
00135   mDetailView = new KOEventViewer( mCalendar, topBox );
00136   mDetailView->setFocus(); // set focus here to start with to make it harder
00137                            // to hit return by mistake and dismiss a reminder.
00138   topLayout->addWidget( mDetailView );
00139 
00140   QHBox *suspendBox = new QHBox( topBox );
00141   suspendBox->setSpacing( spacingHint() );
00142   topLayout->addWidget( suspendBox );
00143 
00144   QLabel *l = new QLabel( i18n("Suspend &duration:"), suspendBox );
00145   mSuspendSpin = new QSpinBox( 1, 9999, 1, suspendBox );
00146   mSuspendSpin->setValue( 5 );  // default suspend duration
00147   l->setBuddy( mSuspendSpin );
00148 
00149   mSuspendUnit = new KComboBox( suspendBox );
00150   mSuspendUnit->insertItem( i18n("minute(s)") );
00151   mSuspendUnit->insertItem( i18n("hour(s)") );
00152   mSuspendUnit->insertItem( i18n("day(s)") );
00153   mSuspendUnit->insertItem( i18n("week(s)") );
00154   connect( &mSuspendTimer, SIGNAL(timeout()), SLOT(wakeUp()) );
00155 
00156   setMinimumSize( 300, 200 );
00157 }
00158 
00159 AlarmDialog::~AlarmDialog()
00160 {
00161   mIncidenceListView->clear();
00162 }
00163 
00164 void AlarmDialog::addIncidence( Incidence *incidence,
00165                                 const QDateTime &reminderAt,
00166                                 const QString &displayText )
00167 {
00168   AlarmListItem *item = new AlarmListItem( incidence, mIncidenceListView );
00169   item->setText( 0, incidence->summary() );
00170   item->mRemindAt = reminderAt;
00171   item->mDisplayText = displayText;
00172 
00173   //TODO: this function needs to consider all Display type alarms in each incidence.
00174 
00175   Event *event;
00176   Todo *todo;
00177   Alarm *alarm = incidence->alarms().first();
00178   if ( ( event = dynamic_cast<Event *>( incidence ) ) ) {
00179     item->setPixmap( 0, SmallIcon( "appointment" ) );
00180     if ( event->doesRecur() ) {
00181       QDateTime nextStart = event->recurrence()->getNextDateTime( reminderAt );
00182       if ( nextStart.isValid() ) {
00183         item->mHappening = nextStart;
00184         item->setText( 1, KGlobal::locale()->formatDateTime( nextStart ) );
00185       }
00186     }
00187     if ( item->text( 1 ).isEmpty() ) {
00188       QDateTime qdt;
00189       if ( alarm->hasStartOffset() ) {
00190         qdt = event->dtStart();
00191       } else {
00192         qdt = event->dtEnd();
00193       }
00194       item->mHappening = qdt;
00195       item->setText( 1, IncidenceFormatter::dateTimeToString( qdt, false, true ) );
00196     }
00197   } else if ( ( todo = dynamic_cast<Todo *>( incidence ) ) ) {
00198     item->setPixmap( 0, SmallIcon( "todo" ) );
00199     if ( todo->doesRecur() ) {
00200       QDateTime nextStart = todo->recurrence()->getNextDateTime( reminderAt );
00201       if ( nextStart.isValid() ) {
00202         item->mHappening = nextStart;
00203         item->setText( 1, KGlobal::locale()->formatDateTime( nextStart ) );
00204       }
00205     }
00206     if ( item->text( 1 ).isEmpty() ) {
00207       QDateTime qdt;
00208       if ( alarm->hasStartOffset() && todo->dtStart().isValid() ) {
00209         qdt = todo->dtStart();
00210       } else {
00211         qdt = todo->dtDue();
00212       }
00213       item->mHappening = qdt;
00214       item->setText( 1, IncidenceFormatter::dateTimeToString( qdt, false, true ) );
00215     }
00216   }
00217 
00218   if ( activeCount() == 1 ) {// previously empty
00219     mIncidenceListView->clearSelection();
00220     item->setSelected( true );
00221   }
00222   showDetails();
00223 }
00224 
00225 void AlarmDialog::slotOk()
00226 {
00227   suspend();
00228 }
00229 
00230 void AlarmDialog::slotUser1()
00231 {
00232   edit();
00233 }
00234 
00235 void AlarmDialog::slotUser2()
00236 {
00237   dismissAll();
00238 }
00239 
00240 void AlarmDialog::slotUser3()
00241 {
00242   dismissCurrent();
00243 }
00244 
00245 void AlarmDialog::dismissCurrent()
00246 {
00247   ItemList selection = selectedItems();
00248   for ( ItemList::Iterator it = selection.begin(); it != selection.end(); ++it ) {
00249     if ( (*it)->itemBelow() )
00250       (*it)->itemBelow()->setSelected( true );
00251     else if ( (*it)->itemAbove() )
00252       (*it)->itemAbove()->setSelected( true );
00253     delete *it;
00254   }
00255   if ( activeCount() == 0 )
00256     accept();
00257   else {
00258     updateButtons();
00259     showDetails();
00260   }
00261   emit reminderCount( activeCount() );
00262 }
00263 
00264 void AlarmDialog::dismissAll()
00265 {
00266   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ) {
00267     AlarmListItem *item = static_cast<AlarmListItem*>( it.current() );
00268     if ( !item->isVisible() ) {
00269       ++it;
00270       continue;
00271     }
00272     delete item;
00273   }
00274   setTimer();
00275   accept();
00276   emit reminderCount( activeCount() );
00277 }
00278 
00279 void AlarmDialog::edit()
00280 {
00281   ItemList selection = selectedItems();
00282   if ( selection.count() != 1 ) {
00283     return;
00284   }
00285   Incidence *incidence = selection.first()->mIncidence;
00286   if ( incidence->isReadOnly() ) {
00287     KMessageBox::sorry(
00288       this,
00289       i18n( "\"%1\" is a read-only item so modifications are not possible." ).
00290       arg( incidence->summary() ) );
00291     return;
00292   }
00293 
00294   if ( !ensureKorganizerRunning() ) {
00295     KMessageBox::error(
00296       this,
00297       i18n( "Could not start KOrganizer so editing is not possible." ) );
00298     return;
00299   }
00300 
00301   //kdDebug(5890) << "editing incidence " << incidence->summary() << endl;
00302   if ( !kapp->dcopClient()->send( "korganizer", "KOrganizerIface",
00303                                   "editIncidence(QString)",
00304                                   incidence->uid() ) ) {
00305     KMessageBox::error(
00306       this,
00307       i18n( "An internal KOrganizer error occurred attempting to start the incidence editor" ) );
00308     return;
00309   }
00310 
00311   // get desktop # where korganizer (or kontact) runs
00312   QByteArray replyData;
00313   QCString object, replyType;
00314   object = kapp->dcopClient()->isApplicationRegistered( "kontact" ) ?
00315            "kontact-mainwindow#1" : "KOrganizer MainWindow";
00316   if (!kapp->dcopClient()->call( "korganizer", object,
00317                             "getWinID()", 0, replyType, replyData, true, -1 ) ) {
00318   }
00319 
00320   if ( replyType == "int" ) {
00321     int desktop, window;
00322     QDataStream ds( replyData, IO_ReadOnly );
00323     ds >> window;
00324     desktop = KWin::windowInfo( window ).desktop();
00325 
00326     if ( KWin::currentDesktop() == desktop ) {
00327       KWin::iconifyWindow( winId(), false );
00328     } else {
00329       KWin::setCurrentDesktop( desktop );
00330     }
00331     KWin::activateWindow( KWin::transientFor( window ) );
00332   }
00333 }
00334 
00335 void AlarmDialog::suspend()
00336 {
00337   if ( !isVisible() )
00338     return;
00339 
00340   int unit=1;
00341   switch (mSuspendUnit->currentItem()) {
00342     case 3: // weeks
00343       unit *=  7;
00344     case 2: // days
00345       unit *= 24;
00346     case 1: // hours
00347       unit *= 60;
00348     case 0: // minutes
00349       unit *= 60;
00350     default:
00351       break;
00352   }
00353 
00354   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00355     AlarmListItem * item = static_cast<AlarmListItem*>( it.current() );
00356     if ( item->isSelected() && item->isVisible() ) {
00357       item->setVisible( false );
00358       item->setSelected( false );
00359       item->mRemindAt = QDateTime::currentDateTime().addSecs( unit * mSuspendSpin->value() );
00360       item->mNotified = false;
00361     }
00362   }
00363 
00364   setTimer();
00365   if ( activeCount() == 0 )
00366     accept();
00367   else {
00368     updateButtons();
00369     showDetails();
00370   }
00371   emit reminderCount( activeCount() );
00372 }
00373 
00374 void AlarmDialog::setTimer()
00375 {
00376   int nextReminderAt = -1;
00377   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00378     AlarmListItem * item = static_cast<AlarmListItem*>( it.current() );
00379     if ( item->mRemindAt > QDateTime::currentDateTime() ) {
00380       int secs = QDateTime::currentDateTime().secsTo( item->mRemindAt );
00381       nextReminderAt = nextReminderAt <= 0 ? secs : QMIN( nextReminderAt, secs );
00382     }
00383   }
00384 
00385   if ( nextReminderAt >= 0 ) {
00386     mSuspendTimer.stop();
00387     mSuspendTimer.start( 1000 * (nextReminderAt + 1), true );
00388   }
00389 }
00390 
00391 void AlarmDialog::show()
00392 {
00393   mIncidenceListView->sort();
00394 
00395   mIncidenceListView->clearSelection();
00396   if ( mIncidenceListView->firstChild() )
00397     mIncidenceListView->firstChild()->setSelected( true );
00398 
00399   updateButtons();
00400   showDetails();
00401 
00402   KDialogBase::show();
00403   KWin::setState( winId(), NET::KeepAbove );
00404   KWin::setOnAllDesktops( winId(), true );
00405   eventNotification();
00406 }
00407 
00408 void AlarmDialog::eventNotification()
00409 {
00410   bool beeped = false, found = false;
00411 
00412   QValueList<AlarmListItem*> list;
00413   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00414     AlarmListItem *item = static_cast<AlarmListItem*>( it.current() );
00415     if ( !item->isVisible() || item->mNotified )
00416       continue;
00417     found = true;
00418     item->mNotified = true;
00419     Alarm::List alarms = item->mIncidence->alarms();
00420     Alarm::List::ConstIterator it;
00421     for ( it = alarms.begin(); it != alarms.end(); ++it ) {
00422       Alarm *alarm = *it;
00423       // FIXME: Check whether this should be done for all multiple alarms
00424       if (alarm->type() == Alarm::Procedure) {
00425         // FIXME: Add a message box asking whether the procedure should really be executed
00426         kdDebug(5890) << "Starting program: '" << alarm->programFile() << "'" << endl;
00427         KProcess proc;
00428         proc << QFile::encodeName(alarm->programFile());
00429         proc.start(KProcess::DontCare);
00430       }
00431       else if (alarm->type() == Alarm::Audio) {
00432         beeped = true;
00433         KAudioPlayer::play(QFile::encodeName(alarm->audioFile()));
00434       }
00435     }
00436   }
00437 
00438   if ( !beeped && found ) {
00439     KNotifyClient::beep();
00440   }
00441 }
00442 
00443 void AlarmDialog::wakeUp()
00444 {
00445   bool activeReminders = false;
00446   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00447     AlarmListItem * item = static_cast<AlarmListItem*>( it.current() );
00448     if ( item->mRemindAt <= QDateTime::currentDateTime() ) {
00449       if ( !item->isVisible() ) {
00450         item->setVisible( true );
00451         item->setSelected( false );
00452       }
00453       activeReminders = true;
00454     } else {
00455       item->setVisible( false );
00456     }
00457   }
00458 
00459   if ( activeReminders )
00460     show();
00461   setTimer();
00462   showDetails();
00463   emit reminderCount( activeCount() );
00464 }
00465 
00466 void AlarmDialog::slotSave()
00467 {
00468   KConfig *config = kapp->config();
00469   KLockFile::Ptr lock = config->lockFile();
00470   if ( lock.data()->lock() != KLockFile::LockOK )
00471     return;
00472 
00473   config->setGroup( "General" );
00474   int numReminders = config->readNumEntry("Reminders", 0);
00475 
00476   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00477     AlarmListItem * item = static_cast<AlarmListItem*>( it.current() );
00478     config->setGroup( QString("Incidence-%1").arg(numReminders + 1) );
00479     config->writeEntry( "UID", item->mIncidence->uid() );
00480     config->writeEntry( "RemindAt", item->mRemindAt );
00481     ++numReminders;
00482   }
00483 
00484   config->setGroup( "General" );
00485   config->writeEntry( "Reminders", numReminders );
00486   config->sync();
00487   lock.data()->unlock();
00488 }
00489 
00490 void AlarmDialog::updateButtons()
00491 {
00492   ItemList selection = selectedItems();
00493   enableButton( User1, selection.count() == 1 ); // can only edit 1 at a time
00494   enableButton( User3, selection.count() > 0 );  // dismiss 1 or more
00495   enableButton( Ok, selection.count() > 0 );     // suspend 1 or more
00496 }
00497 
00498 QValueList< AlarmListItem * > AlarmDialog::selectedItems() const
00499 {
00500   QValueList<AlarmListItem*> list;
00501   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00502     if ( it.current()->isSelected() )
00503       list.append( static_cast<AlarmListItem*>( it.current() ) );
00504   }
00505   return list;
00506 }
00507 
00508 int AlarmDialog::activeCount()
00509 {
00510   int count = 0;
00511   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00512     AlarmListItem * item = static_cast<AlarmListItem*>( it.current() );
00513     if ( item->isVisible() )
00514       ++count;
00515   }
00516   return count;
00517 }
00518 
00519 void AlarmDialog::suspendAll()
00520 {
00521   mIncidenceListView->clearSelection();
00522   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00523     if ( it.current()->isVisible() )
00524       it.current()->setSelected( true );
00525   }
00526   suspend();
00527 }
00528 
00529 void AlarmDialog::showDetails()
00530 {
00531   mDetailView->clearEvents( true );
00532   mDetailView->clear();
00533   AlarmListItem *item = static_cast<AlarmListItem*>( mIncidenceListView->currentItem() );
00534   if ( !item || !item->isVisible() )
00535     return;
00536 
00537   if ( !item->mDisplayText.isEmpty() ) {
00538     QString txt = "<qt><p><b>" + item->mDisplayText + "</b></p></qt>";
00539     mDetailView->addText( txt );
00540   }
00541   mDetailView->appendIncidence( item->mIncidence, item->mRemindAt.date() );
00542 }
00543 
00544 bool AlarmDialog::ensureKorganizerRunning() const
00545 {
00546   QString error;
00547   QCString dcopService;
00548 
00549   int result = KDCOPServiceStarter::self()->findServiceFor(
00550     "DCOP/Organizer", QString::null, QString::null, &error, &dcopService );
00551 
00552   if ( result == 0 ) {
00553     // OK, so korganizer (or kontact) is running. Now ensure the object we
00554     // want is available [that's not the case when kontact was already running,
00555     // but korganizer not loaded into it...]
00556     static const char* const dcopObjectId = "KOrganizerIface";
00557     QCString dummy;
00558     if ( !kapp->dcopClient()->findObject(
00559            dcopService, dcopObjectId, "", QByteArray(), dummy, dummy ) ) {
00560       DCOPRef ref( dcopService, dcopService ); // talk to KUniqueApplication or its kontact wrapper
00561       DCOPReply reply = ref.call( "load()" );
00562       if ( reply.isValid() && (bool)reply ) {
00563         Q_ASSERT( kapp->dcopClient()->findObject(
00564                     dcopService, dcopObjectId, "", QByteArray(), dummy, dummy ) );
00565       } else {
00566         kdWarning() << "Error loading " << dcopService << endl;
00567       }
00568     }
00569 
00570     // We don't do anything with it we just need it to be running
00571     return true;
00572 
00573   } else {
00574     kdWarning() << "Couldn't start DCOP/Organizer: " << dcopService
00575                 << " " << error << endl;
00576   }
00577   return false;
00578 }
KDE Home | KDE Accessibility Home | Description of Access Keys