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