• 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     QAction* lastHoveredAction;
00068     Qt::MouseButtons mouseButtons;
00069     Qt::KeyboardModifiers keyboardModifiers;
00070 
00071     // support for RMB menus on menus
00072     QMenu* ctxMenu;
00073     QPointer<QAction> highlightedAction;
00074 
00075     class EventSniffer;
00076     EventSniffer *eventSniffer;
00077 };
00078 
00089 class KMenu::KMenuPrivate::EventSniffer
00090     : public QObject
00091 {
00092 public:
00093     EventSniffer(QObject *parent = 0)
00094         : QObject(parent) { }
00095 
00096     ~EventSniffer() { }
00097 
00098     bool eventFilter(QObject *object, QEvent *event)
00099     {
00100         Q_UNUSED(object);
00101 
00102         if (event->type() == QEvent::Paint ||
00103             event->type() == QEvent::KeyPress ||
00104             event->type() == QEvent::KeyRelease) {
00105             return false;
00106         }
00107 
00108         event->accept();
00109         return true;
00110     }
00111 };
00112 
00113 KMenu::KMenuPrivate::KMenuPrivate (KMenu *_parent)
00114     : parent(_parent)
00115     , noMatches(false)
00116     , shortcuts(false)
00117     , autoExec(false)
00118     , lastHitAction(0L)
00119     , lastHoveredAction(0L)
00120     , mouseButtons(Qt::NoButton)
00121     , keyboardModifiers(Qt::NoModifier)
00122     , ctxMenu(0)
00123     , highlightedAction(0)
00124     , eventSniffer(new EventSniffer)
00125 {
00126     resetKeyboardVars();
00127     KAcceleratorManager::manage(parent);
00128 }
00129 
00130 KMenu::KMenuPrivate::~KMenuPrivate ()
00131 {
00132     delete ctxMenu;
00133     delete eventSniffer;
00134 }
00135 
00136 
00141 class KMenuContext {
00142 public:
00143     KMenuContext();
00144     KMenuContext(const KMenuContext& o);
00145     KMenuContext(QPointer<KMenu> menu,QPointer<QAction> action);
00146 
00147     inline QPointer<KMenu> menu() const { return m_menu; }
00148     inline QPointer<QAction> action() const { return m_action; }
00149 
00150 private:
00151     QPointer<KMenu> m_menu;
00152     QPointer<QAction> m_action;
00153 };
00154 
00155 
00156 Q_DECLARE_METATYPE(KMenuContext)
00157 
00158 
00159 
00160 KMenu::KMenu(QWidget *parent)
00161     : QMenu(parent)
00162     , d(new KMenuPrivate(this))
00163 {
00164     connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars()));
00165 }
00166 
00167 KMenu::KMenu( const QString & title, QWidget * parent )
00168     : QMenu(title, parent)
00169     , d(new KMenuPrivate(this))
00170 {
00171     connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars()));
00172 }
00173 
00174 KMenu::~KMenu()
00175 {
00176     delete d;
00177 }
00178 
00179 QAction* KMenu::addTitle(const QString &text, QAction* before)
00180 {
00181     return addTitle(QIcon(), text, before);
00182 }
00183 
00184 QAction* KMenu::addTitle(const QIcon &icon, const QString &text, QAction* before)
00185 {
00186     QAction *buttonAction = new QAction(this);
00187     QFont font = buttonAction->font();
00188     font.setBold(true);
00189     buttonAction->setFont(font);
00190     buttonAction->setText(text);
00191     buttonAction->setIcon(icon);
00192 
00193     QWidgetAction *action = new QWidgetAction(this);
00194     QToolButton *titleButton = new QToolButton(this);
00195     titleButton->installEventFilter(d->eventSniffer); // prevent clicks on the title of the menu
00196     titleButton->setDefaultAction(buttonAction);
00197     titleButton->setDown(true); // prevent hover style changes in some styles
00198     titleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
00199     action->setDefaultWidget(titleButton);
00200 
00201     insertAction(before, action);
00202     return action;
00203 }
00204 
00208 void KMenu::closeEvent(QCloseEvent*e)
00209 {
00210     if (d->shortcuts)
00211         d->resetKeyboardVars();
00212     QMenu::closeEvent(e);
00213 }
00214 
00215 Qt::MouseButtons KMenu::mouseButtons() const
00216 {
00217     return d->mouseButtons;
00218 }
00219 
00220 Qt::KeyboardModifiers KMenu::keyboardModifiers() const
00221 {
00222     return d->keyboardModifiers;
00223 }
00224 
00225 void KMenu::keyPressEvent(QKeyEvent* e)
00226 {
00227     d->mouseButtons = Qt::NoButton;
00228     d->keyboardModifiers = Qt::NoModifier;
00229 
00230     if (!d->shortcuts) {
00231         d->keyboardModifiers = e->modifiers();
00232         QMenu::keyPressEvent(e);
00233         return;
00234     }
00235 
00236     QAction* a = 0L;
00237     bool firstpass = true;
00238     QString keyString = e->text();
00239 
00240     // check for common commands dealt with by QMenu
00241     int key = e->key();
00242     if (key == Qt::Key_Escape || key == Qt::Key_Return || key == Qt::Key_Enter
00243             || key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_Left
00244             || key == Qt::Key_Right || key == Qt::Key_F1 || key == Qt::Key_PageUp
00245             || key == Qt::Key_PageDown || key == Qt::Key_Back || key == Qt::Key_Select) {
00246 
00247         d->resetKeyboardVars();
00248         // continue event processing by QMenu
00249         //e->ignore();
00250         d->keyboardModifiers = e->modifiers();
00251         QMenu::keyPressEvent(e);
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 KMenu * KMenu::contextMenuFocus( )
00526 {
00527   return qobject_cast<KMenu*>(QApplication::activePopupWidget());
00528 }
00529 
00530 QAction * KMenu::contextMenuFocusAction( )
00531 {
00532   if (KMenu* menu = qobject_cast<KMenu*>(QApplication::activePopupWidget())) {
00533     if (!menu->d->lastHoveredAction) {
00534       return 0;
00535     }
00536     QVariant var = menu->d->lastHoveredAction->data();
00537     KMenuContext ctx = var.value<KMenuContext>();
00538     Q_ASSERT(ctx.menu() == menu);
00539     return ctx.action();
00540   }
00541 
00542   return 0L;
00543 }
00544 
00545 void KMenu::contextMenuEvent(QContextMenuEvent* e)
00546 {
00547     if (d->ctxMenu)
00548     {
00549         if (e->reason() == QContextMenuEvent::Mouse)
00550         {
00551             d->showCtxMenu(e->pos());
00552         }
00553         else if (activeAction())
00554         {
00555             d->showCtxMenu(actionGeometry(activeAction()).center());
00556         }
00557 
00558         e->accept();
00559         return;
00560     }
00561 
00562     QMenu::contextMenuEvent(e);
00563 }
00564 
00565 void KMenu::hideEvent(QHideEvent *e)
00566 {
00567     if (d->ctxMenu && d->ctxMenu->isVisible())
00568     {
00569         // we need to block signals here when the ctxMenu is showing
00570         // to prevent the QPopupMenu::activated(int) signal from emitting
00571         // when hiding with a context menu, the user doesn't expect the
00572         // menu to actually do anything.
00573         // since hideEvent gets called very late in the process of hiding
00574         // (deep within QWidget::hide) the activated(int) signal is the
00575         // last signal to be emitted, even after things like aboutToHide()
00576         // AJS
00577         bool blocked = blockSignals(true);
00578         d->ctxMenu->hide();
00579         blockSignals(blocked);
00580     }
00581     QMenu::hideEvent(e);
00582 }
00591 KMenuContext::KMenuContext( )
00592   : m_menu(0L)
00593   , m_action(0L)
00594 {
00595 }
00596 
00597 KMenuContext::KMenuContext( const KMenuContext & o )
00598   : m_menu(o.m_menu)
00599   , m_action(o.m_action)
00600 {
00601 }
00602 
00603 KMenuContext::KMenuContext(QPointer<KMenu> menu,QPointer<QAction> action)
00604   : m_menu(menu)
00605   , m_action(action)
00606 {
00607 }
00608 
00609 #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