• Skip to content
  • Skip to link menu
KDE 4.7 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • 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/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"

KDEUI

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.5
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