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