kexi

field.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at>
00003    Copyright (C) 2002 Joseph Wenninger <jowenn@kde.org>
00004    Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl>
00005 
00006    This library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public
00008    License as published by the Free Software Foundation; either
00009    version 2 of the License, or (at your option) any later version.
00010 
00011    This library 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 GNU
00014    Library General Public License for more details.
00015 
00016    You should have received a copy of the GNU Library General Public License
00017    along with this library; see the file COPYING.LIB.  If not, write to
00018    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019  * Boston, MA 02110-1301, USA.
00020  */
00021 
00022 #include "field.h"
00023 #include "connection.h"
00024 #include "driver.h"
00025 #include "expression.h"
00026 #include "utils.h"
00027 
00028 // we use here i18n() but this depends on kde libs: TODO: add #ifdefs
00029 #include <kdebug.h>
00030 #include <klocale.h>
00031 
00032 #include <qdatetime.h>
00033 
00034 #include <assert.h>
00035 
00036 using namespace KexiDB;
00037 
00038 Field::FieldTypeNames Field::m_typeNames;
00039 Field::FieldTypeGroupNames Field::m_typeGroupNames;
00040 
00041 Field::Field()
00042 {
00043     init();
00044     setConstraints(NoConstraints);
00045 }
00046 
00047 
00048 Field::Field(TableSchema *tableSchema)
00049 {
00050     init();
00051     m_parent = tableSchema;
00052     m_order = tableSchema->fieldCount();
00053     setConstraints(NoConstraints);
00054 }
00055 
00056 Field::Field(QuerySchema *querySchema, BaseExpr* expr)
00057 {
00058     init();
00059     m_parent = querySchema;
00060     m_order = querySchema->fieldCount();
00061     setConstraints(NoConstraints);
00062     if (expr)
00063         setExpression(expr);
00064 }
00065 
00066 Field::Field(const QString& name, Type ctype,
00067  uint cconst, uint options, uint length, uint precision,
00068  QVariant defaultValue, const QString& caption, const QString& description,
00069  uint width)
00070     : m_parent(0)
00071     ,m_name(name.lower())
00072     ,m_length(length)
00073     ,m_precision(precision)
00074     ,m_visibleDecimalPlaces(-1)
00075     ,m_options(options)
00076     ,m_defaultValue(defaultValue)
00077     ,m_order(-1)
00078     ,m_caption(caption)
00079     ,m_desc(description)
00080     ,m_width(width)
00081     ,m_expr(0)
00082     ,m_customProperties(0)
00083     ,m_type(ctype)
00084 {
00085     setConstraints(cconst);
00086     if (m_length==0) {//0 means default length:
00087         if (m_type==Field::Text)
00088             m_length = defaultTextLength();
00089     }
00090 }
00091 
00093 Field::Field(const Field& f)
00094 {
00095     (*this) = f;
00096     if (f.m_customProperties)
00097         m_customProperties = new CustomPropertiesMap( f.customProperties() );
00098 
00099     if (f.m_expr) {//deep copy the expression
00100 //TODO      m_expr = new BaseExpr(*f.m_expr);
00101 
00102 //      m_expr->m_field = this;
00103     } else
00104         m_expr = 0;
00105 }
00106 
00107 Field::~Field()
00108 {
00109     delete m_expr;
00110     delete m_customProperties;
00111 }
00112 
00113 Field* Field::copy() const
00114 {
00115     return new Field(*this);
00116 }
00117 
00118 void Field::init()
00119 {
00120     m_parent = 0;
00121     m_name = "";
00122     m_type = InvalidType;
00123     m_length = 0;
00124     m_precision = 0;
00125     m_visibleDecimalPlaces = -1;
00126     m_options = NoOptions;
00127     m_defaultValue = QVariant(QString::null);
00128     m_order = -1;
00129     m_width = 0;
00130     m_expr = 0;
00131     m_customProperties = 0;
00132 }
00133 
00134 Field::Type Field::type() const
00135 {
00136     if (m_expr)
00137         return m_expr->type();
00138     return m_type;
00139 }
00140 
00141 QVariant::Type Field::variantType(uint type)
00142 {
00143     switch(type)
00144     {
00145         case Byte:
00146         case ShortInteger:
00147         case Integer:
00148         case BigInteger:
00149             return QVariant::Int;
00150         case Boolean:
00151             return QVariant::Bool;
00152         case Date:
00153             return QVariant::Date;
00154         case DateTime:
00155             return QVariant::DateTime;
00156         case Time:
00157             return QVariant::Time;
00158         case Float:
00159         case Double:
00160             return QVariant::Double;
00161         case Text:
00162         case LongText:
00163             return QVariant::String;
00164         case BLOB:
00165             return QVariant::ByteArray;
00166         default:
00167             return QVariant::Invalid;
00168     }
00169 
00170     return QVariant::Invalid;
00171 }
00172 
00173 QString Field::typeName(uint type)
00174 {
00175     m_typeNames.init();
00176     return (type <= LastType) ? m_typeNames.at(type) : QString::number(type);
00177 }
00178 
00179 QString Field::typeString(uint type)
00180 {
00181     m_typeNames.init();
00182     return (type <= LastType) ? m_typeNames.at((int)LastType+1 + type) : QString("Type%1").arg(type);
00183 }
00184 
00185 QString Field::typeGroupName(uint typeGroup)
00186 {
00187     m_typeGroupNames.init();
00188     return (typeGroup <= LastTypeGroup) ? m_typeGroupNames.at(typeGroup) : typeGroupString(typeGroup);
00189 }
00190 
00191 QString Field::typeGroupString(uint typeGroup)
00192 {
00193     m_typeGroupNames.init();
00194     return (typeGroup <= LastTypeGroup) ? m_typeGroupNames.at((int)LastTypeGroup+1 + typeGroup) : QString("TypeGroup%1").arg(typeGroup);
00195 }
00196 
00197 Field::Type Field::typeForString(const QString& typeString)
00198 {
00199     m_typeNames.init();
00200     QMap<QString,Type>::ConstIterator it = m_typeNames.str2num.find(typeString.lower());
00201     if (it==m_typeNames.str2num.end())
00202         return InvalidType;
00203     return it.data();
00204 }
00205 
00206 Field::TypeGroup Field::typeGroupForString(const QString& typeGroupString)
00207 {
00208     m_typeGroupNames.init();
00209     QMap<QString,TypeGroup>::ConstIterator it = m_typeGroupNames.str2num.find(typeGroupString.lower());
00210     if (it==m_typeGroupNames.str2num.end())
00211         return InvalidGroup;
00212     return it.data();
00213 }
00214 
00215 bool Field::isIntegerType( uint type )
00216 {
00217     switch (type) {
00218     case Field::Byte:
00219     case Field::ShortInteger:
00220     case Field::Integer:
00221     case Field::BigInteger:
00222         return true;
00223     default:;
00224     }
00225     return false;
00226 }
00227 
00228 bool Field::isNumericType( uint type )
00229 {
00230     switch (type) {
00231     case Field::Byte:
00232     case Field::ShortInteger:
00233     case Field::Integer:
00234     case Field::BigInteger:
00235     case Field::Float:
00236     case Field::Double:
00237         return true;
00238     default:;
00239     }
00240     return false;
00241 }
00242 
00243 bool Field::isFPNumericType( uint type )
00244 {
00245     return type==Field::Float || type==Field::Double;
00246 }
00247 
00248 bool Field::isDateTimeType(uint type)
00249 {
00250     switch (type) {
00251     case Field::Date:
00252     case Field::DateTime:
00253     case Field::Time:
00254         return true;
00255     default:;
00256     }
00257     return false;
00258 }
00259 
00260 bool Field::isTextType( uint type )
00261 {
00262     switch (type) {
00263     case Field::Text:
00264     case Field::LongText:
00265         return true;
00266     default:;
00267     }
00268     return false;
00269 }
00270 
00271 bool Field::isQueryAsterisk() const
00272 {
00273     return dynamic_cast<QueryAsterisk const *>(this);
00274 }
00275 
00276 bool Field::hasEmptyProperty(uint type)
00277 {
00278     return Field::isTextType(type) || type==BLOB;
00279 }
00280 
00281 bool Field::isAutoIncrementAllowed(uint type)
00282 {
00283     return Field::isIntegerType(type);
00284 }
00285 
00286 Field::TypeGroup Field::typeGroup(uint type)
00287 {
00288     if (Field::isTextType(type))
00289         return TextGroup;
00290     else if (Field::isIntegerType(type))
00291         return IntegerGroup;
00292     else if (Field::isFPNumericType(type))
00293         return FloatGroup;
00294     else if (type==Boolean)
00295         return BooleanGroup;
00296     else if (Field::isDateTimeType(type))
00297         return DateTimeGroup;
00298     else if (type==BLOB)
00299         return BLOBGroup;
00300 
00301     return InvalidGroup; //unknown
00302 }
00303 
00304 TableSchema*
00305 Field::table() const
00306 {
00307     return dynamic_cast<TableSchema*>(m_parent);
00308 }
00309 
00310 void
00311 Field::setTable(TableSchema *tableSchema)
00312 {
00313     m_parent = tableSchema;
00314 }
00315 
00316 QuerySchema*
00317 Field::query() const
00318 {
00319     return dynamic_cast<QuerySchema*>(m_parent);
00320 }
00321 
00322 void
00323 Field::setQuery(QuerySchema *querySchema)
00324 {
00325     m_parent = querySchema;
00326 }
00327 
00328 void
00329 Field::setName(const QString& n)
00330 {
00331     m_name = n.lower();
00332 }
00333 
00334 void
00335 Field::setType(Type t)
00336 {
00337     if (m_expr) {
00338         KexiDBWarn << QString("Field::setType(%1)").arg(t) 
00339             << " could not set type because the field has expression assigned!" << endl;
00340         return;
00341     }
00342     m_type = t;
00343 }
00344 
00345 void
00346 Field::setConstraints(uint c)
00347 {
00348     m_constraints = c;
00349     //pkey must be unique notnull
00350     if (isPrimaryKey()) {
00351         setPrimaryKey(true);
00352     }
00353     if (isIndexed()) {
00354         setIndexed(true);
00355     }
00356     if (isAutoIncrement() && !isAutoIncrementAllowed()) {
00357         setAutoIncrement(false);
00358     }
00359 }
00360 
00361 void
00362 Field::setLength(uint l)
00363 {
00364     if (type()!=Field::Text)
00365         return;
00366     m_length = l;
00367 }
00368 
00369 void
00370 Field::setPrecision(uint p)
00371 {
00372     if (!isFPNumericType())
00373         return;
00374     m_precision = p;
00375 }
00376 
00377 void
00378 Field::setScale(uint s)
00379 {
00380     if (!isFPNumericType())
00381         return;
00382     m_length = s;
00383 }
00384 
00385 void
00386 Field::setVisibleDecimalPlaces(int p)
00387 {
00388     if (!KexiDB::supportsVisibleDecimalPlacesProperty(type()))
00389         return;
00390     m_visibleDecimalPlaces = p < 0 ? -1 : p;
00391 }
00392 
00393 void
00394 Field::setUnsigned(bool u)
00395 {
00396     m_options |= Unsigned;
00397     m_options ^= (!u * Unsigned);
00398 }
00399 
00400 void
00401 Field::setDefaultValue(const QVariant& def)
00402 {
00403     m_defaultValue = def;
00404 }
00405 
00406 bool
00407 Field::setDefaultValue(const QCString& def)
00408 {
00409     if (def.isNull()) {
00410         m_defaultValue = QVariant();
00411         return true;
00412     }
00413     
00414     bool ok;
00415     switch(type())
00416     {
00417         case Byte: {
00418             unsigned int v = def.toUInt(&ok);
00419             if (!ok || v > 255)
00420                 m_defaultValue = QVariant();
00421             else
00422                 m_defaultValue = QVariant(v);
00423             break;
00424         }case ShortInteger: {
00425             int v = def.toInt(&ok);
00426             if (!ok || (!(m_options & Unsigned) && (v < -32768 || v > 32767)) || ((m_options & Unsigned) && (v < 0 || v > 65535)))
00427                 m_defaultValue = QVariant();
00428             else
00429                 m_defaultValue = QVariant(v);
00430             break;
00431         }case Integer: {//4 bytes
00432             long v = def.toLong(&ok);
00433 //js: FIXME         if (!ok || (!(m_options & Unsigned) && (-v > 0x080000000 || v > (0x080000000-1))) || ((m_options & Unsigned) && (v < 0 || v > 0x100000000)))
00434             if (!ok || (!(m_options & Unsigned) && (-v > (int)0x07FFFFFFF || v > (int)(0x080000000-1))))
00435                 m_defaultValue = QVariant();
00436             else
00437                 m_defaultValue = QVariant((Q_LLONG)v);
00438             break;
00439         }case BigInteger: {//8 bytes
00441 /*
00442             Q_LLONG long v = def.toLongLong(&ok);
00443 //TODO: 2-part decoding
00444             if (!ok || (!(m_options & Unsigned) && (-v > 0x080000000 || v > (0x080000000-1))))
00445                 m_defaultValue = QVariant();
00446             else
00447                 if (m_options & Unsigned)
00448                     m_defaultValue=QVariant((Q_ULLONG) v);
00449                 else
00450                     m_defaultValue = QVariant((Q_LLONG)v);*/
00451             break;
00452         }case Boolean: {
00453             unsigned short v = def.toUShort(&ok);
00454             if (!ok || v > 1)
00455                 m_defaultValue = QVariant();
00456             else
00457                 m_defaultValue = QVariant((bool)v);
00458             break;
00459         }case Date: {//YYYY-MM-DD
00460             QDate date = QDate::fromString( def, Qt::ISODate );
00461             if (!date.isValid())
00462                 m_defaultValue = QVariant();
00463             else
00464                 m_defaultValue = QVariant(date);
00465             break;
00466         }case DateTime: {//YYYY-MM-DDTHH:MM:SS
00467             QDateTime dt = QDateTime::fromString( def, Qt::ISODate );
00468             if (!dt.isValid())
00469                 m_defaultValue = QVariant();
00470             else
00471                 m_defaultValue = QVariant(dt);
00472             break;
00473         }case Time: {//HH:MM:SS
00474             QTime time = QTime::fromString( def, Qt::ISODate );
00475             if (!time.isValid())
00476                 m_defaultValue = QVariant();
00477             else
00478                 m_defaultValue = QVariant(time);
00479             break;
00480         }case Float: {
00481             float v = def.toFloat(&ok);
00482             if (!ok || ((m_options & Unsigned) && (v < 0.0)))
00483                 m_defaultValue = QVariant();
00484             else
00485                 m_defaultValue = QVariant(v);
00486             break;
00487         }case Double: {
00488             double v = def.toDouble(&ok);
00489             if (!ok || ((m_options & Unsigned) && (v < 0.0)))
00490                 m_defaultValue = QVariant();
00491             else
00492                 m_defaultValue = QVariant(v);
00493             break;
00494         }case Text: {
00495             if (def.isNull() || (def.length() > 255))
00496                 m_defaultValue = QVariant();
00497             else
00498                 m_defaultValue = QVariant((QString)def);
00499             break;
00500         }case LongText: {
00501             if (def.isNull())
00502                 m_defaultValue = QVariant();
00503             else
00504                 m_defaultValue = QVariant((QString)def);
00505             break;
00506         }case BLOB: {
00507 //TODO
00508             if (def.isNull())
00509                 m_defaultValue = QVariant();
00510             else
00511                 m_defaultValue = QVariant(def);
00512             break;
00513         }default:
00514             m_defaultValue = QVariant();
00515     }
00516     return m_defaultValue.isNull();
00517 }
00518 
00519 void
00520 Field::setAutoIncrement(bool a)
00521 {
00522     if (a && !isAutoIncrementAllowed())
00523         return;
00524     if (isAutoIncrement() != a)
00525         m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::AutoInc);
00526 }
00527 
00528 void
00529 Field::setPrimaryKey(bool p)
00530 {
00531     if(isPrimaryKey() != p)
00532         m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::PrimaryKey);
00533     if (p) {//also set implied constraints
00534         setUniqueKey(true);
00535         setNotNull(true);
00536         setNotEmpty(true);
00537         setIndexed(true);
00538     }
00539     else {
00541         setAutoIncrement(false);
00542     }
00543 }
00544 
00545 void
00546 Field::setUniqueKey(bool u)
00547 {
00548     if(isUniqueKey() != u) {
00549         m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::Unique);
00550         if (u)
00551             setNotNull(true);
00552     }
00553 }
00554 
00555 void
00556 Field::setForeignKey(bool f)
00557 {
00558     if (isForeignKey() != f)
00559         m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::ForeignKey);
00560 }
00561 
00562 void
00563 Field::setNotNull(bool n)
00564 {
00565     if (isNotNull() != n)
00566         m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::NotNull);
00567 }
00568 
00569 void Field::setNotEmpty(bool n)
00570 {
00571     if (isNotEmpty() != n)
00572         m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::NotEmpty);
00573 }
00574 
00575 void Field::setIndexed(bool s)
00576 {
00577     if (isIndexed() != s)
00578         m_constraints = static_cast<Field::Constraints>(m_constraints ^ Field::Indexed);
00579     if (!s) {//also set implied constraints
00580         setPrimaryKey(false);
00581         setUniqueKey(false);
00582         setNotNull(false);
00583         setNotEmpty(false);
00584     }
00585 }
00586 
00587 
00588 QString Field::debugString() const
00589 {
00590     KexiDB::Connection *conn = table() ? table()->connection() : 0;
00591     QString dbg = (m_name.isEmpty() ? "<NONAME> " : m_name + " ");
00592     if (m_options & Field::Unsigned)
00593         dbg += " UNSIGNED ";
00594     dbg += (conn && conn->driver()) ? conn->driver()->sqlTypeName(type()) : Driver::defaultSQLTypeName(type());
00595     if (isFPNumericType() && m_precision>0) {
00596         if (scale()>0)
00597             dbg += QString::fromLatin1("(%1,%2)").arg(m_precision).arg(scale());
00598         else 
00599             dbg += QString::fromLatin1("(%1)").arg(m_precision);
00600     }
00601     else if (m_type==Field::Text && m_length>0)
00602         dbg += QString::fromLatin1("(%1)").arg(m_length);
00603     if (m_constraints & Field::AutoInc)
00604         dbg += " AUTOINC";
00605     if (m_constraints & Field::Unique)
00606         dbg += " UNIQUE";
00607     if (m_constraints & Field::PrimaryKey)
00608         dbg += " PKEY";
00609     if (m_constraints & Field::ForeignKey)
00610         dbg += " FKEY";
00611     if (m_constraints & Field::NotNull)
00612         dbg += " NOTNULL";
00613     if (m_constraints & Field::NotEmpty)
00614         dbg += " NOTEMPTY";
00615     if (!m_defaultValue.isNull())
00616         dbg += QString(" DEFAULT=[%1]").arg(m_defaultValue.typeName()) + KexiDB::variantToString(m_defaultValue);
00617     if (m_expr)
00618         dbg += " EXPRESSION=" + m_expr->debugString();
00619     if (m_customProperties && !m_customProperties->isEmpty()) {
00620         dbg += QString(" CUSTOM PROPERTIES (%1): ").arg(m_customProperties->count());
00621         bool first = true;
00622         foreach (CustomPropertiesMap::ConstIterator, it, *m_customProperties) {
00623             if (first)
00624                 first = false;
00625             else
00626                 dbg += ", ";
00627             dbg += QString("%1 = %2 (%3)").arg(it.key()).arg(it.data().toString()).arg(it.data().typeName());
00628         }
00629     }
00630     return dbg;
00631 }
00632 
00633 void Field::debug()
00634 {
00635     KexiDBDbg << debugString() << endl;
00636 }
00637 
00638 void Field::setExpression(KexiDB::BaseExpr *expr)
00639 {
00640     assert(!m_parent || dynamic_cast<QuerySchema*>(m_parent));
00641     if (m_expr==expr)
00642         return;
00643     if (m_expr) {
00644         delete m_expr;
00645     }
00646     m_expr = expr;
00647 }
00648 
00649 QVariant Field::customProperty(const QCString& propertyName,
00650     const QVariant& defaultValue) const
00651 {
00652     if (!m_customProperties)
00653         return defaultValue;
00654     CustomPropertiesMap::ConstIterator it(m_customProperties->find(propertyName));
00655     if (it==m_customProperties->constEnd())
00656         return defaultValue;
00657     return it.data();
00658 }
00659 
00660 void Field::setCustomProperty(const QCString& propertyName, const QVariant& value)
00661 {
00662     if (propertyName.isEmpty())
00663         return;
00664     if (!m_customProperties)
00665         m_customProperties = new CustomPropertiesMap();
00666     m_customProperties->insert(propertyName, value);
00667 }
00668 
00669 //-------------------------------------------------------
00670 #define ADDTYPE(type, i18, str) this->at(Field::type) = i18; \
00671     this->at(Field::type+Field::LastType+1) = str; \
00672     str2num.insert(QString::fromLatin1(str).lower(), type)
00673 #define ADDGROUP(type, i18, str) this->at(Field::type) = i18; \
00674     this->at(Field::type+Field::LastTypeGroup+1) = str; \
00675     str2num.insert(QString::fromLatin1(str).lower(), type)
00676 
00677 Field::FieldTypeNames::FieldTypeNames()
00678  : QValueVector<QString>()
00679  , m_initialized(false)
00680 {
00681 }
00682 
00683 void Field::FieldTypeNames::init()
00684 {
00685     if (m_initialized)
00686         return;
00687     m_initialized = true;
00688     resize((Field::LastType + 1)*2);
00689 
00690     ADDTYPE( InvalidType, i18n("Invalid Type"), "InvalidType" );
00691     ADDTYPE( Byte, i18n("Byte"), "Byte" );
00692     ADDTYPE( ShortInteger, i18n("Short Integer Number"), "ShortInteger" );
00693     ADDTYPE( Integer, i18n("Integer Number"), "Integer" );
00694     ADDTYPE( BigInteger, i18n("Big Integer Number"), "BigInteger" );
00695     ADDTYPE( Boolean, i18n("Yes/No Value"), "Boolean" );
00696     ADDTYPE( Date, i18n("Date"), "Date" );
00697     ADDTYPE( DateTime, i18n("Date and Time"), "DateTime" );
00698     ADDTYPE( Time, i18n("Time"), "Time" );
00699     ADDTYPE( Float, i18n("Single Precision Number"), "Float" );
00700     ADDTYPE( Double, i18n("Double Precision Number"), "Double" );
00701     ADDTYPE( Text, i18n("Text"), "Text" );
00702     ADDTYPE( LongText, i18n("Long Text"), "LongText" );
00703     ADDTYPE( BLOB, i18n("Object"), "BLOB" );
00704 }
00705 
00706 //-------------------------------------------------------
00707 
00708 Field::FieldTypeGroupNames::FieldTypeGroupNames()
00709  : QValueVector<QString>()
00710  , m_initialized(false)
00711 {
00712 }
00713 
00714 void Field::FieldTypeGroupNames::init()
00715 {
00716     if (m_initialized)
00717         return;
00718     m_initialized = true;
00719     resize((Field::LastTypeGroup + 1)*2);
00720 
00721     ADDGROUP( InvalidGroup, i18n("Invalid Group"), "InvalidGroup" );
00722     ADDGROUP( TextGroup, i18n("Text"), "TextGroup" );
00723     ADDGROUP( IntegerGroup, i18n("Integer Number"), "IntegerGroup" );
00724     ADDGROUP( FloatGroup, i18n("Floating Point Number"), "FloatGroup" );
00725     ADDGROUP( BooleanGroup, i18n("Yes/No"), "BooleanGroup" );
00726     ADDGROUP( DateTimeGroup, i18n("Date/Time"), "DateTimeGroup" );
00727     ADDGROUP( BLOBGroup, i18n("Object"), "BLOBGroup" );
00728 }
00729 
00730 //-------------------------------------------------------
00731 
KDE Home | KDE Accessibility Home | Description of Access Keys