KDE3Support
k3popupmenu.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 00020 #include "k3popupmenu.h" 00021 00022 #include <QtGui/QCursor> 00023 #include <QtGui/QPainter> 00024 #include <QtCore/QTimer> 00025 #include <QtGui/QFontMetrics> 00026 #include <QKeyEvent> 00027 #include <QPointer> 00028 #include <QMenuItem> 00029 00030 #include <kdebug.h> 00031 #include <kglobal.h> 00032 #include <klocale.h> 00033 00034 class K3PopupMenu::K3PopupMenuPrivate 00035 { 00036 public: 00037 K3PopupMenuPrivate () 00038 : noMatches(false) 00039 , shortcuts(false) 00040 , autoExec(false) 00041 , lastHitAction(0L) 00042 #ifdef QT3_SUPPORT 00043 , state(Qt::NoButton) 00044 #endif 00045 , mouseButtons(Qt::NoButton) 00046 , keyboardModifiers(Qt::NoModifier) 00047 , m_ctxMenu(0) 00048 {} 00049 00050 ~K3PopupMenuPrivate () 00051 { 00052 delete m_ctxMenu; 00053 } 00054 00055 QString m_lastTitle; 00056 00057 // variables for keyboard navigation 00058 QTimer clearTimer; 00059 00060 bool noMatches : 1; 00061 bool shortcuts : 1; 00062 bool autoExec : 1; 00063 00064 QString keySeq; 00065 QString originalText; 00066 00067 QAction* lastHitAction; 00068 #ifdef QT3_SUPPORT 00069 Qt::ButtonState state; 00070 Qt::MouseButtons mouseButtons; 00071 Qt::KeyboardModifiers keyboardModifiers; 00072 #endif 00073 00074 // support for RMB menus on menus 00075 Q3PopupMenu* m_ctxMenu; 00076 static bool s_continueCtxMenuShow; 00077 static QPointer<QAction> s_highlightedAction; 00078 // KDE4: deprecated 00079 static int s_highlightedItem; 00080 static K3PopupMenu* s_contextedMenu; 00081 }; 00082 00083 QPointer<QAction> K3PopupMenu::K3PopupMenuPrivate::s_highlightedAction(0L); 00084 int K3PopupMenu::K3PopupMenuPrivate::s_highlightedItem(-1); 00085 K3PopupMenu* K3PopupMenu::K3PopupMenuPrivate::s_contextedMenu(0); 00086 bool K3PopupMenu::K3PopupMenuPrivate::s_continueCtxMenuShow(true); 00087 00088 K3PopupMenu::K3PopupMenu(QWidget *parent) 00089 : Q3PopupMenu(parent) 00090 , d(new K3PopupMenuPrivate()) 00091 { 00092 resetKeyboardVars(); 00093 connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars())); 00094 } 00095 00096 K3PopupMenu::~K3PopupMenu() 00097 { 00098 if (K3PopupMenuPrivate::s_contextedMenu == this) 00099 { 00100 K3PopupMenuPrivate::s_contextedMenu = 0; 00101 K3PopupMenuPrivate::s_highlightedAction = 0L; 00102 K3PopupMenuPrivate::s_highlightedItem = -1; 00103 } 00104 00105 delete d; 00106 } 00107 00108 QAction* K3PopupMenu::addTitle(const QString &text, QAction* before) 00109 { 00110 QAction* action = new QAction(text, this); 00111 action->setEnabled(false); 00112 QFont f = action->font(); 00113 f.setBold(true); 00114 action->setFont(f); 00115 insertAction(before, action); 00116 return action; 00117 } 00118 00119 QAction* K3PopupMenu::addTitle(const QIcon &icon, const QString &text, QAction* before) 00120 { 00121 QAction* action = new QAction(icon, text, this); 00122 action->setEnabled(false); 00123 QFont f = action->font(); 00124 f.setBold(true); 00125 action->setFont(f); 00126 insertAction(before, action); 00127 return action; 00128 } 00129 00133 void K3PopupMenu::closeEvent(QCloseEvent*e) 00134 { 00135 if (d->shortcuts) 00136 resetKeyboardVars(); 00137 Q3PopupMenu::closeEvent(e); 00138 } 00139 00140 void K3PopupMenu::activateItemAt(int index) 00141 { 00142 #ifdef QT3_SUPPORT 00143 d->state = Qt::NoButton; 00144 #endif 00145 d->mouseButtons = Qt::NoButton; 00146 d->keyboardModifiers = Qt::NoModifier; 00147 Q3PopupMenu::activateItemAt(index); 00148 } 00149 00150 #ifdef QT3_SUPPORT 00151 Qt::ButtonState K3PopupMenu::state() const 00152 { 00153 return d->state; 00154 } 00155 #endif 00156 00157 Qt::MouseButtons K3PopupMenu::mouseButtons() const 00158 { 00159 return d->mouseButtons; 00160 } 00161 00162 Qt::KeyboardModifiers K3PopupMenu::keyboardModifiers() const 00163 { 00164 return d->keyboardModifiers; 00165 } 00166 00167 void K3PopupMenu::keyPressEvent(QKeyEvent* e) 00168 { 00169 #ifdef QT3_SUPPORT 00170 d->state = Qt::NoButton; 00171 #endif 00172 d->mouseButtons = Qt::NoButton; 00173 d->keyboardModifiers = Qt::NoModifier; 00174 if (!d->shortcuts) { 00175 // continue event processing by Qpopup 00176 //e->ignore(); 00177 #ifdef QT3_SUPPORT 00178 d->state = e->state(); 00179 #endif 00180 d->keyboardModifiers = e->modifiers(); 00181 Q3PopupMenu::keyPressEvent(e); 00182 return; 00183 } 00184 00185 QAction* a = 0L; 00186 bool firstpass = true; 00187 QString keyString = e->text(); 00188 00189 // check for common commands dealt with by QPopup 00190 int key = e->key(); 00191 if (key == Qt::Key_Escape || key == Qt::Key_Return || key == Qt::Key_Enter 00192 || key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_Left 00193 || key == Qt::Key_Right || key == Qt::Key_F1) { 00194 00195 resetKeyboardVars(); 00196 // continue event processing by Qpopup 00197 //e->ignore(); 00198 #ifdef QT3_SUPPORT 00199 d->state = e->state(); 00200 #endif 00201 d->keyboardModifiers = e->modifiers(); 00202 Q3PopupMenu::keyPressEvent(e); 00203 return; 00204 } else if ( key == Qt::Key_Shift || key == Qt::Key_Control || key == Qt::Key_Alt || key == Qt::Key_Meta ) 00205 return Q3PopupMenu::keyPressEvent(e); 00206 00207 // check to see if the user wants to remove a key from the sequence (backspace) 00208 // or clear the sequence (delete) 00209 if (!d->keySeq.isNull()) { 00210 if (key == Qt::Key_Backspace) { 00211 00212 if (d->keySeq.length() == 1) { 00213 resetKeyboardVars(); 00214 return; 00215 } 00216 00217 // keep the last sequence in keyString 00218 keyString = d->keySeq.left(d->keySeq.length() - 1); 00219 00220 // allow sequence matching to be tried again 00221 resetKeyboardVars(); 00222 00223 } else if (key == Qt::Key_Delete) { 00224 resetKeyboardVars(); 00225 00226 // clear active item 00227 setActiveAction(0L); 00228 return; 00229 00230 } else if (d->noMatches) { 00231 // clear if there are no matches 00232 resetKeyboardVars(); 00233 00234 // clear active item 00235 setActiveAction(0L); 00236 00237 } else { 00238 // the key sequence is not a null string 00239 // therefore the lastHitAction is valid 00240 a = d->lastHitAction; 00241 } 00242 00243 } else if (key == Qt::Key_Backspace && menuAction()) { 00244 // backspace with no chars in the buffer... go back a menu. 00245 hide(); 00246 resetKeyboardVars(); 00247 return; 00248 } 00249 00250 d->keySeq += keyString; 00251 int seqLen = d->keySeq.length(); 00252 00253 foreach (a, actions()) { 00254 // don't search disabled entries 00255 if (!a->isEnabled()) 00256 continue; 00257 00258 QString thisText; 00259 00260 // retrieve the right text 00261 // (the last selected item one may have additional ampersands) 00262 if (a == d->lastHitAction) 00263 thisText = d->originalText; 00264 else 00265 thisText = a->text(); 00266 00267 // if there is an accelerator present, remove it 00268 thisText = KGlobal::locale()->removeAcceleratorMarker(thisText); 00269 00270 // chop text to the search length 00271 thisText = thisText.left(seqLen); 00272 00273 // do the search 00274 if (!thisText.contains(d->keySeq, Qt::CaseInsensitive)) { 00275 00276 if (firstpass) { 00277 // match 00278 setActiveAction(a); 00279 00280 // check to see if we're underlining a different item 00281 if (d->lastHitAction != a) 00282 // yes; revert the underlining 00283 d->lastHitAction->setText(d->originalText); 00284 00285 // set the original text if it's a different item 00286 if (d->lastHitAction != a || d->lastHitAction == 0L) 00287 d->originalText = a->text(); 00288 00289 // underline the currently selected item 00290 a->setText(underlineText(d->originalText, d->keySeq.length())); 00291 00292 // remember what's going on 00293 d->lastHitAction = a; 00294 00295 // start/restart the clear timer 00296 d->clearTimer.start(5000, true); 00297 00298 // go around for another try, to see if we can execute 00299 firstpass = false; 00300 } else { 00301 // don't allow execution 00302 return; 00303 } 00304 } 00305 00306 // fall through to allow execution 00307 } 00308 00309 if (!firstpass) { 00310 if (d->autoExec) { 00311 // activate anything 00312 d->lastHitAction->activate(QAction::Trigger); 00313 resetKeyboardVars(); 00314 00315 } else if (d->lastHitAction && d->lastHitAction->menu()) { 00316 // only activate sub-menus 00317 d->lastHitAction->activate(QAction::Trigger); 00318 resetKeyboardVars(); 00319 } 00320 00321 return; 00322 } 00323 00324 // no matches whatsoever, clean up 00325 resetKeyboardVars(true); 00326 //e->ignore(); 00327 Q3PopupMenu::keyPressEvent(e); 00328 } 00329 00330 bool K3PopupMenu::focusNextPrevChild( bool next ) 00331 { 00332 resetKeyboardVars(); 00333 return Q3PopupMenu::focusNextPrevChild( next ); 00334 } 00335 00336 QString K3PopupMenu::underlineText(const QString& text, uint length) 00337 { 00338 QString ret = text; 00339 for (uint i = 0; i < length; i++) { 00340 if (ret[2*i] != '&') 00341 ret.insert(2*i, "&"); 00342 } 00343 return ret; 00344 } 00345 00346 void K3PopupMenu::resetKeyboardVars(bool noMatches /* = false */) 00347 { 00348 // Clean up keyboard variables 00349 if (d->lastHitAction) { 00350 d->lastHitAction->setText(d->originalText); 00351 d->lastHitAction = 0L; 00352 } 00353 00354 if (!noMatches) { 00355 d->keySeq.clear(); 00356 } 00357 00358 d->noMatches = noMatches; 00359 } 00360 00361 void K3PopupMenu::setKeyboardShortcutsEnabled(bool enable) 00362 { 00363 d->shortcuts = enable; 00364 } 00365 00366 void K3PopupMenu::setKeyboardShortcutsExecute(bool enable) 00367 { 00368 d->autoExec = enable; 00369 } 00378 void K3PopupMenu::mousePressEvent(QMouseEvent* e) 00379 { 00380 if (d->m_ctxMenu && d->m_ctxMenu->isVisible()) 00381 { 00382 // hide on a second context menu event 00383 d->m_ctxMenu->hide(); 00384 } 00385 00386 Q3PopupMenu::mousePressEvent(e); 00387 } 00388 00389 void K3PopupMenu::mouseReleaseEvent(QMouseEvent* e) 00390 { 00391 #ifdef QT3_SUPPORT 00392 // Save the button, and the modifiers from state() 00393 d->state = Qt::ButtonState(uint(e->button()) | (e->state() & Qt::KeyboardModifierMask)); 00394 #endif 00395 // Save the button, and the modifiers 00396 d->keyboardModifiers = e->modifiers(); 00397 d->mouseButtons = e->buttons(); 00398 00399 if ( !d->m_ctxMenu || !d->m_ctxMenu->isVisible() ) 00400 Q3PopupMenu::mouseReleaseEvent(e); 00401 } 00402 00403 Q3PopupMenu* K3PopupMenu::contextMenu() 00404 { 00405 if (!d->m_ctxMenu) 00406 { 00407 d->m_ctxMenu = new Q3PopupMenu(this); 00408 connect(d->m_ctxMenu, SIGNAL(aboutToHide()), this, SLOT(ctxMenuHiding())); 00409 } 00410 00411 return d->m_ctxMenu; 00412 } 00413 00414 const Q3PopupMenu* K3PopupMenu::contextMenu() const 00415 { 00416 return const_cast< K3PopupMenu* >( this )->contextMenu(); 00417 } 00418 00419 void K3PopupMenu::hideContextMenu() 00420 { 00421 K3PopupMenuPrivate::s_continueCtxMenuShow = false; 00422 } 00423 00424 QAction* K3PopupMenu::contextMenuFocusAction() 00425 { 00426 return K3PopupMenuPrivate::s_highlightedAction; 00427 } 00428 00429 K3PopupMenu* K3PopupMenu::contextMenuFocus() 00430 { 00431 return K3PopupMenuPrivate::s_contextedMenu; 00432 } 00433 00434 void K3PopupMenu::actionHovered(QAction* action) 00435 { 00436 if (!d->m_ctxMenu || !d->m_ctxMenu->isVisible()) 00437 { 00438 return; 00439 } 00440 00441 d->m_ctxMenu->hide(); 00442 showCtxMenu(actionGeometry(action).center()); 00443 } 00444 00445 void K3PopupMenu::showCtxMenu(const QPoint &pos) 00446 { 00447 if (K3PopupMenuPrivate::s_highlightedAction) 00448 if (QMenu* subMenu = K3PopupMenuPrivate::s_highlightedAction->menu()) 00449 disconnect(subMenu, SIGNAL(aboutToShow()), this, SLOT(ctxMenuHideShowingMenu())); 00450 00451 K3PopupMenuPrivate::s_highlightedAction = activeAction(); 00452 K3PopupMenuPrivate::s_highlightedItem = itemAtPos(pos); 00453 00454 if (!K3PopupMenuPrivate::s_highlightedAction) 00455 { 00456 K3PopupMenuPrivate::s_contextedMenu = 0; 00457 return; 00458 } 00459 00460 emit aboutToShowContextMenu(this, K3PopupMenuPrivate::s_highlightedAction, d->m_ctxMenu); 00461 emit aboutToShowContextMenu(this, K3PopupMenuPrivate::s_highlightedItem, d->m_ctxMenu); 00462 00463 if (QMenu* subMenu = K3PopupMenuPrivate::s_highlightedAction->menu()) 00464 { 00465 connect(subMenu, SIGNAL(aboutToShow()), SLOT(ctxMenuHideShowingMenu())); 00466 QTimer::singleShot(100, subMenu, SLOT(hide())); 00467 } 00468 00469 if (!K3PopupMenuPrivate::s_continueCtxMenuShow) 00470 { 00471 K3PopupMenuPrivate::s_continueCtxMenuShow = true; 00472 return; 00473 } 00474 00475 K3PopupMenuPrivate::s_contextedMenu = this; 00476 d->m_ctxMenu->exec(this->mapToGlobal(pos)); 00477 connect(this, SIGNAL(hovered(QAction*)), SLOT(actionHovered(QAction*))); 00478 } 00479 00480 /* 00481 * this method helps prevent submenus popping up while we have a context menu 00482 * showing 00483 */ 00484 void K3PopupMenu::ctxMenuHideShowingMenu() 00485 { 00486 if (K3PopupMenuPrivate::s_highlightedAction) 00487 if (QMenu* subMenu = K3PopupMenuPrivate::s_highlightedAction->menu()) 00488 QTimer::singleShot(0, subMenu, SLOT(hide())); 00489 } 00490 00491 void K3PopupMenu::ctxMenuHiding() 00492 { 00493 if (K3PopupMenuPrivate::s_highlightedAction) 00494 if (QMenu* subMenu = K3PopupMenuPrivate::s_highlightedAction->menu()) 00495 disconnect(subMenu, SIGNAL(aboutToShow()), this, SLOT(ctxMenuHideShowingMenu())); 00496 00497 connect(this, SIGNAL(hovered(QAction*)), this, SLOT(actionHovered(QAction*))); 00498 K3PopupMenuPrivate::s_continueCtxMenuShow = true; 00499 } 00500 00501 void K3PopupMenu::contextMenuEvent(QContextMenuEvent* e) 00502 { 00503 if (d->m_ctxMenu) 00504 { 00505 if (e->reason() == QContextMenuEvent::Mouse) 00506 { 00507 showCtxMenu(e->pos()); 00508 } 00509 else if (activeAction()) 00510 { 00511 showCtxMenu(actionGeometry(activeAction()).center()); 00512 } 00513 00514 e->accept(); 00515 return; 00516 } 00517 00518 Q3PopupMenu::contextMenuEvent(e); 00519 } 00520 00521 void K3PopupMenu::hideEvent(QHideEvent *e) 00522 { 00523 if (d->m_ctxMenu && d->m_ctxMenu->isVisible()) 00524 { 00525 // we need to block signals here when the ctxMenu is showing 00526 // to prevent the QPopupMenu::activated(int) signal from emitting 00527 // when hiding with a context menu, the user doesn't expect the 00528 // menu to actually do anything. 00529 // since hideEvent gets called very late in the process of hiding 00530 // (deep within QWidget::hide) the activated(int) signal is the 00531 // last signal to be emitted, even after things like aboutToHide() 00532 // AJS 00533 bool blocked = blockSignals(true); 00534 d->m_ctxMenu->hide(); 00535 blockSignals(blocked); 00536 } 00537 Q3PopupMenu::hideEvent(e); 00538 } 00543 void K3PopupMenu::virtual_hook( int, void* ) 00544 { /*BASE::virtual_hook( id, data );*/ } 00545 00546 00547 K3PopupMenu::K3PopupMenu(const QString &title, QWidget *parent) 00548 : Q3PopupMenu(parent) 00549 , d(new K3PopupMenuPrivate()) 00550 { 00551 resetKeyboardVars(); 00552 connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars())); 00553 addAction(title); 00554 } 00555 00556 #ifdef QT3_SUPPORT 00557 int K3PopupMenu::insertTitle(const QString &text, int id, int index) 00558 { 00559 int newid = insertItem(text, id, index); 00560 QMenuItem* menuItem = findItem(newid); 00561 Q_ASSERT(menuItem); 00562 menuItem->setEnabled(false); 00563 QFont f = menuItem->font(); 00564 f.setBold(true); 00565 menuItem->setFont(f); 00566 return newid; 00567 } 00568 00569 int K3PopupMenu::insertTitle(const QPixmap &icon, const QString &text, int id, int index) 00570 { 00571 int newid = insertItem(text, id, index); 00572 QMenuItem* menuItem = findItem(newid); 00573 Q_ASSERT(menuItem); 00574 menuItem->setEnabled(false); 00575 menuItem->setIcon(icon); 00576 QFont f = menuItem->font(); 00577 f.setBold(true); 00578 menuItem->setFont(f); 00579 return newid; 00580 } 00581 00582 void K3PopupMenu::changeTitle(int id, const QString &text) 00583 { 00584 QMenuItem* menuItem = findItem(id); 00585 Q_ASSERT(menuItem); 00586 if (!menuItem) 00587 return; 00588 menuItem->setText(text); 00589 menuItem->setIcon(QIcon()); 00590 return; 00591 } 00592 00593 void K3PopupMenu::changeTitle(int id, const QPixmap &icon, const QString &text) 00594 { 00595 QMenuItem* menuItem = findItem(id); 00596 Q_ASSERT(menuItem); 00597 if (!menuItem) 00598 return; 00599 menuItem->setText(text); 00600 menuItem->setIcon(icon); 00601 return; 00602 } 00603 00604 QString K3PopupMenu::title(int id) const 00605 { 00606 QMenuItem* menuItem = findItem(id); 00607 Q_ASSERT(menuItem); 00608 if (!menuItem) 00609 return QString(); 00610 return menuItem->text(); 00611 } 00612 00613 QPixmap K3PopupMenu::titlePixmap(int id) const 00614 { 00615 QMenuItem* menuItem = findItem(id); 00616 Q_ASSERT(menuItem); 00617 if (!menuItem) 00618 return QPixmap(); 00619 return menuItem->icon().pixmap(); 00620 } 00621 00622 void K3PopupMenu::setTitle(const QString &title) 00623 { 00624 addAction(title); 00625 } 00626 00627 int K3PopupMenu::contextMenuFocusItem() 00628 { 00629 return K3PopupMenuPrivate::s_highlightedItem; 00630 } 00631 00632 #endif // END compat methods 00633 00634 #include "k3popupmenu.moc"
KDE 4.6 API Reference