• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

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

KDEUI

Skip menu "KDEUI"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal