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 Mon Aug 30 22:53:59 2004 by doxygen 1.3.8 written by Dimitri van Heesch, © 1997-2003