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