• Skip to content
  • Skip to link menu
KDE 3.5 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

kdeui

kpopupmenu.cpp

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

kdeui

Skip menu "kdeui"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • dcop
  • DNSSD
  • interfaces
  • Kate
  • kconf_update
  • KDECore
  • KDED
  • kdefx
  • KDEsu
  • kdeui
  • KDocTools
  • KHTML
  • KImgIO
  • KInit
  • kio
  • kioslave
  • KJS
  • KNewStuff
  • KParts
  • KUtils
Generated for API Reference by doxygen 1.5.9
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal