kdeui Library API Documentation

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 <meddie@yoyo.its.monash.edu.au>
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         , m_ctxMenu(0)
00130     {}
00131 
00132     ~KPopupMenuPrivate ()
00133     {
00134         delete m_ctxMenu;
00135     }
00136 
00137     QString m_lastTitle;
00138 
00139     // variables for keyboard navigation
00140     QTimer clearTimer;
00141 
00142     bool noMatches : 1;
00143     bool shortcuts : 1;
00144     bool autoExec : 1;
00145 
00146     QString keySeq;
00147     QString originalText;
00148 
00149     int lastHitIndex;
00150 
00151     // support for RMB menus on menus
00152     QPopupMenu* m_ctxMenu;
00153     static bool s_continueCtxMenuShow;
00154     static int s_highlightedItem;
00155     static KPopupMenu* s_contextedMenu;
00156 };
00157 
00158 int KPopupMenu::KPopupMenuPrivate::s_highlightedItem(-1);
00159 KPopupMenu* KPopupMenu::KPopupMenuPrivate::s_contextedMenu(0);
00160 bool KPopupMenu::KPopupMenuPrivate::s_continueCtxMenuShow(true);
00161 
00162 KPopupMenu::KPopupMenu(QWidget *parent, const char *name)
00163     : QPopupMenu(parent, name)
00164 {
00165     d = new KPopupMenuPrivate;
00166     resetKeyboardVars();
00167     connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars()));
00168 }
00169 
00170 KPopupMenu::~KPopupMenu()
00171 {
00172     if (KPopupMenuPrivate::s_contextedMenu == this)
00173     {
00174         KPopupMenuPrivate::s_contextedMenu = 0;
00175         KPopupMenuPrivate::s_highlightedItem = -1;
00176     }
00177     
00178     delete d;
00179 }
00180 
00181 int KPopupMenu::insertTitle(const QString &text, int id, int index)
00182 {
00183     KPopupTitle *titleItem = new KPopupTitle();
00184     titleItem->setTitle(text);
00185     int ret = insertItem(titleItem, id, index);
00186     setItemEnabled(ret, false);
00187     return ret;
00188 }
00189 
00190 int KPopupMenu::insertTitle(const QPixmap &icon, const QString &text, int id,
00191                             int index)
00192 {
00193     KPopupTitle *titleItem = new KPopupTitle();
00194     titleItem->setTitle(text, &icon);
00195     int ret = insertItem(titleItem, id, index);
00196     setItemEnabled(ret, false);
00197     return ret;
00198 }
00199 
00200 void KPopupMenu::changeTitle(int id, const QString &text)
00201 {
00202     QMenuItem *item = findItem(id);
00203     if(item){
00204         if(item->widget())
00205             ((KPopupTitle *)item->widget())->setTitle(text);
00206 #ifndef NDEBUG
00207         else
00208             kdWarning() << "KPopupMenu: changeTitle() called with non-title id "<< id << endl;
00209 #endif
00210     }
00211 #ifndef NDEBUG
00212     else
00213         kdWarning() << "KPopupMenu: changeTitle() called with invalid id " << id << endl;
00214 #endif
00215 }
00216 
00217 void KPopupMenu::changeTitle(int id, const QPixmap &icon, const QString &text)
00218 {
00219     QMenuItem *item = findItem(id);
00220     if(item){
00221         if(item->widget())
00222             ((KPopupTitle *)item->widget())->setTitle(text, &icon);
00223 #ifndef NDEBUG
00224         else
00225             kdWarning() << "KPopupMenu: changeTitle() called with non-title id "<< id << endl;
00226 #endif
00227     }
00228 #ifndef NDEBUG
00229     else
00230         kdWarning() << "KPopupMenu: changeTitle() called with invalid id " << id << endl;
00231 #endif
00232 }
00233 
00234 QString KPopupMenu::title(int id) const
00235 {
00236     if(id == -1) // obsolete
00237         return(d->m_lastTitle);
00238     QMenuItem *item = findItem(id);
00239     if(item){
00240         if(item->widget())
00241             return(((KPopupTitle *)item->widget())->title());
00242         else
00243             qWarning("KPopupMenu: title() called with non-title id %d.", id);
00244     }
00245     else
00246         qWarning("KPopupMenu: title() called with invalid id %d.", id);
00247     return(QString::null);
00248 }
00249 
00250 QPixmap KPopupMenu::titlePixmap(int id) const
00251 {
00252     QMenuItem *item = findItem(id);
00253     if(item){
00254         if(item->widget())
00255             return(((KPopupTitle *)item->widget())->icon());
00256         else
00257             qWarning("KPopupMenu: titlePixmap() called with non-title id %d.", id);
00258     }
00259     else
00260         qWarning("KPopupMenu: titlePixmap() called with invalid id %d.", id);
00261     QPixmap tmp;
00262     return(tmp);
00263 }
00264 
00268 void KPopupMenu::closeEvent(QCloseEvent*e)
00269 {
00270     if (d->shortcuts)
00271         resetKeyboardVars();
00272     QPopupMenu::closeEvent(e);
00273 }
00274 
00275 void KPopupMenu::keyPressEvent(QKeyEvent* e)
00276 {
00277     if (!d->shortcuts) {
00278         // continue event processing by Qpopup
00279         //e->ignore();
00280         QPopupMenu::keyPressEvent(e);
00281         return;
00282     }
00283 
00284     int i = 0;
00285     bool firstpass = true;
00286     QString keyString = e->text();
00287 
00288     // check for common commands dealt with by QPopup
00289     int key = e->key();
00290     if (key == Key_Escape || key == Key_Return || key == Key_Enter
00291             || key == Key_Up || key == Key_Down || key == Key_Left
00292             || key == Key_Right || key == Key_F1) {
00293 
00294         resetKeyboardVars();
00295         // continue event processing by Qpopup
00296         //e->ignore();
00297         QPopupMenu::keyPressEvent(e);
00298         return;
00299     } else if ( key == Key_Shift || key == Key_Control || key == Key_Alt || key == Key_Meta )
00300     return QPopupMenu::keyPressEvent(e);
00301 
00302     // check to see if the user wants to remove a key from the sequence (backspace)
00303     // or clear the sequence (delete)
00304     if (!d->keySeq.isNull()) {
00305 
00306         if (key == Key_Backspace) {
00307 
00308             if (d->keySeq.length() == 1) {
00309                 resetKeyboardVars();
00310                 return;
00311             }
00312 
00313             // keep the last sequence in keyString
00314             keyString = d->keySeq.left(d->keySeq.length() - 1);
00315 
00316             // allow sequence matching to be tried again
00317             resetKeyboardVars();
00318 
00319         } else if (key == Key_Delete) {
00320             resetKeyboardVars();
00321 
00322             // clear active item
00323             setActiveItem(0);
00324             return;
00325 
00326         } else if (d->noMatches) {
00327             // clear if there are no matches
00328             resetKeyboardVars();
00329 
00330             // clear active item
00331             setActiveItem(0);
00332 
00333         } else {
00334             // the key sequence is not a null string
00335             // therefore the lastHitIndex is valid
00336             i = d->lastHitIndex;
00337         }
00338     } else if (key == Key_Backspace && parentMenu) {
00339         // backspace with no chars in the buffer... go back a menu.
00340         hide();
00341         resetKeyboardVars();
00342         return;
00343     }
00344 
00345     d->keySeq += keyString;
00346     int seqLen = d->keySeq.length();
00347 
00348     for (; i < (int)count(); i++) {
00349         // compare typed text with text of this entry
00350         int j = idAt(i);
00351 
00352         // don't search disabled entries
00353         if (!isItemEnabled(j))
00354             continue;
00355 
00356         QString thisText;
00357 
00358         // retrieve the right text
00359         // (the last selected item one may have additional ampersands)
00360         if (i == d->lastHitIndex)
00361             thisText = d->originalText;
00362         else
00363             thisText = text(j);
00364 
00365         // if there is an accelerator present, remove it
00366         if ((int)accel(j) != 0)
00367             thisText = thisText.replace("&", QString::null);
00368 
00369         // chop text to the search length
00370         thisText = thisText.left(seqLen);
00371 
00372         // do the search
00373         if (thisText.find(d->keySeq, 0, false) == 0) {
00374 
00375             if (firstpass) {
00376                 // match
00377                 setActiveItem(i);
00378 
00379                 // check to see if we're underlining a different item
00380                 if (d->lastHitIndex != i)
00381                     // yes; revert the underlining
00382                     changeItem(idAt(d->lastHitIndex), d->originalText);
00383 
00384                 // set the original text if it's a different item
00385                 if (d->lastHitIndex != i || d->lastHitIndex == -1)
00386                     d->originalText = text(j);
00387 
00388                 // underline the currently selected item
00389                 changeItem(j, underlineText(d->originalText, d->keySeq.length()));
00390 
00391                 // remember what's going on
00392                 d->lastHitIndex = i;
00393 
00394                 // start/restart the clear timer
00395                 d->clearTimer.start(5000, true);
00396 
00397                 // go around for another try, to see if we can execute
00398                 firstpass = false;
00399             } else {
00400                 // don't allow execution
00401                 return;
00402             }
00403         }
00404 
00405         // fall through to allow execution
00406     }
00407 
00408     if (!firstpass) {
00409         if (d->autoExec) {
00410             // activate anything
00411             activateItemAt(d->lastHitIndex);
00412             resetKeyboardVars();
00413 
00414         } else if (findItem(idAt(d->lastHitIndex)) &&
00415                  findItem(idAt(d->lastHitIndex))->popup()) {
00416             // only activate sub-menus
00417             activateItemAt(d->lastHitIndex);
00418             resetKeyboardVars();
00419         }
00420 
00421         return;
00422     }
00423 
00424     // no matches whatsoever, clean up
00425     resetKeyboardVars(true);
00426     //e->ignore();
00427     QPopupMenu::keyPressEvent(e);
00428 }
00429 
00430 bool KPopupMenu::focusNextPrevChild( bool next )
00431 {
00432     resetKeyboardVars();
00433     return QPopupMenu::focusNextPrevChild( next );
00434 }
00435 
00436 QString KPopupMenu::underlineText(const QString& text, uint length)
00437 {
00438     QString ret = text;
00439     for (uint i = 0; i < length; i++) {
00440         if (ret[2*i] != '&')
00441             ret.insert(2*i, "&");
00442     }
00443     return ret;
00444 }
00445 
00446 void KPopupMenu::resetKeyboardVars(bool noMatches /* = false */)
00447 {
00448     // Clean up keyboard variables
00449     if (d->lastHitIndex != -1) {
00450         changeItem(idAt(d->lastHitIndex), d->originalText);
00451         d->lastHitIndex = -1;
00452     }
00453 
00454     if (!noMatches) {
00455         d->keySeq = QString::null;
00456     }
00457 
00458     d->noMatches = noMatches;
00459 }
00460 
00461 void KPopupMenu::setKeyboardShortcutsEnabled(bool enable)
00462 {
00463     d->shortcuts = enable;
00464 }
00465 
00466 void KPopupMenu::setKeyboardShortcutsExecute(bool enable)
00467 {
00468     d->autoExec = enable;
00469 }
00478 void KPopupMenu::mousePressEvent(QMouseEvent* e)
00479 {
00480     if (d->m_ctxMenu && d->m_ctxMenu->isVisible())
00481     {
00482         // hide on a second context menu event
00483         d->m_ctxMenu->hide();
00484     }
00485 
00486     QPopupMenu::mousePressEvent(e);
00487 }
00488 
00489 QPopupMenu* KPopupMenu::contextMenu()
00490 {
00491     if (!d->m_ctxMenu)
00492     {
00493         d->m_ctxMenu = new QPopupMenu(this);
00494         connect(d->m_ctxMenu, SIGNAL(aboutToHide()), this, SLOT(ctxMenuHiding()));
00495     }
00496 
00497     return d->m_ctxMenu;
00498 }
00499 
00500 const QPopupMenu* KPopupMenu::contextMenu() const
00501 {
00502     return const_cast< KPopupMenu* >( this )->contextMenu();
00503 }
00504 
00505 void KPopupMenu::hideContextMenu()
00506 {
00507     KPopupMenuPrivate::s_continueCtxMenuShow = false;
00508 }
00509 
00510 int KPopupMenu::contextMenuFocusItem()
00511 {
00512     return KPopupMenuPrivate::s_highlightedItem;
00513 }
00514 
00515 KPopupMenu* KPopupMenu::contextMenuFocus()
00516 {
00517     return KPopupMenuPrivate::s_contextedMenu;
00518 }
00519 
00520 void KPopupMenu::itemHighlighted(int /* whichItem */)
00521 {
00522     if (!d->m_ctxMenu || !d->m_ctxMenu->isVisible())
00523     {
00524         return;
00525     }
00526 
00527     d->m_ctxMenu->hide();
00528     showCtxMenu(mapFromGlobal(QCursor::pos()));
00529 }
00530 
00531 void KPopupMenu::showCtxMenu(QPoint pos)
00532 {
00533     QMenuItem* item = findItem(KPopupMenuPrivate::s_highlightedItem);
00534     if (item)
00535     {
00536         QPopupMenu* subMenu = item->popup();
00537         if (subMenu)
00538         {
00539             disconnect(subMenu, SIGNAL(aboutToShow()), this, SLOT(ctxMenuHideShowingMenu()));
00540         }
00541     }
00542 
00543     KPopupMenuPrivate::s_highlightedItem = idAt(pos);
00544 
00545     if (KPopupMenuPrivate::s_highlightedItem == -1)
00546     {
00547         KPopupMenuPrivate::s_contextedMenu = 0;
00548         return;
00549     }
00550 
00551     emit aboutToShowContextMenu(this, KPopupMenuPrivate::s_highlightedItem, d->m_ctxMenu);
00552 
00553     QPopupMenu* subMenu = findItem(KPopupMenuPrivate::s_highlightedItem)->popup();
00554     if (subMenu)
00555     {
00556         connect(subMenu, SIGNAL(aboutToShow()), SLOT(ctxMenuHideShowingMenu()));
00557         QTimer::singleShot(100, subMenu, SLOT(hide()));
00558     }
00559 
00560     if (!KPopupMenuPrivate::s_continueCtxMenuShow)
00561     {
00562         KPopupMenuPrivate::s_continueCtxMenuShow = true;
00563         return;
00564     }
00565 
00566     KPopupMenuPrivate::s_contextedMenu = this;
00567     d->m_ctxMenu->popup(this->mapToGlobal(pos));
00568     connect(this, SIGNAL(highlighted(int)), this, SLOT(itemHighlighted(int)));
00569 }
00570 
00571 /*
00572  * this method helps prevent submenus popping up while we have a context menu
00573  * showing
00574  */
00575 void KPopupMenu::ctxMenuHideShowingMenu()
00576 {
00577     QMenuItem* item = findItem(KPopupMenuPrivate::s_highlightedItem);
00578     if (item)
00579     {
00580         QPopupMenu* subMenu = item->popup();
00581         if (subMenu)
00582         {
00583             QTimer::singleShot(0, subMenu, SLOT(hide()));
00584         }
00585     }
00586 }
00587 
00588 void KPopupMenu::ctxMenuHiding()
00589 {
00590     if (KPopupMenuPrivate::s_highlightedItem != 0)
00591     {
00592         QPopupMenu* subMenu = findItem(KPopupMenuPrivate::s_highlightedItem)->popup();
00593         if (subMenu)
00594         {
00595             disconnect(subMenu, SIGNAL(aboutToShow()), this, SLOT(ctxMenuHideShowingMenu()));
00596         }
00597     }
00598 
00599     disconnect(this, SIGNAL(highlighted(int)), this, SLOT(itemHighlighted(int)));
00600     KPopupMenuPrivate::s_continueCtxMenuShow = true;
00601 }
00602 
00603 void KPopupMenu::contextMenuEvent(QContextMenuEvent* e)
00604 {
00605     if (d->m_ctxMenu)
00606     {
00607         if (e->reason() == QContextMenuEvent::Mouse)
00608         {
00609             showCtxMenu(e->pos());
00610         }
00611         else if (actItem != -1)
00612         {   
00613             showCtxMenu(itemGeometry(actItem).center());
00614         }
00615     
00616         e->accept();
00617         return;
00618     }
00619 
00620     QPopupMenu::contextMenuEvent(e);
00621 }
00622 
00623 void KPopupMenu::hideEvent(QHideEvent*)
00624 {
00625     if (d->m_ctxMenu && d->m_ctxMenu->isVisible())
00626     {
00627         // we need to block signals here when the ctxMenu is showing
00628         // to prevent the QPopupMenu::activated(int) signal from emitting
00629         // when hiding with a context menu, the user doesn't expect the
00630         // menu to actually do anything.
00631         // since hideEvent gets called very late in the process of hiding
00632         // (deep within QWidget::hide) the activated(int) signal is the
00633         // last signal to be emitted, even after things like aboutToHide()
00634         // AJS
00635         blockSignals(true);
00636         d->m_ctxMenu->hide();
00637         blockSignals(false);
00638     }
00639 }
00644 // Obsolete
00645 KPopupMenu::KPopupMenu(const QString& title, QWidget *parent, const char *name)
00646     : QPopupMenu(parent, name)
00647 {
00648     d = new KPopupMenuPrivate;
00649     insertTitle(title);
00650 }
00651 
00652 // Obsolete
00653 void KPopupMenu::setTitle(const QString &title)
00654 {
00655     KPopupTitle *titleItem = new KPopupTitle();
00656     titleItem->setTitle(title);
00657     insertItem(titleItem);
00658     d->m_lastTitle = title;
00659 }
00660 
00661 void KPopupTitle::virtual_hook( int, void* )
00662 { /*BASE::virtual_hook( id, data );*/ }
00663 
00664 void KPopupMenu::virtual_hook( int, void* )
00665 { /*BASE::virtual_hook( id, data );*/ }
00666 
00667 #include "kpopupmenu.moc"
KDE Logo
This file is part of the documentation for kdeui Library Version 3.2.3.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Aug 4 05:24:00 2004 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2003