KDEUI
kmenu.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,2006 Hamish Rodda <rodda@kde.org> 00004 Copyright (C) 2006 Olivier Goffart <ogoffart@kde.org> 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Library General Public 00008 License version 2 as published by the Free Software Foundation. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "kmenu.h" 00022 #include "khbox.h" 00023 00024 #include <QtCore/QMetaMethod> 00025 #include <QtCore/QObject> 00026 #include <QtCore/QPointer> 00027 #include <QtCore/QTimer> 00028 #include <QtGui/QApplication> 00029 #include <QtGui/QCursor> 00030 #include <QtGui/QFontMetrics> 00031 #include <QtGui/QHBoxLayout> 00032 #include <QtGui/QKeyEvent> 00033 #include <QtGui/QMenuItem> 00034 #include <QtGui/QLabel> 00035 #include <QtGui/QPainter> 00036 #include <QtGui/QStyle> 00037 #include <QtGui/QToolButton> 00038 #include <QtGui/QWidgetAction> 00039 00040 #include <kdebug.h> 00041 #include <kglobal.h> 00042 #include <klocale.h> 00043 #include <kacceleratormanager.h> 00044 00045 static const char KMENU_TITLE[] = "kmenu_title"; 00046 00047 class KMenu::KMenuPrivate 00048 : public QObject 00049 { 00050 public: 00051 KMenuPrivate (KMenu *_parent); 00052 ~KMenuPrivate (); 00053 00054 void resetKeyboardVars(bool noMatches = false); 00055 void actionHovered(QAction* action); 00056 void showCtxMenu(const QPoint &pos); 00057 void skipTitles(QKeyEvent *event); 00058 00069 bool eventFilter(QObject *object, QEvent *event) 00070 { 00071 Q_UNUSED(object); 00072 00073 if (event->type() == QEvent::Paint || 00074 event->type() == QEvent::KeyPress || 00075 event->type() == QEvent::KeyRelease) { 00076 return false; 00077 } 00078 00079 event->accept(); 00080 return true; 00081 } 00082 00083 KMenu *parent; 00084 00085 // variables for keyboard navigation 00086 QTimer clearTimer; 00087 00088 bool noMatches : 1; 00089 bool shortcuts : 1; 00090 bool autoExec : 1; 00091 00092 QString keySeq; 00093 QString originalText; 00094 00095 QAction* lastHitAction; 00096 QAction* lastHoveredAction; 00097 Qt::MouseButtons mouseButtons; 00098 Qt::KeyboardModifiers keyboardModifiers; 00099 00100 // support for RMB menus on menus 00101 QMenu* ctxMenu; 00102 QPointer<QAction> highlightedAction; 00103 00104 }; 00105 00106 KMenu::KMenuPrivate::KMenuPrivate (KMenu *_parent) 00107 : parent(_parent) 00108 , noMatches(false) 00109 , shortcuts(false) 00110 , autoExec(false) 00111 , lastHitAction(0L) 00112 , lastHoveredAction(0L) 00113 , mouseButtons(Qt::NoButton) 00114 , keyboardModifiers(Qt::NoModifier) 00115 , ctxMenu(0) 00116 , highlightedAction(0) 00117 { 00118 resetKeyboardVars(); 00119 KAcceleratorManager::manage(parent); 00120 } 00121 00122 KMenu::KMenuPrivate::~KMenuPrivate () 00123 { 00124 delete ctxMenu; 00125 } 00126 00127 00132 class KMenuContext { 00133 public: 00134 KMenuContext(); 00135 KMenuContext(const KMenuContext& o); 00136 KMenuContext(QPointer<KMenu> menu,QPointer<QAction> action); 00137 00138 inline QPointer<KMenu> menu() const { return m_menu; } 00139 inline QPointer<QAction> action() const { return m_action; } 00140 00141 private: 00142 QPointer<KMenu> m_menu; 00143 QPointer<QAction> m_action; 00144 }; 00145 00146 00147 Q_DECLARE_METATYPE(KMenuContext) 00148 00149 00150 00151 KMenu::KMenu(QWidget *parent) 00152 : QMenu(parent) 00153 , d(new KMenuPrivate(this)) 00154 { 00155 connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars())); 00156 } 00157 00158 KMenu::KMenu( const QString & title, QWidget * parent ) 00159 : QMenu(title, parent) 00160 , d(new KMenuPrivate(this)) 00161 { 00162 connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars())); 00163 } 00164 00165 KMenu::~KMenu() 00166 { 00167 delete d; 00168 } 00169 00170 QAction* KMenu::addTitle(const QString &text, QAction* before) 00171 { 00172 return addTitle(QIcon(), text, before); 00173 } 00174 00175 QAction* KMenu::addTitle(const QIcon &icon, const QString &text, QAction* before) 00176 { 00177 QAction *buttonAction = new QAction(this); 00178 QFont font = buttonAction->font(); 00179 font.setBold(true); 00180 buttonAction->setFont(font); 00181 buttonAction->setText(text); 00182 buttonAction->setIcon(icon); 00183 00184 QWidgetAction *action = new QWidgetAction(this); 00185 action->setObjectName(KMENU_TITLE); 00186 QToolButton *titleButton = new QToolButton(this); 00187 titleButton->installEventFilter(d); // prevent clicks on the title of the menu 00188 titleButton->setDefaultAction(buttonAction); 00189 titleButton->setDown(true); // prevent hover style changes in some styles 00190 titleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 00191 action->setDefaultWidget(titleButton); 00192 00193 insertAction(before, action); 00194 return action; 00195 } 00196 00200 void KMenu::closeEvent(QCloseEvent*e) 00201 { 00202 if (d->shortcuts) 00203 d->resetKeyboardVars(); 00204 QMenu::closeEvent(e); 00205 } 00206 00207 Qt::MouseButtons KMenu::mouseButtons() const 00208 { 00209 return d->mouseButtons; 00210 } 00211 00212 Qt::KeyboardModifiers KMenu::keyboardModifiers() const 00213 { 00214 return d->keyboardModifiers; 00215 } 00216 00217 void KMenu::keyPressEvent(QKeyEvent* e) 00218 { 00219 d->mouseButtons = Qt::NoButton; 00220 d->keyboardModifiers = Qt::NoModifier; 00221 00222 if (!d->shortcuts) { 00223 d->keyboardModifiers = e->modifiers(); 00224 QMenu::keyPressEvent(e); 00225 00226 if (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down) { 00227 d->skipTitles(e); 00228 } 00229 00230 return; 00231 } 00232 00233 QAction* a = 0L; 00234 bool firstpass = true; 00235 QString keyString = e->text(); 00236 00237 // check for common commands dealt with by QMenu 00238 int key = e->key(); 00239 if (key == Qt::Key_Escape || key == Qt::Key_Return || key == Qt::Key_Enter 00240 || key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_Left 00241 || key == Qt::Key_Right || key == Qt::Key_F1 || key == Qt::Key_PageUp 00242 || key == Qt::Key_PageDown || key == Qt::Key_Back || key == Qt::Key_Select) { 00243 00244 d->resetKeyboardVars(); 00245 // continue event processing by QMenu 00246 //e->ignore(); 00247 d->keyboardModifiers = e->modifiers(); 00248 QMenu::keyPressEvent(e); 00249 00250 if (key == Qt::Key_Up || key == Qt::Key_Down) { 00251 d->skipTitles(e); 00252 } 00253 return; 00254 } else if ( key == Qt::Key_Shift || key == Qt::Key_Control || key == Qt::Key_Alt || key == Qt::Key_Meta ) 00255 return QMenu::keyPressEvent(e); 00256 00257 // check to see if the user wants to remove a key from the sequence (backspace) 00258 // or clear the sequence (delete) 00259 if (!d->keySeq.isNull()) { 00260 if (key == Qt::Key_Backspace) { 00261 00262 if (d->keySeq.length() == 1) { 00263 d->resetKeyboardVars(); 00264 return; 00265 } 00266 00267 // keep the last sequence in keyString 00268 keyString = d->keySeq.left(d->keySeq.length() - 1); 00269 00270 // allow sequence matching to be tried again 00271 d->resetKeyboardVars(); 00272 00273 } else if (key == Qt::Key_Delete) { 00274 d->resetKeyboardVars(); 00275 00276 // clear active item 00277 setActiveAction(0L); 00278 return; 00279 00280 } else if (d->noMatches) { 00281 // clear if there are no matches 00282 d->resetKeyboardVars(); 00283 00284 // clear active item 00285 setActiveAction(0L); 00286 00287 } else { 00288 // the key sequence is not a null string 00289 // therefore the lastHitAction is valid 00290 a = d->lastHitAction; 00291 } 00292 00293 } else if (key == Qt::Key_Backspace && menuAction()) { 00294 // backspace with no chars in the buffer... go back a menu. 00295 hide(); 00296 d->resetKeyboardVars(); 00297 return; 00298 } 00299 00300 d->keySeq += keyString; 00301 const int seqLen = d->keySeq.length(); 00302 00303 foreach (a, actions()) { 00304 // don't search disabled entries 00305 if (!a->isEnabled()) 00306 continue; 00307 00308 QString thisText; 00309 00310 // retrieve the right text 00311 // (the last selected item one may have additional ampersands) 00312 if (a == d->lastHitAction) 00313 thisText = d->originalText; 00314 else 00315 thisText = a->text(); 00316 00317 // if there is an accelerator present, remove it 00318 thisText = KGlobal::locale()->removeAcceleratorMarker(thisText); 00319 00320 // chop text to the search length 00321 thisText = thisText.left(seqLen); 00322 00323 // do the search 00324 if (!thisText.indexOf(d->keySeq, 0, Qt::CaseInsensitive)) { 00325 00326 if (firstpass) { 00327 // match 00328 setActiveAction(a); 00329 00330 // check to see if we're underlining a different item 00331 if (d->lastHitAction && d->lastHitAction != a) 00332 // yes; revert the underlining 00333 d->lastHitAction->setText(d->originalText); 00334 00335 // set the original text if it's a different item 00336 if (d->lastHitAction != a || d->lastHitAction == 0L) 00337 d->originalText = a->text(); 00338 00339 // underline the currently selected item 00340 a->setText(underlineText(d->originalText, d->keySeq.length())); 00341 00342 // remember what's going on 00343 d->lastHitAction = a; 00344 00345 // start/restart the clear timer 00346 d->clearTimer.setSingleShot(true); 00347 d->clearTimer.start(5000); 00348 00349 // go around for another try, to see if we can execute 00350 firstpass = false; 00351 } else { 00352 // don't allow execution 00353 return; 00354 } 00355 } 00356 00357 // fall through to allow execution 00358 } 00359 00360 if (!firstpass) { 00361 if (d->autoExec) { 00362 // activate anything 00363 d->lastHitAction->activate(QAction::Trigger); 00364 d->resetKeyboardVars(); 00365 00366 } else if (d->lastHitAction && d->lastHitAction->menu()) { 00367 // only activate sub-menus 00368 d->lastHitAction->activate(QAction::Trigger); 00369 d->resetKeyboardVars(); 00370 } 00371 00372 return; 00373 } 00374 00375 // no matches whatsoever, clean up 00376 d->resetKeyboardVars(true); 00377 //e->ignore(); 00378 QMenu::keyPressEvent(e); 00379 } 00380 00381 bool KMenu::focusNextPrevChild( bool next ) 00382 { 00383 d->resetKeyboardVars(); 00384 return QMenu::focusNextPrevChild( next ); 00385 } 00386 00387 QString KMenu::underlineText(const QString& text, uint length) 00388 { 00389 QString ret = text; 00390 for (uint i = 0; i < length; i++) { 00391 if (ret[2*i] != '&') 00392 ret.insert(2*i, '&'); 00393 } 00394 return ret; 00395 } 00396 00397 void KMenu::KMenuPrivate::resetKeyboardVars(bool _noMatches) 00398 { 00399 // Clean up keyboard variables 00400 if (lastHitAction) { 00401 lastHitAction->setText(originalText); 00402 lastHitAction = 0L; 00403 } 00404 00405 if (!noMatches) { 00406 keySeq.clear(); 00407 } 00408 00409 noMatches = _noMatches; 00410 } 00411 00412 void KMenu::setKeyboardShortcutsEnabled(bool enable) 00413 { 00414 d->shortcuts = enable; 00415 } 00416 00417 void KMenu::setKeyboardShortcutsExecute(bool enable) 00418 { 00419 d->autoExec = enable; 00420 } 00429 void KMenu::mousePressEvent(QMouseEvent* e) 00430 { 00431 if (d->ctxMenu && d->ctxMenu->isVisible()) 00432 { 00433 // hide on a second context menu event 00434 d->ctxMenu->hide(); 00435 } 00436 00437 if( e->button() == Qt::MidButton) 00438 return; 00439 00440 QMenu::mousePressEvent(e); 00441 } 00442 00443 void KMenu::mouseReleaseEvent(QMouseEvent* e) 00444 { 00445 // Save the button, and the modifiers 00446 d->keyboardModifiers = e->modifiers(); 00447 d->mouseButtons = e->buttons(); 00448 00449 if ( e->button() == Qt::MidButton) { 00450 if(activeAction() ) { 00451 const QMetaObject *metaObject = activeAction()->metaObject(); 00452 const int index = metaObject->indexOfMethod("triggered(Qt::MouseButtons,Qt::KeyboardModifiers)"); 00453 if (index != -1) { 00454 const QMetaMethod method = metaObject->method(index); 00455 method.invoke(activeAction(), Qt::DirectConnection, 00456 Q_ARG(Qt::MouseButtons, e->button()), 00457 Q_ARG(Qt::KeyboardModifiers, QApplication::keyboardModifiers() )); 00458 } 00459 } 00460 return; 00461 } 00462 00463 if ( !d->ctxMenu || !d->ctxMenu->isVisible() ) 00464 QMenu::mouseReleaseEvent(e); 00465 } 00466 00467 QMenu* KMenu::contextMenu() 00468 { 00469 if (!d->ctxMenu) 00470 { 00471 d->ctxMenu = new QMenu(this); 00472 connect(this, SIGNAL(hovered(QAction*)), SLOT(actionHovered(QAction*))); 00473 } 00474 00475 return d->ctxMenu; 00476 } 00477 00478 const QMenu* KMenu::contextMenu() const 00479 { 00480 return const_cast< KMenu* >( this )->contextMenu(); 00481 } 00482 00483 void KMenu::hideContextMenu() 00484 { 00485 if (!d->ctxMenu || !d->ctxMenu->isVisible()) 00486 { 00487 return; 00488 } 00489 00490 d->ctxMenu->hide(); 00491 } 00492 00493 void KMenu::KMenuPrivate::actionHovered(QAction* action) 00494 { 00495 lastHoveredAction = action; 00496 parent->hideContextMenu(); 00497 } 00498 00499 static void KMenuSetActionData(QMenu *menu,KMenu* contextedMenu, QAction* contextedAction) { 00500 const QList<QAction*> actions=menu->actions(); 00501 QVariant v; 00502 v.setValue(KMenuContext(contextedMenu,contextedAction)); 00503 for(int i=0;i<actions.count();i++) { 00504 actions[i]->setData(v); 00505 } 00506 } 00507 00508 void KMenu::KMenuPrivate::showCtxMenu(const QPoint &pos) 00509 { 00510 highlightedAction = parent->activeAction(); 00511 00512 if (!highlightedAction) 00513 { 00514 KMenuSetActionData(parent,0,0); 00515 return; 00516 } 00517 00518 emit parent->aboutToShowContextMenu(parent, highlightedAction, ctxMenu); 00519 KMenuSetActionData(parent,parent,highlightedAction); 00520 00521 00522 if (QMenu* subMenu = highlightedAction->menu()) 00523 { 00524 QTimer::singleShot(100, subMenu, SLOT(hide())); 00525 } 00526 00527 00528 ctxMenu->popup(parent->mapToGlobal(pos)); 00529 } 00530 00531 void KMenu::KMenuPrivate::skipTitles(QKeyEvent *event) 00532 { 00533 QWidgetAction *action = qobject_cast<QWidgetAction*>(parent->activeAction()); 00534 QWidgetAction *firstAction = action; 00535 while (action && action->objectName() == KMENU_TITLE) 00536 { 00537 parent->keyPressEvent(event); 00538 action = qobject_cast<QWidgetAction*>(parent->activeAction()); 00539 if (firstAction == action) { // we looped and only found titles 00540 parent->setActiveAction(0); 00541 break; 00542 } 00543 } 00544 } 00545 00546 KMenu * KMenu::contextMenuFocus( ) 00547 { 00548 return qobject_cast<KMenu*>(QApplication::activePopupWidget()); 00549 } 00550 00551 QAction * KMenu::contextMenuFocusAction( ) 00552 { 00553 if (KMenu* menu = qobject_cast<KMenu*>(QApplication::activePopupWidget())) { 00554 if (!menu->d->lastHoveredAction) { 00555 return 0; 00556 } 00557 QVariant var = menu->d->lastHoveredAction->data(); 00558 KMenuContext ctx = var.value<KMenuContext>(); 00559 Q_ASSERT(ctx.menu() == menu); 00560 return ctx.action(); 00561 } 00562 00563 return 0L; 00564 } 00565 00566 void KMenu::contextMenuEvent(QContextMenuEvent* e) 00567 { 00568 if (d->ctxMenu) 00569 { 00570 if (e->reason() == QContextMenuEvent::Mouse) 00571 { 00572 d->showCtxMenu(e->pos()); 00573 } 00574 else if (activeAction()) 00575 { 00576 d->showCtxMenu(actionGeometry(activeAction()).center()); 00577 } 00578 00579 e->accept(); 00580 return; 00581 } 00582 00583 QMenu::contextMenuEvent(e); 00584 } 00585 00586 void KMenu::hideEvent(QHideEvent *e) 00587 { 00588 if (d->ctxMenu && d->ctxMenu->isVisible()) 00589 { 00590 // we need to block signals here when the ctxMenu is showing 00591 // to prevent the QPopupMenu::activated(int) signal from emitting 00592 // when hiding with a context menu, the user doesn't expect the 00593 // menu to actually do anything. 00594 // since hideEvent gets called very late in the process of hiding 00595 // (deep within QWidget::hide) the activated(int) signal is the 00596 // last signal to be emitted, even after things like aboutToHide() 00597 // AJS 00598 bool blocked = blockSignals(true); 00599 d->ctxMenu->hide(); 00600 blockSignals(blocked); 00601 } 00602 QMenu::hideEvent(e); 00603 } 00612 KMenuContext::KMenuContext( ) 00613 : m_menu(0L) 00614 , m_action(0L) 00615 { 00616 } 00617 00618 KMenuContext::KMenuContext( const KMenuContext & o ) 00619 : m_menu(o.m_menu) 00620 , m_action(o.m_action) 00621 { 00622 } 00623 00624 KMenuContext::KMenuContext(QPointer<KMenu> menu,QPointer<QAction> action) 00625 : m_menu(menu) 00626 , m_action(action) 00627 { 00628 } 00629 00630 #include "kmenu.moc"
KDE 4.7 API Reference