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 Tue Apr 12 22:56:30 2005 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003