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