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., 51 Franklin Street, Fifth Floor, 00017 Boston, MA 02110-1301, USA. 00018 */ 00019 #include <tqcursor.h> 00020 #include <tqpainter.h> 00021 #include <tqtimer.h> 00022 #include <tqfontmetrics.h> 00023 00024 #ifdef USE_QT4 00025 #undef None 00026 #endif // USE_QT4 00027 00028 #include <tqstyle.h> 00029 00030 #include "kpopupmenu.h" 00031 00032 #include <kdebug.h> 00033 #include <kapplication.h> 00034 00035 KPopupTitle::KPopupTitle(TQWidget *parent, const char *name) 00036 : TQWidget(parent, name) 00037 { 00038 setMinimumSize(16, fontMetrics().height()+8); 00039 } 00040 00041 KPopupTitle::KPopupTitle(KPixmapEffect::GradientType /* gradient */, 00042 const TQColor &/* color */, const TQColor &/* textColor */, 00043 TQWidget *parent, const char *name) 00044 : TQWidget(parent, name) 00045 { 00046 calcSize(); 00047 } 00048 00049 KPopupTitle::KPopupTitle(const KPixmap & /* background */, const TQColor &/* color */, 00050 const TQColor &/* textColor */, TQWidget *parent, 00051 const char *name) 00052 : TQWidget(parent, name) 00053 { 00054 calcSize(); 00055 } 00056 00057 void KPopupTitle::setTitle(const TQString &text, const TQPixmap *icon) 00058 { 00059 titleStr = text; 00060 if (icon) 00061 miniicon = *icon; 00062 else 00063 miniicon.resize(0, 0); 00064 00065 calcSize(); 00066 } 00067 00068 void KPopupTitle::setText( const TQString &text ) 00069 { 00070 titleStr = text; 00071 calcSize(); 00072 } 00073 00074 void KPopupTitle::setIcon( const TQPixmap &pix ) 00075 { 00076 miniicon = pix; 00077 calcSize(); 00078 } 00079 00080 void KPopupTitle::calcSize() 00081 { 00082 TQFont f = font(); 00083 f.setBold(true); 00084 int w = miniicon.width()+TQFontMetrics(f).width(titleStr); 00085 int h = QMAX( fontMetrics().height(), miniicon.height() ); 00086 setMinimumSize( w+16, h+8 ); 00087 } 00088 00089 void KPopupTitle::paintEvent(TQPaintEvent *) 00090 { 00091 TQRect r(rect()); 00092 TQPainter p(this); 00093 kapp->style().tqdrawPrimitive(TQStyle::PE_HeaderSectionMenu, &p, r, palette().active()); 00094 00095 if (!miniicon.isNull()) 00096 p.drawPixmap(4, (r.height()-miniicon.height())/2, miniicon); 00097 00098 if (!titleStr.isNull()) 00099 { 00100 p.setPen(palette().active().text()); 00101 TQFont f = p.font(); 00102 f.setBold(true); 00103 p.setFont(f); 00104 if(!miniicon.isNull()) 00105 { 00106 p.drawText(miniicon.width()+8, 0, width()-(miniicon.width()+8), 00107 height(), AlignLeft | AlignVCenter | SingleLine, 00108 titleStr); 00109 } 00110 else 00111 { 00112 p.drawText(0, 0, width(), height(), 00113 AlignCenter | SingleLine, titleStr); 00114 } 00115 } 00116 } 00117 00118 TQSize KPopupTitle::sizeHint() const 00119 { 00120 return minimumSize(); 00121 } 00122 00123 class KPopupMenu::KPopupMenuPrivate 00124 { 00125 public: 00126 KPopupMenuPrivate () 00127 : noMatches(false) 00128 , shortcuts(false) 00129 , autoExec(false) 00130 , lastHitIndex(-1) 00131 , state(Qt::NoButton) 00132 , m_ctxMenu(0) 00133 {} 00134 00135 ~KPopupMenuPrivate () 00136 { 00137 delete m_ctxMenu; 00138 } 00139 00140 TQString m_lastTitle; 00141 00142 // variables for keyboard navigation 00143 TQTimer clearTimer; 00144 00145 bool noMatches : 1; 00146 bool shortcuts : 1; 00147 bool autoExec : 1; 00148 00149 TQString keySeq; 00150 TQString originalText; 00151 00152 int lastHitIndex; 00153 TQt::ButtonState state; 00154 00155 // support for RMB menus on menus 00156 TQPopupMenu* m_ctxMenu; 00157 static bool s_continueCtxMenuShow; 00158 static int s_highlightedItem; 00159 static KPopupMenu* s_contextedMenu; 00160 }; 00161 00162 int KPopupMenu::KPopupMenuPrivate::s_highlightedItem(-1); 00163 KPopupMenu* KPopupMenu::KPopupMenuPrivate::s_contextedMenu(0); 00164 bool KPopupMenu::KPopupMenuPrivate::s_continueCtxMenuShow(true); 00165 00166 KPopupMenu::KPopupMenu(TQWidget *parent, const char *name) 00167 : TQPopupMenu(parent, name) 00168 { 00169 d = new KPopupMenuPrivate; 00170 resetKeyboardVars(); 00171 connect(&(d->clearTimer), TQT_SIGNAL(timeout()), TQT_SLOT(resetKeyboardVars())); 00172 } 00173 00174 KPopupMenu::~KPopupMenu() 00175 { 00176 if (KPopupMenuPrivate::s_contextedMenu == this) 00177 { 00178 KPopupMenuPrivate::s_contextedMenu = 0; 00179 KPopupMenuPrivate::s_highlightedItem = -1; 00180 } 00181 00182 delete d; 00183 } 00184 00185 int KPopupMenu::insertTitle(const TQString &text, int id, int index) 00186 { 00187 KPopupTitle *titleItem = new KPopupTitle(); 00188 titleItem->setTitle(text); 00189 int ret = insertItem(titleItem, id, index); 00190 setItemEnabled(ret, false); 00191 return ret; 00192 } 00193 00194 int KPopupMenu::insertTitle(const TQPixmap &icon, const TQString &text, int id, 00195 int index) 00196 { 00197 KPopupTitle *titleItem = new KPopupTitle(); 00198 titleItem->setTitle(text, &icon); 00199 int ret = insertItem(titleItem, id, index); 00200 setItemEnabled(ret, false); 00201 return ret; 00202 } 00203 00204 void KPopupMenu::changeTitle(int id, const TQString &text) 00205 { 00206 TQMenuItem *item = findItem(id); 00207 if(item){ 00208 if(item->widget()) 00209 ((KPopupTitle *)item->widget())->setTitle(text); 00210 #ifndef NDEBUG 00211 else 00212 kdWarning() << "KPopupMenu: changeTitle() called with non-title id "<< id << endl; 00213 #endif 00214 } 00215 #ifndef NDEBUG 00216 else 00217 kdWarning() << "KPopupMenu: changeTitle() called with invalid id " << id << endl; 00218 #endif 00219 } 00220 00221 void KPopupMenu::changeTitle(int id, const TQPixmap &icon, const TQString &text) 00222 { 00223 TQMenuItem *item = findItem(id); 00224 if(item){ 00225 if(item->widget()) 00226 ((KPopupTitle *)item->widget())->setTitle(text, &icon); 00227 #ifndef NDEBUG 00228 else 00229 kdWarning() << "KPopupMenu: changeTitle() called with non-title id "<< id << endl; 00230 #endif 00231 } 00232 #ifndef NDEBUG 00233 else 00234 kdWarning() << "KPopupMenu: changeTitle() called with invalid id " << id << endl; 00235 #endif 00236 } 00237 00238 TQString KPopupMenu::title(int id) const 00239 { 00240 if(id == -1) // obsolete 00241 return d->m_lastTitle; 00242 TQMenuItem *item = findItem(id); 00243 if(item){ 00244 if(item->widget()) 00245 return ((KPopupTitle *)item->widget())->title(); 00246 else 00247 qWarning("KPopupMenu: title() called with non-title id %d.", id); 00248 } 00249 else 00250 qWarning("KPopupMenu: title() called with invalid id %d.", id); 00251 return TQString::null; 00252 } 00253 00254 TQPixmap KPopupMenu::titlePixmap(int id) const 00255 { 00256 TQMenuItem *item = findItem(id); 00257 if(item){ 00258 if(item->widget()) 00259 return ((KPopupTitle *)item->widget())->icon(); 00260 else 00261 qWarning("KPopupMenu: titlePixmap() called with non-title id %d.", id); 00262 } 00263 else 00264 qWarning("KPopupMenu: titlePixmap() called with invalid id %d.", id); 00265 TQPixmap tmp; 00266 return tmp; 00267 } 00268 00272 void KPopupMenu::closeEvent(TQCloseEvent*e) 00273 { 00274 if (d->shortcuts) 00275 resetKeyboardVars(); 00276 TQPopupMenu::closeEvent(e); 00277 } 00278 00279 void KPopupMenu::activateItemAt(int index) 00280 { 00281 d->state = Qt::NoButton; 00282 TQPopupMenu::activateItemAt(index); 00283 } 00284 00285 TQt::ButtonState KPopupMenu::state() const 00286 { 00287 return d->state; 00288 } 00289 00290 void KPopupMenu::keyPressEvent(TQKeyEvent* e) 00291 { 00292 d->state = Qt::NoButton; 00293 if (!d->shortcuts) { 00294 // continue event processing by Qpopup 00295 //e->ignore(); 00296 d->state = e->state(); 00297 TQPopupMenu::keyPressEvent(e); 00298 return; 00299 } 00300 00301 int i = 0; 00302 bool firstpass = true; 00303 TQString keyString = e->text(); 00304 00305 // check for common commands dealt with by QPopup 00306 int key = e->key(); 00307 if (key == Key_Escape || key == Key_Return || key == Key_Enter 00308 || key == Key_Up || key == Key_Down || key == Key_Left 00309 || key == Key_Right || key == Key_F1) { 00310 00311 resetKeyboardVars(); 00312 // continue event processing by Qpopup 00313 //e->ignore(); 00314 d->state = e->state(); 00315 TQPopupMenu::keyPressEvent(e); 00316 return; 00317 } else if ( key == Key_Shift || key == Key_Control || key == Key_Alt || key == Key_Meta ) 00318 return TQPopupMenu::keyPressEvent(e); 00319 00320 // check to see if the user wants to remove a key from the sequence (backspace) 00321 // or clear the sequence (delete) 00322 if (!d->keySeq.isNull()) { 00323 00324 if (key == Key_Backspace) { 00325 00326 if (d->keySeq.length() == 1) { 00327 resetKeyboardVars(); 00328 return; 00329 } 00330 00331 // keep the last sequence in keyString 00332 keyString = d->keySeq.left(d->keySeq.length() - 1); 00333 00334 // allow sequence matching to be tried again 00335 resetKeyboardVars(); 00336 00337 } else if (key == Key_Delete) { 00338 resetKeyboardVars(); 00339 00340 // clear active item 00341 setActiveItem(0); 00342 return; 00343 00344 } else if (d->noMatches) { 00345 // clear if there are no matches 00346 resetKeyboardVars(); 00347 00348 // clear active item 00349 setActiveItem(0); 00350 00351 } else { 00352 // the key sequence is not a null string 00353 // therefore the lastHitIndex is valid 00354 i = d->lastHitIndex; 00355 } 00356 } else if (key == Key_Backspace && parentMenu) { 00357 // backspace with no chars in the buffer... go back a menu. 00358 hide(); 00359 resetKeyboardVars(); 00360 return; 00361 } 00362 00363 d->keySeq += keyString; 00364 int seqLen = d->keySeq.length(); 00365 00366 for (; i < (int)count(); i++) { 00367 // compare typed text with text of this entry 00368 int j = idAt(i); 00369 00370 // don't search disabled entries 00371 if (!isItemEnabled(j)) 00372 continue; 00373 00374 TQString thisText; 00375 00376 // retrieve the right text 00377 // (the last selected item one may have additional ampersands) 00378 if (i == d->lastHitIndex) 00379 thisText = d->originalText; 00380 else 00381 thisText = text(j); 00382 00383 // if there is an accelerator present, remove it 00384 if ((int)accel(j) != 0) 00385 thisText = thisText.replace("&", TQString()); 00386 00387 // chop text to the search length 00388 thisText = thisText.left(seqLen); 00389 00390 // do the search 00391 if (!thisText.find(d->keySeq, 0, false)) { 00392 00393 if (firstpass) { 00394 // match 00395 setActiveItem(i); 00396 00397 // check to see if we're underlining a different item 00398 if (d->lastHitIndex != i) 00399 // yes; revert the underlining 00400 changeItem(idAt(d->lastHitIndex), d->originalText); 00401 00402 // set the original text if it's a different item 00403 if (d->lastHitIndex != i || d->lastHitIndex == -1) 00404 d->originalText = text(j); 00405 00406 // underline the currently selected item 00407 changeItem(j, underlineText(d->originalText, d->keySeq.length())); 00408 00409 // remember what's going on 00410 d->lastHitIndex = i; 00411 00412 // start/restart the clear timer 00413 d->clearTimer.start(5000, true); 00414 00415 // go around for another try, to see if we can execute 00416 firstpass = false; 00417 } else { 00418 // don't allow execution 00419 return; 00420 } 00421 } 00422 00423 // fall through to allow execution 00424 } 00425 00426 if (!firstpass) { 00427 if (d->autoExec) { 00428 // activate anything 00429 activateItemAt(d->lastHitIndex); 00430 resetKeyboardVars(); 00431 00432 } else if (findItem(idAt(d->lastHitIndex)) && 00433 findItem(idAt(d->lastHitIndex))->popup()) { 00434 // only activate sub-menus 00435 activateItemAt(d->lastHitIndex); 00436 resetKeyboardVars(); 00437 } 00438 00439 return; 00440 } 00441 00442 // no matches whatsoever, clean up 00443 resetKeyboardVars(true); 00444 //e->ignore(); 00445 TQPopupMenu::keyPressEvent(e); 00446 } 00447 00448 bool KPopupMenu::focusNextPrevChild( bool next ) 00449 { 00450 resetKeyboardVars(); 00451 return TQPopupMenu::focusNextPrevChild( next ); 00452 } 00453 00454 TQString KPopupMenu::underlineText(const TQString& text, uint length) 00455 { 00456 TQString ret = text; 00457 for (uint i = 0; i < length; i++) { 00458 if (ret[2*i] != '&') 00459 ret.insert(2*i, "&"); 00460 } 00461 return ret; 00462 } 00463 00464 void KPopupMenu::resetKeyboardVars(bool noMatches /* = false */) 00465 { 00466 // Clean up keyboard variables 00467 if (d->lastHitIndex != -1) { 00468 changeItem(idAt(d->lastHitIndex), d->originalText); 00469 d->lastHitIndex = -1; 00470 } 00471 00472 if (!noMatches) { 00473 d->keySeq = TQString::null; 00474 } 00475 00476 d->noMatches = noMatches; 00477 } 00478 00479 void KPopupMenu::setKeyboardShortcutsEnabled(bool enable) 00480 { 00481 d->shortcuts = enable; 00482 } 00483 00484 void KPopupMenu::setKeyboardShortcutsExecute(bool enable) 00485 { 00486 d->autoExec = enable; 00487 } 00496 void KPopupMenu::mousePressEvent(TQMouseEvent* e) 00497 { 00498 if (d->m_ctxMenu && d->m_ctxMenu->isVisible()) 00499 { 00500 // hide on a second context menu event 00501 d->m_ctxMenu->hide(); 00502 } 00503 00504 TQPopupMenu::mousePressEvent(e); 00505 } 00506 00507 void KPopupMenu::mouseReleaseEvent(TQMouseEvent* e) 00508 { 00509 // Save the button, and the modifiers from state() 00510 d->state = TQt::ButtonState(e->button() | (e->state() & KeyButtonMask)); 00511 00512 if ( !d->m_ctxMenu || !d->m_ctxMenu->isVisible() ) 00513 TQPopupMenu::mouseReleaseEvent(e); 00514 } 00515 00516 TQPopupMenu* KPopupMenu::contextMenu() 00517 { 00518 if (!d->m_ctxMenu) 00519 { 00520 d->m_ctxMenu = new TQPopupMenu(this); 00521 connect(d->m_ctxMenu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(ctxMenuHiding())); 00522 } 00523 00524 return d->m_ctxMenu; 00525 } 00526 00527 const TQPopupMenu* KPopupMenu::contextMenu() const 00528 { 00529 return const_cast< KPopupMenu* >( this )->contextMenu(); 00530 } 00531 00532 void KPopupMenu::hideContextMenu() 00533 { 00534 KPopupMenuPrivate::s_continueCtxMenuShow = false; 00535 } 00536 00537 int KPopupMenu::contextMenuFocusItem() 00538 { 00539 return KPopupMenuPrivate::s_highlightedItem; 00540 } 00541 00542 KPopupMenu* KPopupMenu::contextMenuFocus() 00543 { 00544 return KPopupMenuPrivate::s_contextedMenu; 00545 } 00546 00547 void KPopupMenu::itemHighlighted(int /* whichItem */) 00548 { 00549 if (!d->m_ctxMenu || !d->m_ctxMenu->isVisible()) 00550 { 00551 return; 00552 } 00553 00554 d->m_ctxMenu->hide(); 00555 showCtxMenu(mapFromGlobal(TQCursor::pos())); 00556 } 00557 00558 void KPopupMenu::showCtxMenu(TQPoint pos) 00559 { 00560 TQMenuItem* item = findItem(KPopupMenuPrivate::s_highlightedItem); 00561 if (item) 00562 { 00563 TQPopupMenu* subMenu = item->popup(); 00564 if (subMenu) 00565 { 00566 disconnect(subMenu, TQT_SIGNAL(aboutToShow()), this, TQT_SLOT(ctxMenuHideShowingMenu())); 00567 } 00568 } 00569 00570 KPopupMenuPrivate::s_highlightedItem = idAt(pos); 00571 00572 if (KPopupMenuPrivate::s_highlightedItem == -1) 00573 { 00574 KPopupMenuPrivate::s_contextedMenu = 0; 00575 return; 00576 } 00577 00578 emit aboutToShowContextMenu(this, KPopupMenuPrivate::s_highlightedItem, d->m_ctxMenu); 00579 00580 TQPopupMenu* subMenu = findItem(KPopupMenuPrivate::s_highlightedItem)->popup(); 00581 if (subMenu) 00582 { 00583 connect(subMenu, TQT_SIGNAL(aboutToShow()), TQT_SLOT(ctxMenuHideShowingMenu())); 00584 TQTimer::singleShot(100, subMenu, TQT_SLOT(hide())); 00585 } 00586 00587 if (!KPopupMenuPrivate::s_continueCtxMenuShow) 00588 { 00589 KPopupMenuPrivate::s_continueCtxMenuShow = true; 00590 return; 00591 } 00592 00593 KPopupMenuPrivate::s_contextedMenu = this; 00594 d->m_ctxMenu->popup(this->mapToGlobal(pos)); 00595 connect(this, TQT_SIGNAL(highlighted(int)), this, TQT_SLOT(itemHighlighted(int))); 00596 } 00597 00598 /* 00599 * this method helps prevent submenus popping up while we have a context menu 00600 * showing 00601 */ 00602 void KPopupMenu::ctxMenuHideShowingMenu() 00603 { 00604 TQMenuItem* item = findItem(KPopupMenuPrivate::s_highlightedItem); 00605 if (item) 00606 { 00607 TQPopupMenu* subMenu = item->popup(); 00608 if (subMenu) 00609 { 00610 TQTimer::singleShot(0, subMenu, TQT_SLOT(hide())); 00611 } 00612 } 00613 } 00614 00615 void KPopupMenu::ctxMenuHiding() 00616 { 00617 if (KPopupMenuPrivate::s_highlightedItem) 00618 { 00619 TQPopupMenu* subMenu = findItem(KPopupMenuPrivate::s_highlightedItem)->popup(); 00620 if (subMenu) 00621 { 00622 disconnect(subMenu, TQT_SIGNAL(aboutToShow()), this, TQT_SLOT(ctxMenuHideShowingMenu())); 00623 } 00624 } 00625 00626 disconnect(this, TQT_SIGNAL(highlighted(int)), this, TQT_SLOT(itemHighlighted(int))); 00627 KPopupMenuPrivate::s_continueCtxMenuShow = true; 00628 } 00629 00630 void KPopupMenu::contextMenuEvent(TQContextMenuEvent* e) 00631 { 00632 if (d->m_ctxMenu) 00633 { 00634 if (e->reason() == TQContextMenuEvent::Mouse) 00635 { 00636 showCtxMenu(e->pos()); 00637 } 00638 else if (actItem != -1) 00639 { 00640 showCtxMenu(itemGeometry(actItem).center()); 00641 } 00642 00643 e->accept(); 00644 return; 00645 } 00646 00647 TQPopupMenu::contextMenuEvent(e); 00648 } 00649 00650 void KPopupMenu::hideEvent(TQHideEvent*) 00651 { 00652 if (d->m_ctxMenu && d->m_ctxMenu->isVisible()) 00653 { 00654 // we need to block signals here when the ctxMenu is showing 00655 // to prevent the TQPopupMenu::activated(int) signal from emitting 00656 // when hiding with a context menu, the user doesn't expect the 00657 // menu to actually do anything. 00658 // since hideEvent gets called very late in the process of hiding 00659 // (deep within TQWidget::hide) the activated(int) signal is the 00660 // last signal to be emitted, even after things like aboutToHide() 00661 // AJS 00662 blockSignals(true); 00663 d->m_ctxMenu->hide(); 00664 blockSignals(false); 00665 } 00666 } 00671 // Obsolete 00672 KPopupMenu::KPopupMenu(const TQString& title, TQWidget *parent, const char *name) 00673 : TQPopupMenu(parent, name) 00674 { 00675 d = new KPopupMenuPrivate; 00676 insertTitle(title); 00677 } 00678 00679 // Obsolete 00680 void KPopupMenu::setTitle(const TQString &title) 00681 { 00682 KPopupTitle *titleItem = new KPopupTitle(); 00683 titleItem->setTitle(title); 00684 insertItem(titleItem); 00685 d->m_lastTitle = title; 00686 } 00687 00688 void KPopupTitle::virtual_hook( int, void* ) 00689 { /*BASE::virtual_hook( id, data );*/ } 00690 00691 void KPopupMenu::virtual_hook( int, void* ) 00692 { /*BASE::virtual_hook( id, data );*/ } 00693 00694 #include "kpopupmenu.moc"