korganizer

koeditorgeneralevent.cpp

00001 /*
00002     This file is part of KOrganizer.
00003     Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
00004     Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
00005 
00006     This program is free software; you can redistribute it and/or modify
00007     it under the terms of the GNU General Public License as published by
00008     the Free Software Foundation; either version 2 of the License, or
00009     (at your option) any later version.
00010 
00011     This program is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014     GNU General Public License for more details.
00015 
00016     You should have received a copy of the GNU General Public License
00017     along with this program; if not, write to the Free Software
00018     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019 
00020     As a special exception, permission is given to link this program
00021     with any edition of Qt, and distribute the resulting executable,
00022     without including the source code for Qt in the source distribution.
00023 */
00024 
00025 #include <qtooltip.h>
00026 #include <qlayout.h>
00027 #include <qvbox.h>
00028 #include <qbuttongroup.h>
00029 #include <qvgroupbox.h>
00030 #include <qwidgetstack.h>
00031 #include <qspinbox.h>
00032 #include <qdatetime.h>
00033 #include <qlabel.h>
00034 #include <qcheckbox.h>
00035 #include <qcombobox.h>
00036 #include <qpushbutton.h>
00037 #include <qwhatsthis.h>
00038 
00039 #include <kdebug.h>
00040 #include <kglobal.h>
00041 #include <klocale.h>
00042 #include <kmessagebox.h>
00043 #include <kfiledialog.h>
00044 #include <kstandarddirs.h>
00045 #include <ktextedit.h>
00046 
00047 #include <libkcal/event.h>
00048 #include <libkcal/incidenceformatter.h>
00049 
00050 #include "ktimeedit.h"
00051 #include <libkdepim/kdateedit.h>
00052 
00053 #include "koprefs.h"
00054 #include "koglobals.h"
00055 
00056 #include "koeditorgeneralevent.h"
00057 #include "koeditorgeneralevent.moc"
00058 
00059 KOEditorGeneralEvent::KOEditorGeneralEvent(QObject* parent,
00060                                            const char* name) :
00061   KOEditorGeneral( parent, name)
00062 {
00063   connect( this, SIGNAL( dateTimesChanged( const QDateTime &, const QDateTime & )),
00064            SLOT( setDuration() ) );
00065   connect( this, SIGNAL( dateTimesChanged( const QDateTime &, const QDateTime & )),
00066            SLOT( emitDateTimeStr() ));
00067 }
00068 
00069 KOEditorGeneralEvent::~KOEditorGeneralEvent()
00070 {
00071 }
00072 
00073 void KOEditorGeneralEvent::finishSetup()
00074 {
00075   QWidget::setTabOrder( mSummaryEdit, mLocationEdit );
00076   QWidget::setTabOrder( mLocationEdit, mStartDateEdit );
00077   QWidget::setTabOrder( mStartDateEdit, mStartTimeEdit );
00078   QWidget::setTabOrder( mStartTimeEdit, mEndDateEdit );
00079   QWidget::setTabOrder( mEndDateEdit, mEndTimeEdit );
00080   QWidget::setTabOrder( mEndTimeEdit, mAlldayEventCheckbox );
00081   QWidget::setTabOrder( mAlldayEventCheckbox, mRecEditButton );
00082   QWidget::setTabOrder( mRecEditButton, mAlarmButton );
00083   QWidget::setTabOrder( mAlarmButton, mAlarmTimeEdit );
00084   QWidget::setTabOrder( mAlarmTimeEdit, mAlarmIncrCombo );
00085   QWidget::setTabOrder( mAlarmIncrCombo, mAlarmEditButton );
00086   QWidget::setTabOrder( mAlarmEditButton, mFreeTimeCombo );
00087   QWidget::setTabOrder( mFreeTimeCombo, mDescriptionEdit );
00088   QWidget::setTabOrder( mDescriptionEdit, mCategoriesButton );
00089   QWidget::setTabOrder( mCategoriesButton, mSecrecyCombo );
00090 
00091   mSummaryEdit->setFocus();
00092 }
00093 
00094 void KOEditorGeneralEvent::initTime(QWidget *parent,QBoxLayout *topLayout)
00095 {
00096   QBoxLayout *timeLayout = new QVBoxLayout(topLayout);
00097 
00098   QGroupBox *timeGroupBox = new QGroupBox(1,QGroupBox::Horizontal,
00099                                           i18n("Date && Time"),parent);
00100   QWhatsThis::add( timeGroupBox,
00101        i18n("Sets options related to the date and time of the "
00102             "event or to-do.") );
00103   timeLayout->addWidget(timeGroupBox);
00104 
00105   QFrame *timeBoxFrame = new QFrame(timeGroupBox);
00106 
00107   QGridLayout *layoutTimeBox = new QGridLayout( timeBoxFrame );
00108   layoutTimeBox->setSpacing(topLayout->spacing());
00109   layoutTimeBox->setColStretch( 3, 1 );
00110 
00111   mStartDateLabel = new QLabel(i18n("&Start:"),timeBoxFrame);
00112   layoutTimeBox->addWidget(mStartDateLabel,0,0);
00113 
00114   mStartDateEdit = new KDateEdit(timeBoxFrame);
00115   layoutTimeBox->addWidget(mStartDateEdit,0,1);
00116   mStartDateLabel->setBuddy( mStartDateEdit );
00117 
00118   mStartTimeEdit = new KTimeEdit(timeBoxFrame);
00119   layoutTimeBox->addWidget(mStartTimeEdit,0,2);
00120 
00121   mEndDateLabel = new QLabel(i18n("&End:"),timeBoxFrame);
00122   layoutTimeBox->addWidget(mEndDateLabel,1,0);
00123 
00124   mEndDateEdit = new KDateEdit(timeBoxFrame);
00125   layoutTimeBox->addWidget(mEndDateEdit,1,1);
00126   mEndDateLabel->setBuddy( mEndDateEdit );
00127 
00128   mEndTimeEdit = new KTimeEdit(timeBoxFrame);
00129   layoutTimeBox->addWidget(mEndTimeEdit,1,2);
00130 
00131   mAlldayEventCheckbox = new QCheckBox(i18n("All-&day"),timeBoxFrame);
00132   layoutTimeBox->addWidget( mAlldayEventCheckbox, 0, 3 );
00133   connect(mAlldayEventCheckbox, SIGNAL(toggled(bool)),SLOT(associateTime(bool)));
00134 
00135   mDurationLabel = new QLabel( timeBoxFrame );
00136   layoutTimeBox->addWidget( mDurationLabel, 1, 3 );
00137 
00138   // time widgets are checked if they contain a valid time
00139   connect(mStartTimeEdit, SIGNAL(timeChanged(QTime)),
00140           this, SLOT(startTimeChanged(QTime)));
00141   connect(mEndTimeEdit, SIGNAL(timeChanged(QTime)),
00142           this, SLOT(endTimeChanged(QTime)));
00143 
00144   // date widgets are checked if they contain a valid date
00145   connect(mStartDateEdit, SIGNAL(dateChanged(const QDate&)),
00146           this, SLOT(startDateChanged(const QDate&)));
00147   connect(mEndDateEdit, SIGNAL(dateChanged(const QDate&)),
00148           this, SLOT(endDateChanged(const QDate&)));
00149 
00150   QLabel *label = new QLabel( i18n( "Recurrence:" ), timeBoxFrame );
00151   layoutTimeBox->addWidget( label, 2, 0 );
00152   QBoxLayout *recLayout = new QHBoxLayout();
00153   layoutTimeBox->addMultiCellLayout( recLayout, 2, 2, 1, 4 );
00154   mRecEditButton = new QPushButton( timeBoxFrame );
00155   mRecEditButton->setIconSet( KOGlobals::self()->smallIconSet( "recur", 16 ) );
00156   recLayout->addWidget( mRecEditButton );
00157   connect( mRecEditButton, SIGNAL(clicked()), SIGNAL(editRecurrence()) );
00158   mRecEditLabel = new QLabel( QString(), timeBoxFrame );
00159   recLayout->addWidget( mRecEditLabel );
00160   recLayout->addStretch( 1 );
00161 
00162   label = new QLabel( i18n("Reminder:"), timeBoxFrame );
00163   layoutTimeBox->addWidget( label, 3, 0 );
00164   QBoxLayout *alarmLineLayout = new QHBoxLayout();
00165   layoutTimeBox->addMultiCellLayout( alarmLineLayout, 3, 3, 1, 4 );
00166   initAlarm( timeBoxFrame, alarmLineLayout );
00167   alarmLineLayout->addStretch( 1 );
00168 
00169   QBoxLayout *secLayout = new QHBoxLayout();
00170   layoutTimeBox->addLayout( secLayout, 0, 4 );
00171   initSecrecy( timeBoxFrame, secLayout );
00172 
00173   QBoxLayout *classLayout = new QHBoxLayout();
00174   layoutTimeBox->addLayout( classLayout, 1, 4 );
00175   initClass( timeBoxFrame, classLayout );
00176 }
00177 
00178 void KOEditorGeneralEvent::initClass(QWidget *parent,QBoxLayout *topLayout)
00179 {
00180   QBoxLayout *classLayout = new QHBoxLayout(topLayout);
00181 
00182   QLabel *freeTimeLabel = new QLabel(i18n("S&how time as:"),parent);
00183   QString whatsThis = i18n("Sets how this time will appear on your Free/Busy "
00184                            "information.");
00185   QWhatsThis::add( freeTimeLabel, whatsThis );
00186   classLayout->addWidget(freeTimeLabel);
00187 
00188   mFreeTimeCombo = new QComboBox(false, parent);
00189   QWhatsThis::add( mFreeTimeCombo, whatsThis );
00190   mFreeTimeCombo->insertItem(i18n("Busy"));
00191   mFreeTimeCombo->insertItem(i18n("Free"));
00192   classLayout->addWidget(mFreeTimeCombo);
00193   freeTimeLabel->setBuddy( mFreeTimeCombo );
00194 }
00195 
00196 void KOEditorGeneralEvent::initInvitationBar(QWidget * parent, QBoxLayout * layout)
00197 {
00198   QBoxLayout *topLayout = new QHBoxLayout( layout );
00199   mInvitationBar = new QFrame( parent );
00200   mInvitationBar->setPaletteBackgroundColor( KGlobalSettings::alternateBackgroundColor() );
00201   topLayout->addWidget( mInvitationBar );
00202 
00203   QBoxLayout *barLayout = new QHBoxLayout( mInvitationBar );
00204   barLayout->setSpacing( layout->spacing() );
00205   QLabel *label = new QLabel( i18n("You have not yet definitely responded to this invitation." ), mInvitationBar );
00206   barLayout->addWidget( label );
00207   barLayout->addStretch( 1 );
00208   QPushButton *button = new QPushButton( i18n("Accept"), mInvitationBar );
00209   connect( button, SIGNAL(clicked()), SIGNAL(acceptInvitation()) );
00210   connect( button, SIGNAL(clicked()), mInvitationBar, SLOT(hide()) );
00211   barLayout->addWidget( button );
00212   button = new QPushButton( i18n("Decline"), mInvitationBar );
00213   connect( button, SIGNAL(clicked()), SIGNAL(declineInvitation()) );
00214   connect( button, SIGNAL(clicked()), mInvitationBar, SLOT(hide()) );
00215   barLayout->addWidget( button );
00216 
00217   mInvitationBar->hide();
00218 }
00219 
00220 void KOEditorGeneralEvent::timeStuffDisable(bool disable)
00221 {
00222   mStartTimeEdit->setEnabled( !disable );
00223   mEndTimeEdit->setEnabled( !disable );
00224 
00225   setDuration();
00226   emitDateTimeStr();
00227 }
00228 
00229 void KOEditorGeneralEvent::associateTime(bool time)
00230 {
00231   timeStuffDisable(time);
00232   allDayChanged(time);
00233 }
00234 
00235 void KOEditorGeneralEvent::setDateTimes( const QDateTime &start, const QDateTime &end )
00236 {
00237 //  kdDebug(5850) << "KOEditorGeneralEvent::setDateTimes(): Start DateTime: " << start.toString() << endl;
00238 
00239   mStartDateEdit->setDate(start.date());
00240   // KTimeEdit seems to emit some signals when setTime() is called.
00241   mStartTimeEdit->blockSignals( true );
00242   mStartTimeEdit->setTime(start.time());
00243   mStartTimeEdit->blockSignals( false );
00244   mEndDateEdit->setDate(end.date());
00245   mEndTimeEdit->setTime(end.time());
00246 
00247   mCurrStartDateTime = start;
00248   mCurrEndDateTime = end;
00249 
00250   setDuration();
00251   emitDateTimeStr();
00252 }
00253 
00254 void KOEditorGeneralEvent::startTimeChanged( QTime newtime )
00255 {
00256   kdDebug(5850) << "KOEditorGeneralEvent::startTimeChanged() " << newtime.toString() << endl;
00257 
00258   int secsep = mCurrStartDateTime.secsTo(mCurrEndDateTime);
00259 
00260   mCurrStartDateTime.setTime(newtime);
00261 
00262   // adjust end time so that the event has the same duration as before.
00263   mCurrEndDateTime = mCurrStartDateTime.addSecs(secsep);
00264   mEndTimeEdit->setTime(mCurrEndDateTime.time());
00265   mEndDateEdit->setDate(mCurrEndDateTime.date());
00266 
00267   emit dateTimesChanged(mCurrStartDateTime,mCurrEndDateTime);
00268 }
00269 
00270 void KOEditorGeneralEvent::endTimeChanged( QTime newtime )
00271 {
00272 //  kdDebug(5850) << "KOEditorGeneralEvent::endTimeChanged " << newtime.toString() << endl;
00273 
00274   QDateTime newdt(mCurrEndDateTime.date(), newtime);
00275   mCurrEndDateTime = newdt;
00276 
00277   emit dateTimesChanged(mCurrStartDateTime,mCurrEndDateTime);
00278 }
00279 
00280 void KOEditorGeneralEvent::startDateChanged( const QDate &newdate )
00281 {
00282   if ( !newdate.isValid() )
00283     return;
00284 
00285   int daysep = mCurrStartDateTime.daysTo(mCurrEndDateTime);
00286 
00287   mCurrStartDateTime.setDate(newdate);
00288 
00289   // adjust end date so that the event has the same duration as before
00290   mCurrEndDateTime.setDate(mCurrStartDateTime.date().addDays(daysep));
00291   mEndDateEdit->setDate(mCurrEndDateTime.date());
00292 
00293   emit dateTimesChanged(mCurrStartDateTime,mCurrEndDateTime);
00294 }
00295 
00296 void KOEditorGeneralEvent::endDateChanged( const QDate &newdate )
00297 {
00298   if ( !newdate.isValid() )
00299     return;
00300 
00301   QDateTime newdt(newdate, mCurrEndDateTime.time());
00302   mCurrEndDateTime = newdt;
00303 
00304   emit dateTimesChanged(mCurrStartDateTime,mCurrEndDateTime);
00305 }
00306 
00307 void KOEditorGeneralEvent::setDefaults( const QDateTime &from,
00308                                         const QDateTime &to, bool allDay)
00309 {
00310   KOEditorGeneral::setDefaults(allDay);
00311 
00312   mAlldayEventCheckbox->setChecked(allDay);
00313   timeStuffDisable(allDay);
00314 
00315   setDateTimes(from,to);
00316 }
00317 
00318 void KOEditorGeneralEvent::readEvent( Event *event, Calendar *calendar, const QDate &date, bool tmpl )
00319 {
00320   QString tmpStr;
00321 
00322   mAlldayEventCheckbox->setChecked(event->doesFloat());
00323   timeStuffDisable(event->doesFloat());
00324 
00325   if ( !tmpl ) {
00326     QDateTime startDT = event->dtStart();
00327     QDateTime endDT = event->dtEnd();
00328     if ( event->doesRecur() && date.isValid() ) {
00329       // Consider the active date when editing recurring Events.
00330       QDateTime kdt( date, QTime( 0, 0, 0 ) );
00331       const int eventLength = startDT.daysTo( endDT );
00332       kdt = kdt.addSecs( -1 );
00333       startDT.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
00334       if ( event->hasEndDate() ) {
00335         endDT.setDate( startDT.addDays( eventLength ).date() );
00336       } else {
00337         if ( event->hasDuration() ) {
00338           endDT = startDT.addSecs( event->duration() );
00339         } else {
00340           endDT = startDT;
00341         }
00342       }
00343     }
00344     // the rest is for the events only
00345     setDateTimes( startDT, endDT );
00346   }
00347 
00348   switch( event->transparency() ) {
00349   case Event::Transparent:
00350     mFreeTimeCombo->setCurrentItem(1);
00351     break;
00352   case Event::Opaque:
00353     mFreeTimeCombo->setCurrentItem(0);
00354     break;
00355   }
00356 
00357   updateRecurrenceSummary( event );
00358 
00359   Attendee *me = event->attendeeByMails( KOPrefs::instance()->allEmails() );
00360   if ( event->attendeeCount() > 1 &&
00361        me && ( me->status() == Attendee::NeedsAction ||
00362        me->status() == Attendee::Tentative ||
00363        me->status() == Attendee::InProcess ) ) {
00364     mInvitationBar->show();
00365   } else {
00366     mInvitationBar->hide();
00367   }
00368 
00369   readIncidence(event, calendar);
00370 }
00371 
00372 void KOEditorGeneralEvent::writeEvent(Event *event)
00373 {
00374 //  kdDebug(5850) << "KOEditorGeneralEvent::writeEvent()" << endl;
00375 
00376   writeIncidence(event);
00377 
00378   QDate tmpDate;
00379   QTime tmpTime;
00380   QDateTime tmpDT;
00381 
00382   // temp. until something better happens.
00383   QString tmpStr;
00384 
00385   if (mAlldayEventCheckbox->isChecked()) {
00386     event->setFloats(true);
00387     // need to change this.
00388     tmpDate = mStartDateEdit->date();
00389     tmpTime.setHMS(0,0,0);
00390     tmpDT.setDate(tmpDate);
00391     tmpDT.setTime(tmpTime);
00392     event->setDtStart(tmpDT);
00393 
00394     tmpDate = mEndDateEdit->date();
00395     tmpTime.setHMS(0,0,0);
00396     tmpDT.setDate(tmpDate);
00397     tmpDT.setTime(tmpTime);
00398     event->setDtEnd(tmpDT);
00399   } else {
00400     event->setFloats(false);
00401 
00402     // set date/time end
00403     tmpDate = mEndDateEdit->date();
00404     tmpTime = mEndTimeEdit->getTime();
00405     tmpDT.setDate(tmpDate);
00406     tmpDT.setTime(tmpTime);
00407     event->setDtEnd(tmpDT);
00408 
00409     // set date/time start
00410     tmpDate = mStartDateEdit->date();
00411     tmpTime = mStartTimeEdit->getTime();
00412     tmpDT.setDate(tmpDate);
00413     tmpDT.setTime(tmpTime);
00414     event->setDtStart(tmpDT);
00415   } // check for float
00416 
00417   event->setTransparency(mFreeTimeCombo->currentItem() > 0
00418                          ? KCal::Event::Transparent
00419                          : KCal::Event::Opaque);
00420 
00421 //  kdDebug(5850) << "KOEditorGeneralEvent::writeEvent() done" << endl;
00422 }
00423 
00424 void KOEditorGeneralEvent::setDuration()
00425 {
00426   QString tmpStr, catStr;
00427   int hourdiff, minutediff;
00428   // end<date is an accepted temporary state while typing, but don't show
00429   // any duration if this happens
00430   if(mCurrEndDateTime >= mCurrStartDateTime) {
00431 
00432     if (mAlldayEventCheckbox->isChecked()) {
00433       int daydiff = mCurrStartDateTime.date().daysTo(mCurrEndDateTime.date()) + 1;
00434       tmpStr = i18n("Duration: ");
00435       tmpStr.append(i18n("1 Day","%n Days",daydiff));
00436     } else {
00437       hourdiff = mCurrStartDateTime.date().daysTo(mCurrEndDateTime.date()) * 24;
00438       hourdiff += mCurrEndDateTime.time().hour() -
00439                   mCurrStartDateTime.time().hour();
00440       minutediff = mCurrEndDateTime.time().minute() -
00441                    mCurrStartDateTime.time().minute();
00442       // If minutediff is negative, "borrow" 60 minutes from hourdiff
00443       if (minutediff < 0 && hourdiff > 0) {
00444         hourdiff -= 1;
00445         minutediff += 60;
00446       }
00447       if (hourdiff || minutediff){
00448         tmpStr = i18n("Duration: ");
00449         if (hourdiff){
00450           catStr = i18n("1 hour","%n hours",hourdiff);
00451           tmpStr.append(catStr);
00452         }
00453         if (hourdiff && minutediff){
00454           tmpStr += i18n(", ");
00455         }
00456         if (minutediff){
00457           catStr = i18n("1 minute","%n minutes",minutediff);
00458           tmpStr += catStr;
00459         }
00460       } else tmpStr = "";
00461     }
00462   }
00463   mDurationLabel->setText(tmpStr);
00464   QWhatsThis::add( mDurationLabel,
00465        i18n("Shows the duration of the event or to-do with the "
00466       "current start and end dates and times.") );
00467 }
00468 
00469 void KOEditorGeneralEvent::emitDateTimeStr()
00470 {
00471   KLocale *l = KGlobal::locale();
00472 
00473   QString from,to;
00474   if (mAlldayEventCheckbox->isChecked()) {
00475     from = l->formatDate(mCurrStartDateTime.date());
00476     to = l->formatDate(mCurrEndDateTime.date());
00477   } else {
00478     from = l->formatDateTime(mCurrStartDateTime);
00479     to = l->formatDateTime(mCurrEndDateTime);
00480   }
00481 
00482   QString str = i18n("From: %1   To: %2   %3").arg(from).arg(to)
00483                 .arg(mDurationLabel->text());
00484 
00485   emit dateTimeStrChanged(str);
00486 }
00487 
00488 bool KOEditorGeneralEvent::validateInput()
00489 {
00490 //  kdDebug(5850) << "KOEditorGeneralEvent::validateInput()" << endl;
00491 
00492   if (!mAlldayEventCheckbox->isChecked()) {
00493     if (!mStartTimeEdit->inputIsValid()) {
00494       KMessageBox::sorry( 0,
00495           i18n("Please specify a valid start time, for example '%1'.")
00496           .arg( KGlobal::locale()->formatTime( QTime::currentTime() ) ) );
00497       return false;
00498     }
00499 
00500     if (!mEndTimeEdit->inputIsValid()) {
00501       KMessageBox::sorry( 0,
00502           i18n("Please specify a valid end time, for example '%1'.")
00503           .arg( KGlobal::locale()->formatTime( QTime::currentTime() ) ) );
00504       return false;
00505     }
00506   }
00507 
00508   if (!mStartDateEdit->date().isValid()) {
00509     KMessageBox::sorry( 0,
00510         i18n("Please specify a valid start date, for example '%1'.")
00511         .arg( KGlobal::locale()->formatDate( QDate::currentDate() ) ) );
00512     return false;
00513   }
00514 
00515   if (!mEndDateEdit->date().isValid()) {
00516     KMessageBox::sorry( 0,
00517         i18n("Please specify a valid end date, for example '%1'.")
00518         .arg( KGlobal::locale()->formatDate( QDate::currentDate() ) ) );
00519     return false;
00520   }
00521 
00522   QDateTime startDt,endDt;
00523   startDt.setDate(mStartDateEdit->date());
00524   endDt.setDate(mEndDateEdit->date());
00525   if (!mAlldayEventCheckbox->isChecked()) {
00526     startDt.setTime(mStartTimeEdit->getTime());
00527     endDt.setTime(mEndTimeEdit->getTime());
00528   }
00529 
00530   if ( startDt > endDt ) {
00531     KMessageBox::sorry(
00532       0,
00533       i18n( "The event ends before it starts.\n"
00534             "Please correct dates and times." ) );
00535     return false;
00536   }
00537 
00538   return KOEditorGeneral::validateInput();
00539 }
00540 
00541 void KOEditorGeneralEvent::updateRecurrenceSummary( Event *event )
00542 {
00543   if ( event->doesRecur() ) {
00544     mRecEditLabel->setText( IncidenceFormatter::recurrenceString( event ) );
00545   } else {
00546     mRecEditLabel->setText( QString() );
00547   }
00548 }
KDE Home | KDE Accessibility Home | Description of Access Keys