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 static int defSuspendVal = 5;
00062 static int defSuspendUnit = 0; // 0=>minutes, 1=>hours, 2=>days, 3=>weeks
00063 
00064 class AlarmListItem : public KListViewItem
00065 {
00066   public:
00067     AlarmListItem( const QString &uid, QListView *parent ) :
00068       KListViewItem( parent ),
00069       mUid( uid ),
00070       mNotified( false )
00071     {}
00072 
00073     ~AlarmListItem()
00074     {
00075     }
00076 
00077     int compare( KListViewItem *item, int iCol, bool bAscending ) const;
00078 
00079     QString mDisplayText;
00080 
00081     QString mUid;
00082     QDateTime mRemindAt;
00083     QDateTime mHappening;
00084     bool mNotified;
00085 };
00086 
00087 int AlarmListItem::compare( KListViewItem *item, int iCol, bool bAscending ) const
00088 {
00089   if ( iCol == 1 ) {
00090     AlarmListItem *pItem = dynamic_cast<AlarmListItem *>( item );
00091     return mHappening < pItem->mHappening;
00092   } else {
00093     return KListViewItem::compare( item, iCol, bAscending );
00094   }
00095 }
00096 
00097 typedef QValueList<AlarmListItem*> ItemList;
00098 
00099 AlarmDialog::AlarmDialog( KCal::CalendarResources *calendar, QWidget *parent, const char *name )
00100   : KDialogBase( Plain, WType_TopLevel | WStyle_Customize | WStyle_StaysOnTop |
00101                  WStyle_DialogBorder,
00102                  parent, name, false, i18n("Reminder"),
00103                  Ok | User1 | User2 | User3, NoDefault,
00104                  false, i18n("Edit..."), i18n("Dismiss All"), i18n("Dismiss Reminder") ),
00105                  mCalendar( calendar ), mSuspendTimer(this)
00106 {
00107   // User1 => Edit...
00108   // User2 => Dismiss All
00109   // User3 => Dismiss Selected
00110   //    Ok => Suspend
00111 
00112   KGlobal::iconLoader()->addAppDir( "kdepim" );
00113   setButtonOK( i18n( "Suspend" ) );
00114 
00115   QWidget *topBox = plainPage();
00116   QBoxLayout *topLayout = new QVBoxLayout( topBox );
00117   topLayout->setSpacing( spacingHint() );
00118 
00119   QLabel *label = new QLabel( i18n("The following items triggered reminders:"),
00120                               topBox );
00121   topLayout->addWidget( label );
00122 
00123   mIncidenceListView = new KListView( topBox );
00124   mIncidenceListView->addColumn( i18n( "Summary" ) );
00125   mIncidenceListView->addColumn( i18n( "Due" ) );
00126   mIncidenceListView->setSorting( 0, true );
00127   mIncidenceListView->setSorting( 1, true );
00128   mIncidenceListView->setSortColumn( 1 );
00129   mIncidenceListView->setShowSortIndicator( true );
00130   mIncidenceListView->setAllColumnsShowFocus( true );
00131   mIncidenceListView->setSelectionMode( QListView::Extended );
00132   topLayout->addWidget( mIncidenceListView );
00133   connect( mIncidenceListView, SIGNAL(selectionChanged()), SLOT(updateButtons()) );
00134   connect( mIncidenceListView, SIGNAL(doubleClicked(QListViewItem*)), SLOT(edit()) );
00135   connect( mIncidenceListView, SIGNAL(currentChanged(QListViewItem*)), SLOT(showDetails()) );
00136   connect( mIncidenceListView, SIGNAL(selectionChanged()), SLOT(showDetails()) );
00137 
00138   mDetailView = new KOEventViewer( mCalendar, topBox );
00139   mDetailView->setFocus(); // set focus here to start with to make it harder
00140                            // to hit return by mistake and dismiss a reminder.
00141   topLayout->addWidget( mDetailView );
00142 
00143   QHBox *suspendBox = new QHBox( topBox );
00144   suspendBox->setSpacing( spacingHint() );
00145   topLayout->addWidget( suspendBox );
00146 
00147   QLabel *l = new QLabel( i18n("Suspend &duration:"), suspendBox );
00148   mSuspendSpin = new QSpinBox( 1, 9999, 1, suspendBox );
00149   mSuspendSpin->setValue( defSuspendVal );  // default suspend duration
00150   l->setBuddy( mSuspendSpin );
00151 
00152   mSuspendUnit = new KComboBox( suspendBox );
00153   mSuspendUnit->insertItem( i18n("minute(s)") );
00154   mSuspendUnit->insertItem( i18n("hour(s)") );
00155   mSuspendUnit->insertItem( i18n("day(s)") );
00156   mSuspendUnit->insertItem( i18n("week(s)") );
00157   mSuspendUnit->setCurrentItem( defSuspendUnit );
00158 
00159   connect( &mSuspendTimer, SIGNAL(timeout()), SLOT(wakeUp()) );
00160 
00161   setMinimumSize( 300, 200 );
00162 }
00163 
00164 AlarmDialog::~AlarmDialog()
00165 {
00166   mIncidenceListView->clear();
00167 }
00168 
00169 void AlarmDialog::addIncidence( Incidence *incidence,
00170                                 const QDateTime &reminderAt,
00171                                 const QString &displayText )
00172 {
00173   AlarmListItem *item = new AlarmListItem( incidence->uid(), mIncidenceListView );
00174   item->setText( 0, incidence->summary() );
00175   item->mRemindAt = reminderAt;
00176   item->mDisplayText = displayText;
00177 
00178   //TODO: this function needs to consider all Display type alarms in each incidence.
00179 
00180   Event *event;
00181   Todo *todo;
00182   Alarm *alarm = incidence->alarms().first();
00183   if ( ( event = dynamic_cast<Event *>( incidence ) ) ) {
00184     item->setPixmap( 0, SmallIcon( "appointment" ) );
00185     if ( event->doesRecur() ) {
00186       QDateTime nextStart = event->recurrence()->getNextDateTime( reminderAt );
00187       if ( nextStart.isValid() ) {
00188         item->mHappening = nextStart;
00189         item->setText( 1, KGlobal::locale()->formatDateTime( nextStart ) );
00190       }
00191     }
00192     if ( item->text( 1 ).isEmpty() ) {
00193       QDateTime qdt;
00194       if ( alarm->hasStartOffset() ) {
00195         qdt = event->dtStart();
00196       } else {
00197         qdt = event->dtEnd();
00198       }
00199       item->mHappening = qdt;
00200       item->setText( 1, IncidenceFormatter::dateTimeToString( qdt, false, true ) );
00201     }
00202   } else if ( ( todo = dynamic_cast<Todo *>( incidence ) ) ) {
00203     item->setPixmap( 0, SmallIcon( "todo" ) );
00204     if ( todo->doesRecur() ) {
00205       QDateTime nextStart = todo->recurrence()->getNextDateTime( reminderAt );
00206       if ( nextStart.isValid() ) {
00207         item->mHappening = nextStart;
00208         item->setText( 1, KGlobal::locale()->formatDateTime( nextStart ) );
00209       }
00210     }
00211     if ( item->text( 1 ).isEmpty() ) {
00212       QDateTime qdt;
00213       if ( alarm->hasStartOffset() && todo->dtStart().isValid() ) {
00214         qdt = todo->dtStart();
00215       } else {
00216         qdt = todo->dtDue();
00217       }
00218       item->mHappening = qdt;
00219       item->setText( 1, IncidenceFormatter::dateTimeToString( qdt, false, true ) );
00220     }
00221   }
00222 
00223   if ( activeCount() == 1 ) {// previously empty
00224     mIncidenceListView->clearSelection();
00225     item->setSelected( true );
00226   }
00227   showDetails();
00228 }
00229 
00230 void AlarmDialog::slotOk()
00231 {
00232   suspend();
00233 }
00234 
00235 void AlarmDialog::slotUser1()
00236 {
00237   edit();
00238 }
00239 
00240 void AlarmDialog::slotUser2()
00241 {
00242   dismissAll();
00243 }
00244 
00245 void AlarmDialog::slotUser3()
00246 {
00247   dismissCurrent();
00248 }
00249 
00250 void AlarmDialog::dismissCurrent()
00251 {
00252   ItemList selection = selectedItems();
00253   for ( ItemList::Iterator it = selection.begin(); it != selection.end(); ++it ) {
00254     if ( (*it)->itemBelow() )
00255       (*it)->itemBelow()->setSelected( true );
00256     else if ( (*it)->itemAbove() )
00257       (*it)->itemAbove()->setSelected( true );
00258     delete *it;
00259   }
00260   if ( activeCount() == 0 )
00261     accept();
00262   else {
00263     updateButtons();
00264     showDetails();
00265   }
00266   emit reminderCount( activeCount() );
00267 }
00268 
00269 void AlarmDialog::dismissAll()
00270 {
00271   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ) {
00272     AlarmListItem *item = static_cast<AlarmListItem*>( it.current() );
00273     if ( !item->isVisible() ) {
00274       ++it;
00275       continue;
00276     }
00277     delete item;
00278   }
00279   setTimer();
00280   accept();
00281   emit reminderCount( activeCount() );
00282 }
00283 
00284 void AlarmDialog::edit()
00285 {
00286   ItemList selection = selectedItems();
00287   if ( selection.count() != 1 ) {
00288     return;
00289   }
00290   Incidence *incidence = mCalendar->incidence( selection.first()->mUid );
00291   QDate dt = selection.first()->mRemindAt.date();
00292 
00293   if ( incidence->isReadOnly() ) {
00294     KMessageBox::sorry(
00295       this,
00296       i18n( "\"%1\" is a read-only item so modifications are not possible." ).
00297       arg( incidence->summary() ) );
00298     return;
00299   }
00300 
00301   if ( !ensureKorganizerRunning() ) {
00302     KMessageBox::error(
00303       this,
00304       i18n( "Could not start KOrganizer so editing is not possible." ) );
00305     return;
00306   }
00307 
00308   QByteArray data;
00309   QDataStream arg( data, IO_WriteOnly );
00310   arg << incidence->uid();
00311   arg << dt;
00312   //kdDebug(5890) << "editing incidence " << incidence->summary() << endl;
00313   if ( !kapp->dcopClient()->send( "korganizer", "KOrganizerIface",
00314                                   "editIncidence(QString,QDate)",
00315                                   data ) ) {
00316     KMessageBox::error(
00317       this,
00318       i18n( "An internal KOrganizer error occurred attempting to start the incidence editor" ) );
00319     return;
00320   }
00321 
00322   // get desktop # where korganizer (or kontact) runs
00323   QByteArray replyData;
00324   QCString object, replyType;
00325   object = kapp->dcopClient()->isApplicationRegistered( "kontact" ) ?
00326            "kontact-mainwindow#1" : "KOrganizer MainWindow";
00327   if (!kapp->dcopClient()->call( "korganizer", object,
00328                             "getWinID()", 0, replyType, replyData, true, -1 ) ) {
00329   }
00330 
00331   if ( replyType == "int" ) {
00332     int desktop, window;
00333     QDataStream ds( replyData, IO_ReadOnly );
00334     ds >> window;
00335     desktop = KWin::windowInfo( window ).desktop();
00336 
00337     if ( KWin::currentDesktop() == desktop ) {
00338       KWin::iconifyWindow( winId(), false );
00339     } else {
00340       KWin::setCurrentDesktop( desktop );
00341     }
00342     KWin::activateWindow( KWin::transientFor( window ) );
00343   }
00344 }
00345 
00346 void AlarmDialog::suspend()
00347 {
00348   if ( !isVisible() )
00349     return;
00350 
00351   int unit=1;
00352   switch (mSuspendUnit->currentItem()) {
00353     case 3: // weeks
00354       unit *=  7;
00355     case 2: // days
00356       unit *= 24;
00357     case 1: // hours
00358       unit *= 60;
00359     case 0: // minutes
00360       unit *= 60;
00361     default:
00362       break;
00363   }
00364 
00365   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00366     AlarmListItem * item = static_cast<AlarmListItem*>( it.current() );
00367     if ( item->isSelected() && item->isVisible() ) {
00368       item->setVisible( false );
00369       item->setSelected( false );
00370       item->mRemindAt = QDateTime::currentDateTime().addSecs( unit * mSuspendSpin->value() );
00371       item->mNotified = false;
00372     }
00373   }
00374 
00375   // save suspended alarms too so they can be restored on restart
00376   // kolab/issue4108
00377   slotSave();
00378 
00379   setTimer();
00380   if ( activeCount() == 0 ) {
00381     accept();
00382   } else {
00383     updateButtons();
00384     showDetails();
00385   }
00386   emit reminderCount( activeCount() );
00387 }
00388 
00389 void AlarmDialog::setTimer()
00390 {
00391   int nextReminderAt = -1;
00392   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00393     AlarmListItem * item = static_cast<AlarmListItem*>( it.current() );
00394     if ( item->mRemindAt > QDateTime::currentDateTime() ) {
00395       int secs = QDateTime::currentDateTime().secsTo( item->mRemindAt );
00396       nextReminderAt = nextReminderAt <= 0 ? secs : QMIN( nextReminderAt, secs );
00397     }
00398   }
00399 
00400   if ( nextReminderAt >= 0 ) {
00401     mSuspendTimer.stop();
00402     mSuspendTimer.start( 1000 * (nextReminderAt + 1), true );
00403   }
00404 }
00405 
00406 void AlarmDialog::show()
00407 {
00408   mIncidenceListView->sort();
00409 
00410   mIncidenceListView->clearSelection();
00411   if ( mIncidenceListView->firstChild() )
00412     mIncidenceListView->firstChild()->setSelected( true );
00413 
00414   updateButtons();
00415   showDetails();
00416 
00417   // reset the default suspend time
00418   mSuspendSpin->setValue( defSuspendVal );
00419   mSuspendUnit->setCurrentItem( defSuspendUnit );
00420 
00421   KDialogBase::show();
00422   KWin::setState( winId(), NET::KeepAbove );
00423   KWin::setOnAllDesktops( winId(), true );
00424   eventNotification();
00425 }
00426 
00427 void AlarmDialog::eventNotification()
00428 {
00429   bool beeped = false, found = false;
00430 
00431   QValueList<AlarmListItem*> list;
00432   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00433     AlarmListItem *item = static_cast<AlarmListItem*>( it.current() );
00434     if ( !item->isVisible() || item->mNotified ) {
00435       continue;
00436     }
00437     Incidence *incidence = mCalendar->incidence( item->mUid );
00438     if ( !incidence ) {
00439       continue;
00440     }
00441     found = true;
00442     item->mNotified = true;
00443     Alarm::List alarms = incidence->alarms();
00444     Alarm::List::ConstIterator it;
00445     for ( it = alarms.begin(); it != alarms.end(); ++it ) {
00446       Alarm *alarm = *it;
00447       // FIXME: Check whether this should be done for all multiple alarms
00448       if (alarm->type() == Alarm::Procedure) {
00449         // FIXME: Add a message box asking whether the procedure should really be executed
00450         kdDebug(5890) << "Starting program: '" << alarm->programFile() << "'" << endl;
00451         KProcess proc;
00452         proc << QFile::encodeName(alarm->programFile());
00453         proc.start(KProcess::DontCare);
00454       }
00455       else if (alarm->type() == Alarm::Audio) {
00456         beeped = true;
00457         KAudioPlayer::play(QFile::encodeName(alarm->audioFile()));
00458       }
00459     }
00460   }
00461 
00462   if ( !beeped && found ) {
00463     KNotifyClient::beep();
00464   }
00465 }
00466 
00467 void AlarmDialog::wakeUp()
00468 {
00469   bool activeReminders = false;
00470   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00471     AlarmListItem *item = static_cast<AlarmListItem*>( it.current() );
00472     Incidence *incidence = mCalendar->incidence( item->mUid );
00473     if ( !incidence ) {
00474       delete item;
00475       continue;
00476     }
00477 
00478     if ( item->mRemindAt <= QDateTime::currentDateTime() ) {
00479       if ( !item->isVisible() ) {
00480         item->setVisible( true );
00481         item->setSelected( false );
00482       }
00483       activeReminders = true;
00484     } else {
00485       item->setVisible( false );
00486     }
00487   }
00488 
00489   if ( activeReminders )
00490     show();
00491   setTimer();
00492   showDetails();
00493   emit reminderCount( activeCount() );
00494 }
00495 
00496 void AlarmDialog::slotSave()
00497 {
00498   KConfig *config = kapp->config();
00499   KLockFile::Ptr lock = config->lockFile();
00500   if ( lock.data()->lock() != KLockFile::LockOK )
00501     return;
00502 
00503   config->setGroup( "General" );
00504   int numReminders = config->readNumEntry("Reminders", 0);
00505 
00506   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00507     AlarmListItem *item = static_cast<AlarmListItem*>( it.current() );
00508     Incidence *incidence = mCalendar->incidence( item->mUid );
00509     if ( !incidence ) {
00510       continue;
00511     }
00512     config->setGroup( QString("Incidence-%1").arg(numReminders + 1) );
00513     config->writeEntry( "UID", incidence->uid() );
00514     config->writeEntry( "RemindAt", item->mRemindAt );
00515     ++numReminders;
00516   }
00517 
00518   config->setGroup( "General" );
00519   config->writeEntry( "Reminders", numReminders );
00520   config->sync();
00521   lock.data()->unlock();
00522 }
00523 
00524 void AlarmDialog::updateButtons()
00525 {
00526   ItemList selection = selectedItems();
00527   enableButton( User1, selection.count() == 1 ); // can only edit 1 at a time
00528   enableButton( User3, selection.count() > 0 );  // dismiss 1 or more
00529   enableButton( Ok, selection.count() > 0 );     // suspend 1 or more
00530 }
00531 
00532 QValueList< AlarmListItem * > AlarmDialog::selectedItems() const
00533 {
00534   QValueList<AlarmListItem*> list;
00535   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00536     if ( it.current()->isSelected() )
00537       list.append( static_cast<AlarmListItem*>( it.current() ) );
00538   }
00539   return list;
00540 }
00541 
00542 int AlarmDialog::activeCount()
00543 {
00544   int count = 0;
00545   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00546     AlarmListItem * item = static_cast<AlarmListItem*>( it.current() );
00547     if ( item->isVisible() )
00548       ++count;
00549   }
00550   return count;
00551 }
00552 
00553 void AlarmDialog::suspendAll()
00554 {
00555   mIncidenceListView->clearSelection();
00556   for ( QListViewItemIterator it( mIncidenceListView ) ; it.current() ; ++it ) {
00557     if ( it.current()->isVisible() )
00558       it.current()->setSelected( true );
00559   }
00560   suspend();
00561 }
00562 
00563 void AlarmDialog::showDetails()
00564 {
00565   mDetailView->clearEvents( true );
00566   mDetailView->clear();
00567   AlarmListItem *item = static_cast<AlarmListItem*>( mIncidenceListView->currentItem() );
00568   if ( !item || !item->isVisible() )
00569     return;
00570 
00571   Incidence *incidence = mCalendar->incidence( item->mUid );
00572   if ( !incidence ) {
00573     return;
00574   }
00575 
00576   if ( !item->mDisplayText.isEmpty() ) {
00577     QString txt = "<qt><p><b>" + item->mDisplayText + "</b></p></qt>";
00578     mDetailView->addText( txt );
00579   }
00580   item->setText( 0, incidence->summary() );
00581   mDetailView->appendIncidence( incidence, item->mRemindAt.date() );
00582 }
00583 
00584 bool AlarmDialog::ensureKorganizerRunning() const
00585 {
00586   QString error;
00587   QCString dcopService;
00588 
00589   int result = KDCOPServiceStarter::self()->findServiceFor(
00590     "DCOP/Organizer", QString::null, QString::null, &error, &dcopService );
00591 
00592   if ( result == 0 ) {
00593     // OK, so korganizer (or kontact) is running. Now ensure the object we
00594     // want is available [that's not the case when kontact was already running,
00595     // but korganizer not loaded into it...]
00596     static const char* const dcopObjectId = "KOrganizerIface";
00597     QCString dummy;
00598     if ( !kapp->dcopClient()->findObject(
00599            dcopService, dcopObjectId, "", QByteArray(), dummy, dummy ) ) {
00600       DCOPRef ref( dcopService, dcopService ); // talk to KUniqueApplication or its kontact wrapper
00601       DCOPReply reply = ref.call( "load()" );
00602       if ( reply.isValid() && (bool)reply ) {
00603         Q_ASSERT( kapp->dcopClient()->findObject(
00604                     dcopService, dcopObjectId, "", QByteArray(), dummy, dummy ) );
00605       } else {
00606         kdWarning() << "Error loading " << dcopService << endl;
00607       }
00608     }
00609 
00610     // We don't do anything with it we just need it to be running
00611     return true;
00612 
00613   } else {
00614     kdWarning() << "Couldn't start DCOP/Organizer: " << dcopService
00615                 << " " << error << endl;
00616   }
00617   return false;
00618 }
KDE Home | KDE Accessibility Home | Description of Access Keys