• Skip to content
  • Skip to link menu
KDE 4.6 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/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 static const char KMENU_TITLE[] = "kmenu_title";
00045 
00046 class KMenu::KMenuPrivate
00047     : public QObject
00048 {
00049 public:
00050     KMenuPrivate (KMenu *_parent);
00051     ~KMenuPrivate ();
00052 
00053     void resetKeyboardVars(bool noMatches = false);
00054     void actionHovered(QAction* action);
00055     void showCtxMenu(const QPoint &pos);
00056     void skipTitles(QKeyEvent *event);
00057 
00068     bool eventFilter(QObject *object, QEvent *event)
00069     {
00070         Q_UNUSED(object);
00071 
00072         if (event->type() == QEvent::Paint ||
00073             event->type() == QEvent::KeyPress ||
00074             event->type() == QEvent::KeyRelease) {
00075             return false;
00076         }
00077 
00078         event->accept();
00079         return true;
00080     }
00081 
00082     KMenu *parent;
00083 
00084     // variables for keyboard navigation
00085     QTimer clearTimer;
00086 
00087     bool noMatches : 1;
00088     bool shortcuts : 1;
00089     bool autoExec : 1;
00090 
00091     QString keySeq;
00092     QString originalText;
00093 
00094     QAction* lastHitAction;
00095     QAction* lastHoveredAction;
00096     Qt::MouseButtons mouseButtons;
00097     Qt::KeyboardModifiers keyboardModifiers;
00098 
00099     // support for RMB menus on menus
00100     QMenu* ctxMenu;
00101     QPointer<QAction> highlightedAction;
00102 
00103 };
00104 
00105 KMenu::KMenuPrivate::KMenuPrivate (KMenu *_parent)
00106     : parent(_parent)
00107     , noMatches(false)
00108     , shortcuts(false)
00109     , autoExec(false)
00110     , lastHitAction(0L)
00111     , lastHoveredAction(0L)
00112     , mouseButtons(Qt::NoButton)
00113     , keyboardModifiers(Qt::NoModifier)
00114     , ctxMenu(0)
00115     , highlightedAction(0)
00116 {
00117     resetKeyboardVars();
00118     KAcceleratorManager::manage(parent);
00119 }
00120 
00121 KMenu::KMenuPrivate::~KMenuPrivate ()
00122 {
00123     delete ctxMenu;
00124 }
00125 
00126 
00131 class KMenuContext {
00132 public:
00133     KMenuContext();
00134     KMenuContext(const KMenuContext& o);
00135     KMenuContext(QPointer<KMenu> menu,QPointer<QAction> action);
00136 
00137     inline QPointer<KMenu> menu() const { return m_menu; }
00138     inline QPointer<QAction> action() const { return m_action; }
00139 
00140 private:
00141     QPointer<KMenu> m_menu;
00142     QPointer<QAction> m_action;
00143 };
00144 
00145 
00146 Q_DECLARE_METATYPE(KMenuContext)
00147 
00148 
00149 
00150 KMenu::KMenu(QWidget *parent)
00151     : QMenu(parent)
00152     , d(new KMenuPrivate(this))
00153 {
00154     connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars()));
00155 }
00156 
00157 KMenu::KMenu( const QString & title, QWidget * parent )
00158     : QMenu(title, parent)
00159     , d(new KMenuPrivate(this))
00160 {
00161     connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars()));
00162 }
00163 
00164 KMenu::~KMenu()
00165 {
00166     delete d;
00167 }
00168 
00169 QAction* KMenu::addTitle(const QString &text, QAction* before)
00170 {
00171     return addTitle(QIcon(), text, before);
00172 }
00173 
00174 QAction* KMenu::addTitle(const QIcon &icon, const QString &text, QAction* before)
00175 {
00176     QAction *buttonAction = new QAction(this);
00177     QFont font = buttonAction->font();
00178     font.setBold(true);
00179     buttonAction->setFont(font);
00180     buttonAction->setText(text);
00181     buttonAction->setIcon(icon);
00182 
00183     QWidgetAction *action = new QWidgetAction(this);
00184     action->setObjectName(KMENU_TITLE);
00185     QToolButton *titleButton = new QToolButton(this);
00186     titleButton->installEventFilter(d); // prevent clicks on the title of the menu
00187     titleButton->setDefaultAction(buttonAction);
00188     titleButton->setDown(true); // prevent hover style changes in some styles
00189     titleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
00190     action->setDefaultWidget(titleButton);
00191 
00192     insertAction(before, action);
00193     return action;
00194 }
00195 
00199 void KMenu::closeEvent(QCloseEvent*e)
00200 {
00201     if (d->shortcuts)
00202         d->resetKeyboardVars();
00203     QMenu::closeEvent(e);
00204 }
00205 
00206 Qt::MouseButtons KMenu::mouseButtons() const
00207 {
00208     return d->mouseButtons;
00209 }
00210 
00211 Qt::KeyboardModifiers KMenu::keyboardModifiers() const
00212 {
00213     return d->keyboardModifiers;
00214 }
00215 
00216 void KMenu::keyPressEvent(QKeyEvent* e)
00217 {
00218     d->mouseButtons = Qt::NoButton;
00219     d->keyboardModifiers = Qt::NoModifier;
00220 
00221     if (!d->shortcuts) {
00222         d->keyboardModifiers = e->modifiers();
00223         QMenu::keyPressEvent(e);
00224 
00225         if (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down) {
00226             d->skipTitles(e);
00227         }
00228 
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 
00249         if (key == Qt::Key_Up || key == Qt::Key_Down) {
00250             d->skipTitles(e);
00251         }
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 void KMenu::KMenuPrivate::skipTitles(QKeyEvent *event)
00526 {
00527     QWidgetAction *action = qobject_cast<QWidgetAction*>(parent->activeAction());
00528     QWidgetAction *firstAction = action;
00529     while (action && action->objectName() == KMENU_TITLE)
00530     {
00531         parent->keyPressEvent(event);
00532         action = qobject_cast<QWidgetAction*>(parent->activeAction());
00533         if (firstAction == action) { // we looped and only found titles
00534             parent->setActiveAction(0);
00535             break;
00536         }
00537     }
00538 }
00539 
00540 KMenu * KMenu::contextMenuFocus( )
00541 {
00542   return qobject_cast<KMenu*>(QApplication::activePopupWidget());
00543 }
00544 
00545 QAction * KMenu::contextMenuFocusAction( )
00546 {
00547   if (KMenu* menu = qobject_cast<KMenu*>(QApplication::activePopupWidget())) {
00548     if (!menu->d->lastHoveredAction) {
00549       return 0;
00550     }
00551     QVariant var = menu->d->lastHoveredAction->data();
00552     KMenuContext ctx = var.value<KMenuContext>();
00553     Q_ASSERT(ctx.menu() == menu);
00554     return ctx.action();
00555   }
00556 
00557   return 0L;
00558 }
00559 
00560 void KMenu::contextMenuEvent(QContextMenuEvent* e)
00561 {
00562     if (d->ctxMenu)
00563     {
00564         if (e->reason() == QContextMenuEvent::Mouse)
00565         {
00566             d->showCtxMenu(e->pos());
00567         }
00568         else if (activeAction())
00569         {
00570             d->showCtxMenu(actionGeometry(activeAction()).center());
00571         }
00572 
00573         e->accept();
00574         return;
00575     }
00576 
00577     QMenu::contextMenuEvent(e);
00578 }
00579 
00580 void KMenu::hideEvent(QHideEvent *e)
00581 {
00582     if (d->ctxMenu && d->ctxMenu->isVisible())
00583     {
00584         // we need to block signals here when the ctxMenu is showing
00585         // to prevent the QPopupMenu::activated(int) signal from emitting
00586         // when hiding with a context menu, the user doesn't expect the
00587         // menu to actually do anything.
00588         // since hideEvent gets called very late in the process of hiding
00589         // (deep within QWidget::hide) the activated(int) signal is the
00590         // last signal to be emitted, even after things like aboutToHide()
00591         // AJS
00592         bool blocked = blockSignals(true);
00593         d->ctxMenu->hide();
00594         blockSignals(blocked);
00595     }
00596     QMenu::hideEvent(e);
00597 }
00606 KMenuContext::KMenuContext( )
00607   : m_menu(0L)
00608   , m_action(0L)
00609 {
00610 }
00611 
00612 KMenuContext::KMenuContext( const KMenuContext & o )
00613   : m_menu(o.m_menu)
00614   , m_action(o.m_action)
00615 {
00616 }
00617 
00618 KMenuContext::KMenuContext(QPointer<KMenu> menu,QPointer<QAction> action)
00619   : m_menu(menu)
00620   , m_action(action)
00621 {
00622 }
00623 
00624 #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
  • 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.3
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