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 <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     QPopupMenu::mouseReleaseEvent(e);
00510 }
00511 
00512 QPopupMenu* KPopupMenu::contextMenu()
00513 {
00514     if (!d->m_ctxMenu)
00515     {
00516         d->m_ctxMenu = new QPopupMenu(this);
00517         connect(d->m_ctxMenu, SIGNAL(aboutToHide()), this, SLOT(ctxMenuHiding()));
00518     }
00519 
00520     return d->m_ctxMenu;
00521 }
00522 
00523 const QPopupMenu* KPopupMenu::contextMenu() const
00524 {
00525     return const_cast< KPopupMenu* >( this )->contextMenu();
00526 }
00527 
00528 void KPopupMenu::hideContextMenu()
00529 {
00530     KPopupMenuPrivate::s_continueCtxMenuShow = false;
00531 }
00532 
00533 int KPopupMenu::contextMenuFocusItem()
00534 {
00535     return KPopupMenuPrivate::s_highlightedItem;
00536 }
00537 
00538 KPopupMenu* KPopupMenu::contextMenuFocus()
00539 {
00540     return KPopupMenuPrivate::s_contextedMenu;
00541 }
00542 
00543 void KPopupMenu::itemHighlighted(int /* whichItem */)
00544 {
00545     if (!d->m_ctxMenu || !d->m_ctxMenu->isVisible())
00546     {
00547         return;
00548     }
00549 
00550     d->m_ctxMenu->hide();
00551     showCtxMenu(mapFromGlobal(QCursor::pos()));
00552 }
00553 
00554 void KPopupMenu::showCtxMenu(QPoint pos)
00555 {
00556     QMenuItem* item = findItem(KPopupMenuPrivate::s_highlightedItem);
00557     if (item)
00558     {
00559         QPopupMenu* subMenu = item->popup();
00560         if (subMenu)
00561         {
00562             disconnect(subMenu, SIGNAL(aboutToShow()), this, SLOT(ctxMenuHideShowingMenu()));
00563         }
00564     }
00565 
00566     KPopupMenuPrivate::s_highlightedItem = idAt(pos);
00567 
00568     if (KPopupMenuPrivate::s_highlightedItem == -1)
00569     {
00570         KPopupMenuPrivate::s_contextedMenu = 0;
00571         return;
00572     }
00573 
00574     emit aboutToShowContextMenu(this, KPopupMenuPrivate::s_highlightedItem, d->m_ctxMenu);
00575 
00576     QPopupMenu* subMenu = findItem(KPopupMenuPrivate::s_highlightedItem)->popup();
00577     if (subMenu)
00578     {
00579         connect(subMenu, SIGNAL(aboutToShow()), SLOT(ctxMenuHideShowingMenu()));
00580         QTimer::singleShot(100, subMenu, SLOT(hide()));
00581     }
00582 
00583     if (!KPopupMenuPrivate::s_continueCtxMenuShow)
00584     {
00585         KPopupMenuPrivate::s_continueCtxMenuShow = true;
00586         return;
00587     }
00588 
00589     KPopupMenuPrivate::s_contextedMenu = this;
00590     d->m_ctxMenu->popup(this->mapToGlobal(pos));
00591     connect(this, SIGNAL(highlighted(int)), this, SLOT(itemHighlighted(int)));
00592 }
00593 
00594 /*
00595  * this method helps prevent submenus popping up while we have a context menu
00596  * showing
00597  */
00598 void KPopupMenu::ctxMenuHideShowingMenu()
00599 {
00600     QMenuItem* item = findItem(KPopupMenuPrivate::s_highlightedItem);
00601     if (item)
00602     {
00603         QPopupMenu* subMenu = item->popup();
00604         if (subMenu)
00605         {
00606             QTimer::singleShot(0, subMenu, SLOT(hide()));
00607         }
00608     }
00609 }
00610 
00611 void KPopupMenu::ctxMenuHiding()
00612 {
00613     if (KPopupMenuPrivate::s_highlightedItem)
00614     {
00615         QPopupMenu* subMenu = findItem(KPopupMenuPrivate::s_highlightedItem)->popup();
00616         if (subMenu)
00617         {
00618             disconnect(subMenu, SIGNAL(aboutToShow()), this, SLOT(ctxMenuHideShowingMenu()));
00619         }
00620     }
00621 
00622     disconnect(this, SIGNAL(highlighted(int)), this, SLOT(itemHighlighted(int)));
00623     KPopupMenuPrivate::s_continueCtxMenuShow = true;
00624 }
00625 
00626 void KPopupMenu::contextMenuEvent(QContextMenuEvent* e)
00627 {
00628     if (d->m_ctxMenu)
00629     {
00630         if (e->reason() == QContextMenuEvent::Mouse)
00631         {
00632             showCtxMenu(e->pos());
00633         }
00634         else if (actItem != -1)
00635         {
00636             showCtxMenu(itemGeometry(actItem).center());
00637         }
00638 
00639         e->accept();
00640         return;
00641     }
00642 
00643     QPopupMenu::contextMenuEvent(e);
00644 }
00645 
00646 void KPopupMenu::hideEvent(QHideEvent*)
00647 {
00648     if (d->m_ctxMenu && d->m_ctxMenu->isVisible())
00649     {
00650         // we need to block signals here when the ctxMenu is showing
00651         // to prevent the QPopupMenu::activated(int) signal from emitting
00652         // when hiding with a context menu, the user doesn't expect the
00653         // menu to actually do anything.
00654         // since hideEvent gets called very late in the process of hiding
00655         // (deep within QWidget::hide) the activated(int) signal is the
00656         // last signal to be emitted, even after things like aboutToHide()
00657         // AJS
00658         blockSignals(true);
00659         d->m_ctxMenu->hide();
00660         blockSignals(false);
00661     }
00662 }
00667 // Obsolete
00668 KPopupMenu::KPopupMenu(const QString& title, QWidget *parent, const char *name)
00669     : QPopupMenu(parent, name)
00670 {
00671     d = new KPopupMenuPrivate;
00672     insertTitle(title);
00673 }
00674 
00675 // Obsolete
00676 void KPopupMenu::setTitle(const QString &title)
00677 {
00678     KPopupTitle *titleItem = new KPopupTitle();
00679     titleItem->setTitle(title);
00680     insertItem(titleItem);
00681     d->m_lastTitle = title;
00682 }
00683 
00684 void KPopupTitle::virtual_hook( int, void* )
00685 { /*BASE::virtual_hook( id, data );*/ }
00686 
00687 void KPopupMenu::virtual_hook( int, void* )
00688 { /*BASE::virtual_hook( id, data );*/ }
00689 
00690 #include "kpopupmenu.moc"
KDE Logo
This file is part of the documentation for kdeui Library Version 3.4.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Apr 28 01:34:49 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003