| # kdeui Library API Documentation
Overview | Class Hierarchy | Classes | Classes (annotated) | Members | Namespaces | Source Files

# kpopupmenu.cpp

    00001 /* This file is part of the KDE libraries
    00002    Copyright (C) 2000 Daniel M. Duley <mosfet@kde.org>
    00003    Copyright (C) 2002 Hamish Rodda <rodda@kde.org>
    00004
    00005    This library is free software; you can redistribute it and/or
    00006    modify it under the terms of the GNU Library General Public
    00007    License version 2 as published by the Free Software Foundation.
    00008
    00009    This library is distributed in the hope that it will be useful,
    00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
    00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    00012    Library General Public License for more details.
    00013
    00014    You should have received a copy of the GNU Library General Public License
    00015    along with this library; see the file COPYING.LIB.  If not, write to
    00016    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    00017    Boston, MA 02111-1307, USA.
    00018 */
    00019 #include <qcursor.h>
    00020 #include <qpainter.h>
    00021 #include <qtimer.h>
    00022 #include <qfontmetrics.h>
    00023 #include <qstyle.h>
    00024
    00025 #include "kpopupmenu.h"
    00026
    00027 #include <kdebug.h>
    00028 #include <kapplication.h>
    00029
    00030 KPopupTitle::KPopupTitle(QWidget *parent, const char *name)
    00031     : QWidget(parent, name)
    00032 {
    00033     setMinimumSize(16, fontMetrics().height()+8);
    00034 }
    00035
    00036 KPopupTitle::KPopupTitle(KPixmapEffect::GradientType /* gradient */,
    00037                          const QColor &/* color */, const QColor &/* textColor */,
    00038                          QWidget *parent, const char *name)
    00039    : QWidget(parent, name)
    00040 {
    00041     calcSize();
    00042 }
    00043
    00044 KPopupTitle::KPopupTitle(const KPixmap & /* background */, const QColor &/* color */,
    00045                          const QColor &/* textColor */, QWidget *parent,
    00046                          const char *name)
    00047     : QWidget(parent, name)
    00048 {
    00049     calcSize();
    00050 }
    00051
    00052 void KPopupTitle::setTitle(const QString &text, const QPixmap *icon)
    00053 {
    00054     titleStr = text;
    00055     if (icon)
    00056         miniicon = *icon;
    00057     else
    00058         miniicon.resize(0, 0);
    00059
    00060     calcSize();
    00061 }
    00062
    00063 void KPopupTitle::setText( const QString &text )
    00064 {
    00065     titleStr = text;
    00066     calcSize();
    00067 }
    00068
    00069 void KPopupTitle::setIcon( const QPixmap &pix )
    00070 {
    00071     miniicon = pix;
    00072     calcSize();
    00073 }
    00074
    00075 void KPopupTitle::calcSize()
    00076 {
    00077     QFont f = font();
    00078     f.setBold(true);
    00079     int w = miniicon.width()+QFontMetrics(f).width(titleStr);
    00080     int h = QMAX( fontMetrics().height(), miniicon.height() );
    00081     setMinimumSize( w+16, h+8 );
    00082 }
    00083
    00084 void KPopupTitle::paintEvent(QPaintEvent *)
    00085 {
    00086     QRect r(rect());
    00087     QPainter p(this);
    00088     kapp->style().drawPrimitive(QStyle::PE_HeaderSection, &p, r, palette().active());
    00089
    00090     if (!miniicon.isNull())
    00091         p.drawPixmap(4, (r.height()-miniicon.height())/2, miniicon);
    00092
    00093     if (!titleStr.isNull())
    00094     {
    00095         p.setPen(palette().active().text());
    00096         QFont f = p.font();
    00097         f.setBold(true);
    00098         p.setFont(f);
    00099         if(!miniicon.isNull())
    00100         {
    00101             p.drawText(miniicon.width()+8, 0, width()-(miniicon.width()+8),
    00102                        height(), AlignLeft | AlignVCenter | SingleLine,
    00103                        titleStr);
    00104         }
    00105         else
    00106         {
    00107             p.drawText(0, 0, width(), height(),
    00108                        AlignCenter | SingleLine, titleStr);
    00109         }
    00110     }
    00111
    00112     p.setPen(palette().active().highlight());
    00113     p.drawLine(0, 0, r.right(), 0);
    00114 }
    00115
    00116 QSize KPopupTitle::sizeHint() const
    00117 {
    00118     return minimumSize();
    00119 }
    00120
    00121 class KPopupMenu::KPopupMenuPrivate
    00122 {
    00123 public:
    00124     KPopupMenuPrivate ()
    00125         : noMatches(false)
    00126         , shortcuts(false)
    00127         , autoExec(false)
    00128         , lastHitIndex(-1)
    00129         , state(Qt::NoButton)
    00130         , m_ctxMenu(0)
    00131     {}
    00132
    00133     ~KPopupMenuPrivate ()
    00134     {
    00135         delete m_ctxMenu;
    00136     }
    00137
    00138     QString m_lastTitle;
    00139
    00140     // variables for keyboard navigation
    00141     QTimer clearTimer;
    00142
    00143     bool noMatches : 1;
    00144     bool shortcuts : 1;
    00145     bool autoExec : 1;
    00146
    00147     QString keySeq;
    00148     QString originalText;
    00149
    00150     int lastHitIndex;
    00151     Qt::ButtonState state;
    00152
    00153     // support for RMB menus on menus
    00154     QPopupMenu* m_ctxMenu;
    00155     static bool s_continueCtxMenuShow;
    00156     static int s_highlightedItem;
    00157     static KPopupMenu* s_contextedMenu;
    00158 };
    00159
    00160 int KPopupMenu::KPopupMenuPrivate::s_highlightedItem(-1);
    00161 KPopupMenu* KPopupMenu::KPopupMenuPrivate::s_contextedMenu(0);
    00162 bool KPopupMenu::KPopupMenuPrivate::s_continueCtxMenuShow(true);
    00163
    00164 KPopupMenu::KPopupMenu(QWidget *parent, const char *name)
    00165     : QPopupMenu(parent, name)
    00166 {
    00167     d = new KPopupMenuPrivate;
    00168     resetKeyboardVars();
    00169     connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars()));
    00170 }
    00171
    00172 KPopupMenu::~KPopupMenu()
    00173 {
    00174     if (KPopupMenuPrivate::s_contextedMenu == this)
    00175     {
    00176         KPopupMenuPrivate::s_contextedMenu = 0;
    00177         KPopupMenuPrivate::s_highlightedItem = -1;
    00178     }
    00179
    00180     delete d;
    00181 }
    00182
    00183 int KPopupMenu::insertTitle(const QString &text, int id, int index)
    00184 {
    00185     KPopupTitle *titleItem = new KPopupTitle();
    00186     titleItem->setTitle(text);
    00187     int ret = insertItem(titleItem, id, index);
    00188     setItemEnabled(ret, false);
    00189     return ret;
    00190 }
    00191
    00192 int KPopupMenu::insertTitle(const QPixmap &icon, const QString &text, int id,
    00193                             int index)
    00194 {
    00195     KPopupTitle *titleItem = new KPopupTitle();
    00196     titleItem->setTitle(text, &icon);
    00197     int ret = insertItem(titleItem, id, index);
    00198     setItemEnabled(ret, false);
    00199     return ret;
    00200 }
    00201
    00202 void KPopupMenu::changeTitle(int id, const QString &text)
    00203 {
    00204     QMenuItem *item = findItem(id);
    00205     if(item){
    00206         if(item->widget())
    00207             ((KPopupTitle *)item->widget())->setTitle(text);
    00208 #ifndef NDEBUG
    00209         else
    00210             kdWarning() << "KPopupMenu: changeTitle() called with non-title id "<< id << endl;
    00211 #endif
    00212     }
    00213 #ifndef NDEBUG
    00214     else
    00215         kdWarning() << "KPopupMenu: changeTitle() called with invalid id " << id << endl;
    00216 #endif
    00217 }
    00218
    00219 void KPopupMenu::changeTitle(int id, const QPixmap &icon, const QString &text)
    00220 {
    00221     QMenuItem *item = findItem(id);
    00222     if(item){
    00223         if(item->widget())
    00224             ((KPopupTitle *)item->widget())->setTitle(text, &icon);
    00225 #ifndef NDEBUG
    00226         else
    00227             kdWarning() << "KPopupMenu: changeTitle() called with non-title id "<< id << endl;
    00228 #endif
    00229     }
    00230 #ifndef NDEBUG
    00231     else
    00232         kdWarning() << "KPopupMenu: changeTitle() called with invalid id " << id << endl;
    00233 #endif
    00234 }
    00235
    00236 QString KPopupMenu::title(int id) const
    00237 {
    00238     if(id == -1) // obsolete
    00239         return d->m_lastTitle;
    00240     QMenuItem *item = findItem(id);
    00241     if(item){
    00242         if(item->widget())
    00243             return ((KPopupTitle *)item->widget())->title();
    00244         else
    00245             qWarning("KPopupMenu: title() called with non-title id %d.", id);
    00246     }
    00247     else
    00248         qWarning("KPopupMenu: title() called with invalid id %d.", id);
    00249     return QString::null;
    00250 }
    00251
    00252 QPixmap KPopupMenu::titlePixmap(int id) const
    00253 {
    00254     QMenuItem *item = findItem(id);
    00255     if(item){
    00256         if(item->widget())
    00257             return ((KPopupTitle *)item->widget())->icon();
    00258         else
    00259             qWarning("KPopupMenu: titlePixmap() called with non-title id %d.", id);
    00260     }
    00261     else
    00262         qWarning("KPopupMenu: titlePixmap() called with invalid id %d.", id);
    00263     QPixmap tmp;
    00264     return tmp;
    00265 }
    00266
    00270 void KPopupMenu::closeEvent(QCloseEvent*e)
    00271 {
    00272     if (d->shortcuts)
    00273         resetKeyboardVars();
    00274     QPopupMenu::closeEvent(e);
    00275 }
    00276
    00277 void KPopupMenu::activateItemAt(int index)
    00278 {
    00279     d->state = Qt::NoButton;
    00280     QPopupMenu::activateItemAt(index);
    00281 }
    00282
    00283 Qt::ButtonState KPopupMenu::state() const
    00284 {
    00285     return d->state;
    00286 }
    00287
    00288 void KPopupMenu::keyPressEvent(QKeyEvent* e)
    00289 {
    00290     d->state = Qt::NoButton;
    00291     if (!d->shortcuts) {
    00292         // continue event processing by Qpopup
    00293         //e->ignore();
    00294         d->state = e->state();
    00295         QPopupMenu::keyPressEvent(e);
    00296         return;
    00297     }
    00298
    00299     int i = 0;
    00300     bool firstpass = true;
    00301     QString keyString = e->text();
    00302
    00303     // check for common commands dealt with by QPopup
    00304     int key = e->key();
    00305     if (key == Key_Escape || key == Key_Return || key == Key_Enter
    00306             || key == Key_Up || key == Key_Down || key == Key_Left
    00307             || key == Key_Right || key == Key_F1) {
    00308
    00309         resetKeyboardVars();
    00310         // continue event processing by Qpopup
    00311         //e->ignore();
    00312         d->state = e->state();
    00313         QPopupMenu::keyPressEvent(e);
    00314         return;
    00315     } else if ( key == Key_Shift || key == Key_Control || key == Key_Alt || key == Key_Meta )
    00316     return QPopupMenu::keyPressEvent(e);
    00317
    00318     // check to see if the user wants to remove a key from the sequence (backspace)
    00319     // or clear the sequence (delete)
    00320     if (!d->keySeq.isNull()) {
    00321
    00322         if (key == Key_Backspace) {
    00323
    00324             if (d->keySeq.length() == 1) {
    00325                 resetKeyboardVars();
    00326                 return;
    00327             }
    00328
    00329             // keep the last sequence in keyString
    00330             keyString = d->keySeq.left(d->keySeq.length() - 1);
    00331
    00332             // allow sequence matching to be tried again
    00333             resetKeyboardVars();
    00334
    00335         } else if (key == Key_Delete) {
    00336             resetKeyboardVars();
    00337
    00338             // clear active item
    00339             setActiveItem(0);
    00340             return;
    00341
    00342         } else if (d->noMatches) {
    00343             // clear if there are no matches
    00344             resetKeyboardVars();
    00345
    00346             // clear active item
    00347             setActiveItem(0);
    00348
    00349         } else {
    00350             // the key sequence is not a null string
    00351             // therefore the lastHitIndex is valid
    00352             i = d->lastHitIndex;
    00353         }
    00354     } else if (key == Key_Backspace && parentMenu) {
    00355         // backspace with no chars in the buffer... go back a menu.
    00356         hide();
    00357         resetKeyboardVars();
    00358         return;
    00359     }
    00360
    00361     d->keySeq += keyString;
    00362     int seqLen = d->keySeq.length();
    00363
    00364     for (; i < (int)count(); i++) {
    00365         // compare typed text with text of this entry
    00366         int j = idAt(i);
    00367
    00368         // don't search disabled entries
    00369         if (!isItemEnabled(j))
    00370             continue;
    00371
    00372         QString thisText;
    00373
    00374         // retrieve the right text
    00375         // (the last selected item one may have additional ampersands)
    00376         if (i == d->lastHitIndex)
    00377             thisText = d->originalText;
    00378         else
    00379             thisText = text(j);
    00380
    00381         // if there is an accelerator present, remove it
    00382         if ((int)accel(j) != 0)
    00383             thisText = thisText.replace("&", QString::null);
    00384
    00385         // chop text to the search length
    00386         thisText = thisText.left(seqLen);
    00387
    00388         // do the search
    00389         if (!thisText.find(d->keySeq, 0, false)) {
    00390
    00391             if (firstpass) {
    00392                 // match
    00393                 setActiveItem(i);
    00394
    00395                 // check to see if we're underlining a different item
    00396                 if (d->lastHitIndex != i)
    00397                     // yes; revert the underlining
    00398                     changeItem(idAt(d->lastHitIndex), d->originalText);
    00399
    00400                 // set the original text if it's a different item
    00401                 if (d->lastHitIndex != i || d->lastHitIndex == -1)
    00402                     d->originalText = text(j);
    00403
    00404                 // underline the currently selected item
    00405                 changeItem(j, underlineText(d->originalText, d->keySeq.length()));
    00406
    00407                 // remember what's going on
    00408                 d->lastHitIndex = i;
    00409
    00410                 // start/restart the clear timer
    00411                 d->clearTimer.start(5000, true);
    00412
    00413                 // go around for another try, to see if we can execute
    00414                 firstpass = false;
    00415             } else {
    00416                 // don't allow execution
    00417                 return;
    00418             }
    00419         }
    00420
    00421         // fall through to allow execution
    00422     }
    00423
    00424     if (!firstpass) {
    00425         if (d->autoExec) {
    00426             // activate anything
    00427             activateItemAt(d->lastHitIndex);
    00428             resetKeyboardVars();
    00429
    00430         } else if (findItem(idAt(d->lastHitIndex)) &&
    00431                  findItem(idAt(d->lastHitIndex))->popup()) {
    00432             // only activate sub-menus
    00433             activateItemAt(d->lastHitIndex);
    00434             resetKeyboardVars();
    00435         }
    00436
    00437         return;
    00438     }
    00439
    00440     // no matches whatsoever, clean up
    00441     resetKeyboardVars(true);
    00442     //e->ignore();
    00443     QPopupMenu::keyPressEvent(e);
    00444 }
    00445
    00446 bool KPopupMenu::focusNextPrevChild( bool next )
    00447 {
    00448     resetKeyboardVars();
    00449     return QPopupMenu::focusNextPrevChild( next );
    00450 }
    00451
    00452 QString KPopupMenu::underlineText(const QString& text, uint length)
    00453 {
    00454     QString ret = text;
    00455     for (uint i = 0; i < length; i++) {
    00456         if (ret[2*i] != '&')
    00457             ret.insert(2*i, "&");
    00458     }
    00459     return ret;
    00460 }
    00461
    00462 void KPopupMenu::resetKeyboardVars(bool noMatches /* = false */)
    00463 {
    00464     // Clean up keyboard variables
    00465     if (d->lastHitIndex != -1) {
    00466         changeItem(idAt(d->lastHitIndex), d->originalText);
    00467         d->lastHitIndex = -1;
    00468     }
    00469
    00470     if (!noMatches) {
    00471         d->keySeq = QString::null;
    00472     }
    00473
    00474     d->noMatches = noMatches;
    00475 }
    00476
    00477 void KPopupMenu::setKeyboardShortcutsEnabled(bool enable)
    00478 {
    00479     d->shortcuts = enable;
    00480 }
    00481
    00482 void KPopupMenu::setKeyboardShortcutsExecute(bool enable)
    00483 {
    00484     d->autoExec = enable;
    00485 }
    00494 void KPopupMenu::mousePressEvent(QMouseEvent* e)
    00495 {
    00496     if (d->m_ctxMenu && d->m_ctxMenu->isVisible())
    00497     {
    00498         // hide on a second context menu event
    00499         d->m_ctxMenu->hide();
    00500     }
    00501
    00502     QPopupMenu::mousePressEvent(e);
    00503 }
    00504
    00505 void KPopupMenu::mouseReleaseEvent(QMouseEvent* e)
    00506 {
    00507     // Save the button, and the modifiers from state()
    00508     d->state = Qt::ButtonState(e->button() | (e->state() & KeyButtonMask));
    00509
    00510     if ( !d->m_ctxMenu || !d->m_ctxMenu->isVisible() )
    00511     QPopupMenu::mouseReleaseEvent(e);
    00512 }
    00513
    00514 QPopupMenu* KPopupMenu::contextMenu()
    00515 {
    00516     if (!d->m_ctxMenu)
    00517     {
    00518         d->m_ctxMenu = new QPopupMenu(this);
    00519         connect(d->m_ctxMenu, SIGNAL(aboutToHide()), this, SLOT(ctxMenuHiding()));
    00520     }
    00521
    00522     return d->m_ctxMenu;
    00523 }
    00524
    00525 const QPopupMenu* KPopupMenu::contextMenu() const
    00526 {
    00527     return const_cast< KPopupMenu* >( this )->contextMenu();
    00528 }
    00529
    00530 void KPopupMenu::hideContextMenu()
    00531 {
    00532     KPopupMenuPrivate::s_continueCtxMenuShow = false;
    00533 }
    00534
    00535 int KPopupMenu::contextMenuFocusItem()
    00536 {
    00537     return KPopupMenuPrivate::s_highlightedItem;
    00538 }
    00539
    00540 KPopupMenu* KPopupMenu::contextMenuFocus()
    00541 {
    00542     return KPopupMenuPrivate::s_contextedMenu;
    00543 }
    00544
    00545 void KPopupMenu::itemHighlighted(int /* whichItem */)
    00546 {
    00547     if (!d->m_ctxMenu || !d->m_ctxMenu->isVisible())
    00548     {
    00549         return;
    00550     }
    00551
    00552     d->m_ctxMenu->hide();
    00553     showCtxMenu(mapFromGlobal(QCursor::pos()));
    00554 }
    00555
    00556 void KPopupMenu::showCtxMenu(QPoint pos)
    00557 {
    00558     QMenuItem* item = findItem(KPopupMenuPrivate::s_highlightedItem);
    00559     if (item)
    00560     {
    00561         QPopupMenu* subMenu = item->popup();
    00562         if (subMenu)
    00563         {
    00564             disconnect(subMenu, SIGNAL(aboutToShow()), this, SLOT(ctxMenuHideShowingMenu()));
    00565         }
    00566     }
    00567
    00568     KPopupMenuPrivate::s_highlightedItem = idAt(pos);
    00569
    00570     if (KPopupMenuPrivate::s_highlightedItem == -1)
    00571     {
    00572         KPopupMenuPrivate::s_contextedMenu = 0;
    00573         return;
    00574     }
    00575
    00576     emit aboutToShowContextMenu(this, KPopupMenuPrivate::s_highlightedItem, d->m_ctxMenu);
    00577
    00578     QPopupMenu* subMenu = findItem(KPopupMenuPrivate::s_highlightedItem)->popup();
    00579     if (subMenu)
    00580     {
    00581         connect(subMenu, SIGNAL(aboutToShow()), SLOT(ctxMenuHideShowingMenu()));
    00582         QTimer::singleShot(100, subMenu, SLOT(hide()));
    00583     }
    00584
    00585     if (!KPopupMenuPrivate::s_continueCtxMenuShow)
    00586     {
    00587         KPopupMenuPrivate::s_continueCtxMenuShow = true;
    00588         return;
    00589     }
    00590
    00591     KPopupMenuPrivate::s_contextedMenu = this;
    00592     d->m_ctxMenu->popup(this->mapToGlobal(pos));
    00593     connect(this, SIGNAL(highlighted(int)), this, SLOT(itemHighlighted(int)));
    00594 }
    00595
    00596 /*
    00597  * this method helps prevent submenus popping up while we have a context menu
    00598  * showing
    00599  */
    00600 void KPopupMenu::ctxMenuHideShowingMenu()
    00601 {
    00602     QMenuItem* item = findItem(KPopupMenuPrivate::s_highlightedItem);
    00603     if (item)
    00604     {
    00605         QPopupMenu* subMenu = item->popup();
    00606         if (subMenu)
    00607         {
    00608             QTimer::singleShot(0, subMenu, SLOT(hide()));
    00609         }
    00610     }
    00611 }
    00612
    00613 void KPopupMenu::ctxMenuHiding()
    00614 {
    00615     if (KPopupMenuPrivate::s_highlightedItem)
    00616     {
    00617         QPopupMenu* subMenu = findItem(KPopupMenuPrivate::s_highlightedItem)->popup();
    00618         if (subMenu)
    00619         {
    00620             disconnect(subMenu, SIGNAL(aboutToShow()), this, SLOT(ctxMenuHideShowingMenu()));
    00621         }
    00622     }
    00623
    00624     disconnect(this, SIGNAL(highlighted(int)), this, SLOT(itemHighlighted(int)));
    00625     KPopupMenuPrivate::s_continueCtxMenuShow = true;
    00626 }
    00627
    00628 void KPopupMenu::contextMenuEvent(QContextMenuEvent* e)
    00629 {
    00630     if (d->m_ctxMenu)
    00631     {
    00632         if (e->reason() == QContextMenuEvent::Mouse)
    00633         {
    00634             showCtxMenu(e->pos());
    00635         }
    00636         else if (actItem != -1)
    00637         {
    00638             showCtxMenu(itemGeometry(actItem).center());
    00639         }
    00640
    00641         e->accept();
    00642         return;
    00643     }
    00644
    00645     QPopupMenu::contextMenuEvent(e);
    00646 }
    00647
    00648 void KPopupMenu::hideEvent(QHideEvent*)
    00649 {
    00650     if (d->m_ctxMenu && d->m_ctxMenu->isVisible())
    00651     {
    00652         // we need to block signals here when the ctxMenu is showing
    00653         // to prevent the QPopupMenu::activated(int) signal from emitting
    00654         // when hiding with a context menu, the user doesn't expect the
    00655         // menu to actually do anything.
    00656         // since hideEvent gets called very late in the process of hiding
    00657         // (deep within QWidget::hide) the activated(int) signal is the
    00658         // last signal to be emitted, even after things like aboutToHide()
    00659         // AJS
    00660         blockSignals(true);
    00661         d->m_ctxMenu->hide();
    00662         blockSignals(false);
    00663     }
    00664 }
    00669 // Obsolete
    00670 KPopupMenu::KPopupMenu(const QString& title, QWidget *parent, const char *name)
    00671     : QPopupMenu(parent, name)
    00672 {
    00673     d = new KPopupMenuPrivate;
    00674     insertTitle(title);
    00675 }
    00676
    00677 // Obsolete
    00678 void KPopupMenu::setTitle(const QString &title)
    00679 {
    00680     KPopupTitle *titleItem = new KPopupTitle();
    00681     titleItem->setTitle(title);
    00682     insertItem(titleItem);
    00683     d->m_lastTitle = title;
    00684 }
    00685
    00686 void KPopupTitle::virtual_hook( int, void* )
    00687 { /*BASE::virtual_hook( id, data );*/ }
    00688
    00689 void KPopupMenu::virtual_hook( int, void* )
    00690 { /*BASE::virtual_hook( id, data );*/ }
    00691
    00692 #include "kpopupmenu.moc"

This file is part of the documentation for kdeui Library Version 3.4.1.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat Jun 11 20:20:20 2005 by doxygen 1.4.1 written by Dimitri van Heesch, © 1997-2003