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