korganizer

resourceview.cpp

00001 /*
00002     This file is part of KOrganizer.
00003 
00004     Copyright (c) 2003,2004 Cornelius Schumacher <schumacher@kde.org>
00005     Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
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 "resourceview.h"
00027 
00028 #include <kcolordialog.h>
00029 #include <kdialog.h>
00030 #include <klistview.h>
00031 #include <klocale.h>
00032 #include <kdebug.h>
00033 #include <kglobal.h>
00034 #include <kmessagebox.h>
00035 #include <kresources/resource.h>
00036 #include <kresources/configdialog.h>
00037 #include <kinputdialog.h>
00038 #include <libkcal/calendarresources.h>
00039 
00040 #include <qhbox.h>
00041 #include <qlayout.h>
00042 #include <qlabel.h>
00043 #include <qpainter.h>
00044 #include <qpushbutton.h>
00045 #include <qpopupmenu.h>
00046 #include <qwhatsthis.h>
00047 
00048 #include "koprefs.h"
00049 
00050 using namespace KCal;
00051 
00052 ResourceViewFactory::ResourceViewFactory( KCal::CalendarResources *calendar,
00053                                           CalendarView *view )
00054   : mCalendar( calendar ), mView( view ), mResourceView( 0 )
00055 {
00056 }
00057 
00058 CalendarViewExtension *ResourceViewFactory::create( QWidget *parent )
00059 {
00060   mResourceView = new ResourceView( mCalendar, parent );
00061 
00062   QObject::connect( mResourceView, SIGNAL( resourcesChanged() ),
00063                     mView, SLOT( updateView() ) );
00064   QObject::connect( mResourceView, SIGNAL( resourcesChanged() ),
00065                     mView, SLOT( updateCategories() ) );
00066 
00067   QObject::connect( mCalendar,
00068                     SIGNAL( signalResourceAdded( ResourceCalendar * ) ),
00069                     mResourceView,
00070                     SLOT( addResourceItem( ResourceCalendar * ) ) );
00071   QObject::connect( mCalendar,
00072                     SIGNAL( signalResourceModified( ResourceCalendar * ) ),
00073                     mResourceView,
00074                     SLOT( updateResourceItem( ResourceCalendar * ) ) );
00075   QObject::connect( mCalendar, SIGNAL( signalResourceAdded( ResourceCalendar * ) ),
00076                     mView, SLOT( updateCategories() ) );
00077   QObject::connect( mCalendar, SIGNAL( signalResourceModified( ResourceCalendar * ) ),
00078                     mView, SLOT( updateCategories() ) );
00079 
00080   return mResourceView;
00081 }
00082 
00083 ResourceView *ResourceViewFactory::resourceView() const
00084 {
00085   return mResourceView;
00086 }
00087 
00088 ResourceItem::ResourceItem( ResourceCalendar *resource, ResourceView *view,
00089                             KListView *parent )
00090   : QCheckListItem( parent, resource->resourceName(), CheckBox ),
00091     mResource( resource ), mView( view ), mBlockStateChange( false ),
00092     mIsSubresource( false ), mResourceIdentifier( QString::null ),
00093     mSubItemsCreated( false ), mIsStandardResource( false )
00094 {
00095   mResourceColor = QColor();
00096   setGuiState();
00097 
00098   if ( mResource->isActive() ) {
00099     createSubresourceItems();
00100   }
00101 }
00102 
00103 void ResourceItem::createSubresourceItems()
00104 {
00105   const QStringList subresources = mResource->subresources();
00106   if ( !subresources.isEmpty() ) {
00107     setOpen( true );
00108     setExpandable( true );
00109     // This resource has subresources
00110     QStringList::ConstIterator it;
00111     for ( it=subresources.begin(); it!=subresources.end(); ++it ) {
00112       ResourceItem *item = new ResourceItem( mResource, *it, mResource->labelForSubresource( *it ),
00113                                              mView, this );
00114       QColor resourceColor = *KOPrefs::instance()->resourceColor( *it );
00115       item->setResourceColor( resourceColor );
00116     }
00117   }
00118   mSubItemsCreated = true;
00119 }
00120 
00121 ResourceItem::ResourceItem( KCal::ResourceCalendar *resource,
00122                             const QString& sub, const QString& label,
00123                             ResourceView *view, ResourceItem* parent )
00124 
00125   : QCheckListItem( parent, label, CheckBox ), mResource( resource ),
00126     mView( view ), mBlockStateChange( false ), mIsSubresource( true ),
00127     mSubItemsCreated( false ), mIsStandardResource( false )
00128 {
00129   mResourceColor = QColor();
00130   mResourceIdentifier = sub;
00131   setGuiState();
00132 }
00133 
00134 void ResourceItem::setGuiState()
00135 {
00136   mBlockStateChange = true;
00137   if ( mIsSubresource )
00138     setOn( mResource->subresourceActive( mResourceIdentifier ) );
00139   else
00140     setOn( mResource->isActive() );
00141   mBlockStateChange = false;
00142 }
00143 
00144 void ResourceItem::stateChange( bool active )
00145 {
00146   if ( mBlockStateChange ) return;
00147 
00148   if ( mIsSubresource ) {
00149     mResource->setSubresourceActive( mResourceIdentifier, active );
00150   } else {
00151     if ( active ) {
00152       if ( mResource->load() ) {
00153         mResource->setActive( true );
00154         if ( !mSubItemsCreated )
00155           createSubresourceItems();
00156       }
00157     } else {
00158       if ( mResource->save() ) mResource->setActive( false );
00159       mView->requestClose( mResource );
00160     }
00161 
00162     setOpen( mResource->isActive() && childCount() > 0 );
00163 
00164     setGuiState();
00165   }
00166 
00167   mView->emitResourcesChanged();
00168 }
00169 
00170 void ResourceItem::update()
00171 {
00172   setGuiState();
00173 }
00174 
00175 void ResourceItem::setResourceColor(QColor& color)
00176 {
00177   if ( color.isValid() ) {
00178     if ( mResourceColor != color ) {
00179       QPixmap px(height()-4,height()-4);
00180       mResourceColor = color;
00181       px.fill(color);
00182       setPixmap(0,px);
00183     }
00184   } else {
00185     mResourceColor = color ;
00186     setPixmap(0,0);
00187   }
00188 }
00189 
00190 void ResourceItem::setStandardResource( bool std )
00191 {
00192   if ( mIsStandardResource != std ) {
00193     mIsStandardResource = std;
00194     repaint();
00195   }
00196 }
00197 
00198 void ResourceItem::paintCell(QPainter *p, const QColorGroup &cg,
00199       int column, int width, int alignment)
00200 {
00201   QFont oldFont = p->font();
00202   QFont newFont = oldFont;
00203   newFont.setBold( mIsStandardResource && !mIsSubresource );
00204   p->setFont( newFont );
00205   QCheckListItem::paintCell( p, cg, column, width, alignment );
00206   p->setFont( oldFont );
00207 /*  QColorGroup _cg = cg;
00208   if(!mResource) return;
00209   _cg.setColor(QColorGroup::Base, getTextColor(mResourceColor));*/
00210 }
00211 
00212 
00213 ResourceView::ResourceView( KCal::CalendarResources *calendar,
00214                             QWidget *parent, const char *name )
00215   : CalendarViewExtension( parent, name ), mCalendar( calendar )
00216 {
00217   QBoxLayout *topLayout = new QVBoxLayout( this, 0, KDialog::spacingHint() );
00218 
00219   mListView = new KListView( this );
00220   QWhatsThis::add( mListView,
00221                    i18n( "<qt><p>Select on this list the active KOrganizer "
00222                          "resources. Check the resource box to make it "
00223                          "active. Press the \"Add...\" button below to add new "
00224                          "resources to the list.</p>"
00225                          "<p>Events, journal entries and to-dos are retrieved "
00226                          "and stored on resources. Available "
00227                          "resources include groupware servers, local files, "
00228                          "journal entries as blogs on a server, etc...</p>"
00229                          "<p>If you have more than one active resource, "
00230                          "when creating incidents you will either automatically "
00231                          "use the default resource or be prompted "
00232                          "to select the resource to use.</p></qt>" ) );
00233   mListView->addColumn( i18n("Calendar") );
00234   mListView->setResizeMode( QListView::LastColumn );
00235   topLayout->addWidget( mListView );
00236 
00237   QHBox *buttonBox = new QHBox( this );
00238   buttonBox->setSpacing( KDialog::spacingHint() );
00239   topLayout->addWidget( buttonBox );
00240 
00241   mAddButton = new QPushButton( i18n("Add..."), buttonBox, "add" );
00242   QWhatsThis::add( mAddButton,
00243                    i18n( "<qt><p>Press this button to add a resource to "
00244                          "KOrganizer.</p>"
00245                          "<p>Events, journal entries and to-dos are retrieved "
00246                          "and stored on resources. Available "
00247                          "resources include groupware servers, local files, "
00248                          "journal entries as blogs on a server, etc... </p>"
00249                          "<p>If you have more than one active resource, "
00250                          "when creating incidents you will either automatically "
00251                          "use the default resource or be prompted "
00252                          "to select the resource to use.</p></qt>" ) );
00253   mEditButton = new QPushButton( i18n("Edit..."), buttonBox, "edit" );
00254   QWhatsThis::add( mEditButton,
00255                    i18n( "Press this button to edit the resource currently "
00256                          "selected on the KOrganizer resources list above." ) );
00257   mDeleteButton = new QPushButton( i18n("Remove"), buttonBox, "del" );
00258   QWhatsThis::add( mDeleteButton,
00259                    i18n( "Press this button to delete the resource currently "
00260                          "selected on the KOrganizer resources list above." ) );
00261   mDeleteButton->setDisabled( true );
00262   mEditButton->setDisabled( true );
00263 
00264   connect( mListView, SIGNAL( clicked( QListViewItem * ) ),
00265            SLOT( currentChanged( QListViewItem * ) ) );
00266   connect( mAddButton, SIGNAL( clicked() ), SLOT( addResource() ) );
00267   connect( mDeleteButton, SIGNAL( clicked() ), SLOT( removeResource() ) );
00268   connect( mEditButton, SIGNAL( clicked() ), SLOT( editResource() ) );
00269   connect( mListView, SIGNAL( doubleClicked ( QListViewItem *, const QPoint &,
00270                                               int ) ),
00271            SLOT( editResource() ) );
00272   connect( mListView, SIGNAL( contextMenuRequested ( QListViewItem *,
00273                                                      const QPoint &, int ) ),
00274            SLOT( contextMenuRequested( QListViewItem *, const QPoint &,
00275                                        int ) ) );
00276 
00277   updateView();
00278 }
00279 
00280 ResourceView::~ResourceView()
00281 {
00282 }
00283 
00284 void ResourceView::updateView()
00285 {
00286   mListView->clear();
00287 
00288   KCal::CalendarResourceManager *manager = mCalendar->resourceManager();
00289 
00290   KCal::CalendarResourceManager::Iterator it;
00291   for( it = manager->begin(); it != manager->end(); ++it ) {
00292     addResourceItem( *it );
00293   }
00294 }
00295 
00296 void ResourceView::emitResourcesChanged()
00297 {
00298   mCalendar->resourceManager()->writeConfig();
00299   emit resourcesChanged();
00300 }
00301 
00302 void ResourceView::addResource()
00303 {
00304   KCal::CalendarResourceManager *manager = mCalendar->resourceManager();
00305 
00306   QStringList types = manager->resourceTypeNames();
00307   QStringList descs = manager->resourceTypeDescriptions();
00308   bool ok = false;
00309   QString desc = KInputDialog::getItem( i18n( "Resource Configuration" ),
00310       i18n( "Please select type of the new resource:" ), descs, 0, false, &ok,
00311             this );
00312   if ( !ok )
00313     return;
00314 
00315   QString type = types[ descs.findIndex( desc ) ];
00316 
00317   // Create new resource
00318   ResourceCalendar *resource = manager->createResource( type );
00319   if( !resource ) {
00320     KMessageBox::error( this, i18n("<qt>Unable to create resource of type <b>%1</b>.</qt>")
00321                               .arg( type ) );
00322     return;
00323   }
00324 
00325   resource->setResourceName( i18n("%1 resource").arg( type ) );
00326 
00327   KRES::ConfigDialog dlg( this, QString("calendar"), resource,
00328                           "KRES::ConfigDialog" );
00329 
00330   if ( dlg.exec() ) {
00331     resource->setTimeZoneId( KOPrefs::instance()->mTimeZoneId );
00332     manager->add( resource );
00333     // we have to call resourceAdded manually, because for in-process changes
00334     // the dcop signals are not connected, so the resource's signals would not
00335     // be connected otherwise
00336     mCalendar->resourceAdded( resource );
00337   } else {
00338     delete resource;
00339     resource = 0;
00340   }
00341   emitResourcesChanged();
00342 }
00343 
00344 void ResourceView::addResourceItem( ResourceCalendar *resource )
00345 {
00346 
00347   ResourceItem *item=new ResourceItem( resource, this, mListView );
00348 
00349   QColor resourceColor;
00350 
00351   resourceColor= *KOPrefs::instance()->resourceColor(resource->identifier());
00352   item->setResourceColor(resourceColor);
00353 
00354   connect( resource, SIGNAL( signalSubresourceAdded( ResourceCalendar *,
00355                                                      const QString &,
00356                                                      const QString &,
00357                                                      const QString & ) ),
00358            SLOT( slotSubresourceAdded( ResourceCalendar *, const QString &,
00359                                        const QString &, const QString & ) ) );
00360 
00361  connect( resource, SIGNAL( signalSubresourceRemoved( ResourceCalendar *,
00362                                                        const QString &,
00363                                                        const QString & ) ),
00364            SLOT( slotSubresourceRemoved( ResourceCalendar *, const QString &,
00365                                          const QString & ) ) );
00366 
00367   connect( resource, SIGNAL( resourceSaved( ResourceCalendar * ) ),
00368            SLOT( closeResource( ResourceCalendar * ) ) );
00369 
00370   updateResourceList();
00371   emit resourcesChanged();
00372 }
00373 
00374 // Add a new entry
00375 void ResourceView::slotSubresourceAdded( ResourceCalendar *calendar,
00376                                          const QString& /*type*/,
00377                                          const QString& resource,
00378                                          const QString& label)
00379 {
00380   QListViewItem *i = mListView->findItem( calendar->resourceName(), 0 );
00381   if ( !i )
00382     // Not found
00383     return;
00384 
00385   ResourceItem *item = static_cast<ResourceItem *>( i );
00386   ( void )new ResourceItem( calendar, resource, label, this, item );
00387   emitResourcesChanged();
00388 }
00389 
00390 // Remove an entry
00391 void ResourceView::slotSubresourceRemoved( ResourceCalendar * /*calendar*/,
00392                                            const QString &/*type*/,
00393                                            const QString &resource )
00394 {
00395   delete findItemByIdentifier( resource );
00396   emit resourcesChanged();
00397 }
00398 
00399 void ResourceView::closeResource( ResourceCalendar *r )
00400 {
00401   if ( mResourcesToClose.find( r ) >= 0 ) {
00402     r->close();
00403     mResourcesToClose.remove( r );
00404   }
00405 }
00406 
00407 void ResourceView::updateResourceItem( ResourceCalendar *resource )
00408 {
00409   ResourceItem *item = findItem( resource );
00410   if ( item ) {
00411     item->update();
00412   }
00413 }
00414 
00415 ResourceItem *ResourceView::currentItem()
00416 {
00417   QListViewItem *item = mListView->currentItem();
00418   ResourceItem *rItem = static_cast<ResourceItem *>( item );
00419   return rItem;
00420 }
00421 
00422 void ResourceView::removeResource()
00423 {
00424   ResourceItem *item = currentItem();
00425   if ( !item ) return;
00426 
00427   int km = KMessageBox::warningContinueCancel( this,
00428         i18n("<qt>Do you really want to delete the resource <b>%1</b>?</qt>")
00429         .arg( item->text( 0 ) ), "", KStdGuiItem::del() );
00430   if ( km == KMessageBox::Cancel ) return;
00431 
00432 // Don't be so restricitve
00433 #if 0
00434   if ( item->resource() == mCalendar->resourceManager()->standardResource() ) {
00435     KMessageBox::sorry( this,
00436                         i18n( "You cannot delete your standard resource." ) );
00437     return;
00438   }
00439 #endif
00440   if ( item->isSubresource() ) {
00441     // FIXME delete the folder in KMail
00442   } else {
00443     mCalendar->resourceManager()->remove( item->resource() );
00444     mListView->takeItem( item );
00445     delete item;
00446   }
00447   updateResourceList();
00448   emit resourcesChanged();
00449 }
00450 
00451 void ResourceView::editResource()
00452 {
00453   ResourceItem *item = currentItem();
00454 
00455   ResourceCalendar *resource = item->resource();
00456 
00457   KRES::ConfigDialog dlg( this, QString("calendar"), resource,
00458                           "KRES::ConfigDialog" );
00459 
00460   if ( dlg.exec() ) {
00461     item->setText( 0, resource->resourceName() );
00462 
00463     mCalendar->resourceManager()->change( resource );
00464   }
00465   emitResourcesChanged();
00466 }
00467 
00468 void ResourceView::currentChanged( QListViewItem *item )
00469 {
00470    ResourceItem *i = currentItem();
00471    if ( !item || i->isSubresource() ) {
00472      mDeleteButton->setEnabled( false );
00473      mEditButton->setEnabled( false );
00474    } else {
00475      mDeleteButton->setEnabled( true );
00476      mEditButton->setEnabled( true );
00477    }
00478 }
00479 
00480 ResourceItem *ResourceView::findItem( ResourceCalendar *r )
00481 {
00482   QListViewItem *item;
00483   ResourceItem *i = 0;
00484   for( item = mListView->firstChild(); item; item = item->nextSibling() ) {
00485     i = static_cast<ResourceItem *>( item );
00486     if ( i->resource() == r ) break;
00487   }
00488   return i;
00489 }
00490 
00491 ResourceItem *ResourceView::findItemByIdentifier( const QString& id )
00492 {
00493   QListViewItem *item;
00494   ResourceItem *i = 0;
00495   for( item = mListView->firstChild(); item; item = item->itemBelow() ) {
00496     i = static_cast<ResourceItem *>( item );
00497     if ( i->resourceIdentifier() == id )
00498        return i;
00499   }
00500   return 0;
00501 }
00502 
00503 
00504 void ResourceView::contextMenuRequested ( QListViewItem *i,
00505                                           const QPoint &pos, int )
00506 {
00507   KCal::CalendarResourceManager *manager = mCalendar->resourceManager();
00508   ResourceItem *item = static_cast<ResourceItem *>( i );
00509 
00510   QPopupMenu *menu = new QPopupMenu( this );
00511   connect( menu, SIGNAL( aboutToHide() ), menu, SLOT( deleteLater() ) );
00512   if ( item ) {
00513     int reloadId = menu->insertItem( i18n("Re&load"), this,
00514                                      SLOT( reloadResource() ) );
00515     menu->setItemEnabled( reloadId, item->resource()->isActive() );
00516     int saveId = menu->insertItem( i18n("&Save"), this,
00517                                    SLOT( saveResource() ) );
00518     menu->setItemEnabled( saveId, item->resource()->isActive() );
00519     menu->insertSeparator();
00520 
00521     menu->insertItem( i18n("Show &Info"), this, SLOT( showInfo() ) );
00522     //FIXME: This is better on the resource dialog
00523     if ( KOPrefs::instance()->agendaViewUsesResourceColor() ) {
00524       QPopupMenu *assignMenu= new QPopupMenu( menu );
00525       assignMenu->insertItem( i18n( "&Assign Color" ), this, SLOT( assignColor() ) );
00526       if ( item->resourceColor().isValid() )
00527         assignMenu->insertItem( i18n( "&Disable Color" ), this, SLOT( disableColor() ) );
00528       menu->insertItem( i18n( "Resources Colors" ), assignMenu );
00529     }
00530 
00531     menu->insertItem( i18n("&Edit..."), this, SLOT( editResource() ) );
00532     menu->insertItem( i18n("&Remove"), this, SLOT( removeResource() ) );
00533     if ( item->resource() != manager->standardResource() ) {
00534       menu->insertSeparator();
00535       menu->insertItem( i18n("Use as &Default Calendar"), this,
00536                         SLOT( setStandard() ) );
00537     }
00538 
00539     menu->insertSeparator();
00540  }
00541   menu->insertItem( i18n("&Add..."), this, SLOT( addResource() ) );
00542 
00543   menu->popup( pos );
00544 }
00545 
00546 void ResourceView::assignColor()
00547 {
00548   ResourceItem *item = currentItem();
00549   if ( !item )
00550     return;
00551   // A color without initialized is a color invalid
00552   QColor myColor;
00553   KCal::ResourceCalendar *cal = item->resource();
00554 
00555   QString identifier = cal->identifier();
00556   if ( item->isSubresource() )
00557     identifier = item->resourceIdentifier();
00558 
00559   QColor defaultColor =*KOPrefs::instance()->resourceColor( identifier );
00560 
00561   int result = KColorDialog::getColor( myColor,defaultColor);
00562 
00563   if ( result == KColorDialog::Accepted ) {
00564     KOPrefs::instance()->setResourceColor( identifier, myColor );
00565     item->setResourceColor( myColor );
00566     item->update();
00567     emitResourcesChanged();
00568   }
00569 }
00570 
00571 void ResourceView::disableColor()
00572 {
00573   ResourceItem *item = currentItem();
00574   if ( !item )
00575     return;
00576   QColor colorInvalid;
00577   KCal::ResourceCalendar *cal = item->resource();
00578   QString identifier = cal->identifier();
00579   if ( item->isSubresource() )
00580     identifier = item->resourceIdentifier();
00581   KOPrefs::instance()->setResourceColor( identifier, colorInvalid );
00582   item->setResourceColor( colorInvalid );
00583   item->update();
00584   emitResourcesChanged();
00585 }
00586 void ResourceView::showInfo()
00587 {
00588   ResourceItem *item = currentItem();
00589   if ( !item ) return;
00590 
00591   QString txt = "<qt>" + item->resource()->infoText() + "</qt>";
00592   KMessageBox::information( this, txt );
00593 }
00594 
00595 void ResourceView::reloadResource()
00596 {
00597   ResourceItem *item = currentItem();
00598   if ( !item ) return;
00599 
00600   ResourceCalendar *r = item->resource();
00601   r->load();
00602 }
00603 
00604 void ResourceView::saveResource()
00605 {
00606   ResourceItem *item = currentItem();
00607   if ( !item ) return;
00608 
00609   ResourceCalendar *r = item->resource();
00610   r->save();
00611 }
00612 
00613 void ResourceView::setStandard()
00614 {
00615   ResourceItem *item = currentItem();
00616   if ( !item ) return;
00617 
00618   ResourceCalendar *r = item->resource();
00619   KCal::CalendarResourceManager *manager = mCalendar->resourceManager();
00620   manager->setStandardResource( r );
00621   updateResourceList();
00622 }
00623 
00624 void ResourceView::updateResourceList()
00625 {
00626   QListViewItemIterator it( mListView );
00627   ResourceCalendar* stdRes = mCalendar->resourceManager()->standardResource();
00628   while ( it.current() ) {
00629     ResourceItem *item = static_cast<ResourceItem *>( it.current() );
00630     item->setStandardResource( item->resource() == stdRes );
00631     ++it;
00632   }
00633 }
00634 
00635 void ResourceView::showButtons( bool visible )
00636 {
00637   if ( visible ) {
00638     mAddButton->show();
00639     mDeleteButton->show();
00640     mEditButton->show();
00641   } else {
00642     mAddButton->hide();
00643     mDeleteButton->hide();
00644     mEditButton->hide();
00645   }
00646 }
00647 
00648 void ResourceView::requestClose( ResourceCalendar *r )
00649 {
00650   mResourcesToClose.append( r );
00651 }
00652 
00653 #include "resourceview.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys