KDELibs4Support

kmenu.cpp
1 /* This file is part of the KDE libraries
2  Copyright (C) 2000 Daniel M. Duley <[email protected]>
3  Copyright (C) 2002,2006 Hamish Rodda <[email protected]>
4  Copyright (C) 2006 Olivier Goffart <[email protected]>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License version 2 as published by the Free Software Foundation.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Library General Public License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to
17  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19 */
20 
21 #include "kmenu.h"
22 
23 #include <QMetaMethod>
24 #include <QObject>
25 #include <QPointer>
26 #include <QTimer>
27 #include <QApplication>
28 #include <QCursor>
29 #include <QFontMetrics>
30 #include <QHBoxLayout>
31 #include <QKeyEvent>
32 #include <QLabel>
33 #include <QPainter>
34 #include <QStyle>
35 #include <QToolButton>
36 #include <QWidgetAction>
37 
38 #include <kdebug.h>
39 #include <klocalizedstring.h>
40 #include <kacceleratormanager.h>
41 
42 static const char KMENU_TITLE[] = "kmenu_title";
43 
44 class Q_DECL_HIDDEN KMenu::KMenuPrivate
45  : public QObject
46 {
47 public:
48  KMenuPrivate(KMenu *_parent);
49  ~KMenuPrivate();
50 
51  void resetKeyboardVars(bool noMatches = false);
52  void actionHovered(QAction *action);
53  void showCtxMenu(const QPoint &pos);
54  void skipTitles(QKeyEvent *event);
55 
66  bool eventFilter(QObject *object, QEvent *event) override
67  {
68  Q_UNUSED(object);
69 
70  if (event->type() == QEvent::Paint ||
71  event->type() == QEvent::KeyPress ||
72  event->type() == QEvent::KeyRelease) {
73  return false;
74  }
75 
76  event->accept();
77  return true;
78  }
79 
80  KMenu *parent;
81 
82  // variables for keyboard navigation
83  QTimer clearTimer;
84 
85  bool noMatches : 1;
86  bool shortcuts : 1;
87  bool autoExec : 1;
88 
89  QString keySeq;
90  QString originalText;
91 
92  QAction *lastHitAction;
93  QAction *lastHoveredAction;
94  Qt::MouseButtons mouseButtons;
95  Qt::KeyboardModifiers keyboardModifiers;
96 
97  // support for RMB menus on menus
98  QMenu *ctxMenu;
99  QPointer<QAction> highlightedAction;
100 
101 };
102 
103 KMenu::KMenuPrivate::KMenuPrivate(KMenu *_parent)
104  : parent(_parent)
105  , noMatches(false)
106  , shortcuts(false)
107  , autoExec(false)
108  , lastHitAction(nullptr)
109  , lastHoveredAction(nullptr)
110  , mouseButtons(Qt::NoButton)
111  , keyboardModifiers(Qt::NoModifier)
112  , ctxMenu(nullptr)
113  , highlightedAction(nullptr)
114 {
115  resetKeyboardVars();
117 }
118 
119 KMenu::KMenuPrivate::~KMenuPrivate()
120 {
121  delete ctxMenu;
122 }
123 
128 class KMenuContext
129 {
130 public:
131  KMenuContext();
132  KMenuContext(const KMenuContext &o);
133  KMenuContext(QPointer<KMenu> menu, QPointer<QAction> action);
134 
135  inline QPointer<KMenu> menu() const
136  {
137  return m_menu;
138  }
139  inline QPointer<QAction> action() const
140  {
141  return m_action;
142  }
143 
144 private:
145  QPointer<KMenu> m_menu;
146  QPointer<QAction> m_action;
147 };
148 
149 Q_DECLARE_METATYPE(KMenuContext)
150 
151 KMenu::KMenu(QWidget *parent)
152  : QMenu(parent)
153  , d(new KMenuPrivate(this))
154 {
155  connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars()));
156 }
157 
158 KMenu::KMenu(const QString &title, QWidget *parent)
159  : QMenu(title, parent)
160  , d(new KMenuPrivate(this))
161 {
162  connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars()));
163 }
164 
166 {
167  delete d;
168 }
169 
170 QAction *KMenu::addTitle(const QString &text, QAction *before)
171 {
172  return addTitle(QIcon(), text, before);
173 }
174 
175 QAction *KMenu::addTitle(const QIcon &icon, const QString &text, QAction *before)
176 {
177  QAction *buttonAction = new QAction(this);
178  QFont font = buttonAction->font();
179  font.setBold(true);
180  buttonAction->setFont(font);
181  buttonAction->setText(text);
182  buttonAction->setIcon(icon);
183 
184  QWidgetAction *action = new QWidgetAction(this);
185  action->setObjectName(KMENU_TITLE);
186  QToolButton *titleButton = new QToolButton(this);
187  titleButton->installEventFilter(d); // prevent clicks on the title of the menu
188  titleButton->setDefaultAction(buttonAction);
189  titleButton->setDown(true); // prevent hover style changes in some styles
191  action->setDefaultWidget(titleButton);
192 
193  insertAction(before, action);
194  return action;
195 }
196 
201 {
202  if (d->shortcuts) {
203  d->resetKeyboardVars();
204  }
206 }
207 
209 {
210  return d->mouseButtons;
211 }
212 
214 {
215  return d->keyboardModifiers;
216 }
217 
218 void KMenu::keyPressEvent(QKeyEvent *e)
219 {
220  d->mouseButtons = Qt::NoButton;
221  d->keyboardModifiers = Qt::NoModifier;
222 
223  if (!d->shortcuts) {
224  d->keyboardModifiers = e->modifiers();
226 
227  if (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down) {
228  d->skipTitles(e);
229  }
230 
231  return;
232  }
233 
234  QAction *a = nullptr;
235  bool firstpass = true;
236  QString keyString = e->text();
237 
238  // check for common commands dealt with by QMenu
239  int key = e->key();
240  if (key == Qt::Key_Escape || key == Qt::Key_Return || key == Qt::Key_Enter
241  || key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_Left
242  || key == Qt::Key_Right || key == Qt::Key_F1 || key == Qt::Key_PageUp
243  || key == Qt::Key_PageDown || key == Qt::Key_Back || key == Qt::Key_Select) {
244 
245  d->resetKeyboardVars();
246  // continue event processing by QMenu
247  //e->ignore();
248  d->keyboardModifiers = e->modifiers();
250 
251  if (key == Qt::Key_Up || key == Qt::Key_Down) {
252  d->skipTitles(e);
253  }
254  return;
255  } else if (key == Qt::Key_Shift || key == Qt::Key_Control || key == Qt::Key_Alt || key == Qt::Key_Meta) {
256  return QMenu::keyPressEvent(e);
257  }
258 
259  // check to see if the user wants to remove a key from the sequence (backspace)
260  // or clear the sequence (delete)
261  if (!d->keySeq.isNull()) {
262  if (key == Qt::Key_Backspace) {
263 
264  if (d->keySeq.length() == 1) {
265  d->resetKeyboardVars();
266  return;
267  }
268 
269  // keep the last sequence in keyString
270  keyString = d->keySeq.left(d->keySeq.length() - 1);
271 
272  // allow sequence matching to be tried again
273  d->resetKeyboardVars();
274 
275  } else if (key == Qt::Key_Delete) {
276  d->resetKeyboardVars();
277 
278  // clear active item
279  setActiveAction(nullptr);
280  return;
281 
282  } else if (d->noMatches) {
283  // clear if there are no matches
284  d->resetKeyboardVars();
285 
286  // clear active item
287  setActiveAction(nullptr);
288 
289  } else {
290  // the key sequence is not a null string
291  // therefore the lastHitAction is valid
292  a = d->lastHitAction;
293  }
294 
295  } else if (key == Qt::Key_Backspace && menuAction()) {
296  // backspace with no chars in the buffer... go back a menu.
297  hide();
298  d->resetKeyboardVars();
299  return;
300  }
301 
302  d->keySeq += keyString;
303  const int seqLen = d->keySeq.length();
304 
305  foreach (a, actions()) {
306  // don't search disabled entries
307  if (!a->isEnabled()) {
308  continue;
309  }
310 
311  QString thisText;
312 
313  // retrieve the right text
314  // (the last selected item one may have additional ampersands)
315  if (a == d->lastHitAction) {
316  thisText = d->originalText;
317  } else {
318  thisText = a->text();
319  }
320 
321  // if there is an accelerator present, remove it
322  thisText = KLocalizedString::removeAcceleratorMarker(thisText);
323 
324  // chop text to the search length
325  thisText = thisText.left(seqLen);
326 
327  // do the search
328  if (!thisText.indexOf(d->keySeq, 0, Qt::CaseInsensitive)) {
329 
330  if (firstpass) {
331  // match
332  setActiveAction(a);
333 
334  // check to see if we're underlining a different item
335  if (d->lastHitAction && d->lastHitAction != a)
336  // yes; revert the underlining
337  {
338  d->lastHitAction->setText(d->originalText);
339  }
340 
341  // set the original text if it's a different item
342  if (d->lastHitAction != a || d->lastHitAction == nullptr) {
343  d->originalText = a->text();
344  }
345 
346  // underline the currently selected item
347  a->setText(underlineText(d->originalText, d->keySeq.length()));
348 
349  // remember what's going on
350  d->lastHitAction = a;
351 
352  // start/restart the clear timer
353  d->clearTimer.setSingleShot(true);
354  d->clearTimer.start(5000);
355 
356  // go around for another try, to see if we can execute
357  firstpass = false;
358  } else {
359  // don't allow execution
360  return;
361  }
362  }
363 
364  // fall through to allow execution
365  }
366 
367  if (!firstpass) {
368  if (d->autoExec) {
369  // activate anything
370  d->lastHitAction->activate(QAction::Trigger);
371  d->resetKeyboardVars();
372 
373  } else if (d->lastHitAction && d->lastHitAction->menu()) {
374  // only activate sub-menus
375  d->lastHitAction->activate(QAction::Trigger);
376  d->resetKeyboardVars();
377  }
378 
379  return;
380  }
381 
382  // no matches whatsoever, clean up
383  d->resetKeyboardVars(true);
384  //e->ignore();
386 }
387 
388 bool KMenu::focusNextPrevChild(bool next)
389 {
390  d->resetKeyboardVars();
391  return QMenu::focusNextPrevChild(next);
392 }
393 
394 QString KMenu::underlineText(const QString &text, uint length)
395 {
396  QString ret = text;
397  for (uint i = 0; i < length; i++) {
398  if (ret[2 * i] != '&') {
399  ret.insert(2 * i, '&');
400  }
401  }
402  return ret;
403 }
404 
405 void KMenu::KMenuPrivate::resetKeyboardVars(bool _noMatches)
406 {
407  // Clean up keyboard variables
408  if (lastHitAction) {
409  lastHitAction->setText(originalText);
410  lastHitAction = nullptr;
411  }
412 
413  if (!noMatches) {
414  keySeq.clear();
415  }
416 
417  noMatches = _noMatches;
418 }
419 
421 {
422  d->shortcuts = enable;
423 }
424 
426 {
427  d->autoExec = enable;
428 }
438 {
439  if (d->ctxMenu && d->ctxMenu->isVisible()) {
440  // hide on a second context menu event
441  d->ctxMenu->hide();
442  }
443 
444  if (e->button() == Qt::MidButton) {
445  return;
446  }
447 
449 }
450 
451 void KMenu::mouseReleaseEvent(QMouseEvent *e)
452 {
453  // Save the button, and the modifiers
454  d->keyboardModifiers = e->modifiers();
455  d->mouseButtons = e->buttons();
456 
457  if (e->button() == Qt::MidButton) {
458  if (activeAction()) {
460  const int index = metaObject->indexOfMethod("triggered(Qt::MouseButtons,Qt::KeyboardModifiers)");
461  if (index != -1) {
462  const QMetaMethod method = metaObject->method(index);
464  Q_ARG(Qt::MouseButtons, e->button()),
466  }
467  }
468  return;
469  }
470 
471  if (!d->ctxMenu || !d->ctxMenu->isVisible()) {
473  }
474 }
475 
477 {
478  if (!d->ctxMenu) {
479  d->ctxMenu = new QMenu(this);
480  connect(this, SIGNAL(hovered(QAction*)), SLOT(actionHovered(QAction*)));
481  }
482 
483  return d->ctxMenu;
484 }
485 
486 const QMenu *KMenu::contextMenu() const
487 {
488  return const_cast< KMenu * >(this)->contextMenu();
489 }
490 
492 {
493  if (!d->ctxMenu || !d->ctxMenu->isVisible()) {
494  return;
495  }
496 
497  d->ctxMenu->hide();
498 }
499 
500 void KMenu::KMenuPrivate::actionHovered(QAction *action)
501 {
502  lastHoveredAction = action;
503  parent->hideContextMenu();
504 }
505 
506 static void KMenuSetActionData(QMenu *menu, KMenu *contextedMenu, QAction *contextedAction)
507 {
508  const QList<QAction *> actions = menu->actions();
509  QVariant v;
510  v.setValue(KMenuContext(contextedMenu, contextedAction));
511  for (int i = 0; i < actions.count(); i++) {
512  actions[i]->setData(v);
513  }
514 }
515 
516 void KMenu::KMenuPrivate::showCtxMenu(const QPoint &pos)
517 {
518  highlightedAction = parent->activeAction();
519 
520  if (!highlightedAction) {
521  KMenuSetActionData(parent, nullptr, nullptr);
522  return;
523  }
524 
525  emit parent->aboutToShowContextMenu(parent, highlightedAction, ctxMenu);
526  KMenuSetActionData(parent, parent, highlightedAction);
527 
528  if (QMenu *subMenu = highlightedAction->menu()) {
529  QTimer::singleShot(100, subMenu, SLOT(hide()));
530  }
531 
532  ctxMenu->popup(parent->mapToGlobal(pos));
533 }
534 
535 void KMenu::KMenuPrivate::skipTitles(QKeyEvent *event)
536 {
537  QWidgetAction *action = qobject_cast<QWidgetAction *>(parent->activeAction());
538  QWidgetAction *firstAction = action;
539  while (action && action->objectName() == KMENU_TITLE) {
540  parent->keyPressEvent(event);
541  action = qobject_cast<QWidgetAction *>(parent->activeAction());
542  if (firstAction == action) { // we looped and only found titles
543  parent->setActiveAction(nullptr);
544  break;
545  }
546  }
547 }
548 
550 {
552 }
553 
555 {
556  if (KMenu *menu = qobject_cast<KMenu *>(QApplication::activePopupWidget())) {
557  if (!menu->d->lastHoveredAction) {
558  return nullptr;
559  }
560  QVariant var = menu->d->lastHoveredAction->data();
561  KMenuContext ctx = var.value<KMenuContext>();
562  Q_ASSERT(ctx.menu() == menu);
563  return ctx.action();
564  }
565 
566  return nullptr;
567 }
568 
569 void KMenu::contextMenuEvent(QContextMenuEvent *e)
570 {
571  if (d->ctxMenu) {
572  if (e->reason() == QContextMenuEvent::Mouse) {
573  d->showCtxMenu(e->pos());
574  } else if (activeAction()) {
575  d->showCtxMenu(actionGeometry(activeAction()).center());
576  }
577 
578  e->accept();
579  return;
580  }
581 
583 }
584 
585 void KMenu::hideEvent(QHideEvent *e)
586 {
587  if (d->ctxMenu && d->ctxMenu->isVisible()) {
588  // we need to block signals here when the ctxMenu is showing
589  // to prevent the QPopupMenu::activated(int) signal from emitting
590  // when hiding with a context menu, the user doesn't expect the
591  // menu to actually do anything.
592  // since hideEvent gets called very late in the process of hiding
593  // (deep within QWidget::hide) the activated(int) signal is the
594  // last signal to be emitted, even after things like aboutToHide()
595  // AJS
596  bool blocked = blockSignals(true);
597  d->ctxMenu->hide();
598  blockSignals(blocked);
599  }
600  QMenu::hideEvent(e);
601 }
606 KMenuContext::KMenuContext()
607  : m_menu(nullptr)
608  , m_action(nullptr)
609 {
610 }
611 
612 KMenuContext::KMenuContext(const KMenuContext &o)
613  : m_menu(o.m_menu)
614  , m_action(o.m_action)
615 {
616 }
617 
618 KMenuContext::KMenuContext(QPointer<KMenu> menu, QPointer<QAction> action)
619  : m_menu(menu)
620  , m_action(action)
621 {
622 }
623 
624 #include "moc_kmenu.cpp"
void hovered(QAction *action)
void setText(const QString &text)
void setDown(bool)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
typedef KeyboardModifiers
Qt::KeyboardModifiers modifiers() const const
QEvent::Type type() const const
void setKeyboardShortcutsEnabled(bool enable)
Enables keyboard navigation by searching for the entered key sequence.
Definition: kmenu.cpp:420
bool invoke(QObject *object, Qt::ConnectionType connectionType, QGenericReturnArgument returnValue, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9) const const
int indexOfMethod(const char *method) const const
static QAction * contextMenuFocusAction()
returns the QAction associated with the current context menu
Definition: kmenu.cpp:554
virtual bool focusNextPrevChild(bool next) override
static QString removeAcceleratorMarker(const QString &label)
void setDefaultAction(QAction *action)
A menu with keyboard searching.
Definition: kmenu.h:42
void setActiveAction(QAction *act)
void setIcon(const QIcon &icon)
typedef MouseButtons
virtual const QMetaObject * metaObject() const const
T value() const const
QRect actionGeometry(QAction *act) const const
void setKeyboardShortcutsExecute(bool enable)
Enables execution of the menu item once it is uniquely specified.
Definition: kmenu.cpp:425
Qt::MouseButtons buttons() const const
Qt::KeyboardModifiers keyboardModifiers()
void clear()
void setBold(bool enable)
~KMenu()
Destructs the object.
Definition: kmenu.cpp:165
void hideContextMenu()
Hides the context menu if shown.
Definition: kmenu.cpp:491
void insertAction(QAction *before, QAction *action)
int count(const T &value) const const
QIcon icon() const const
virtual void contextMenuEvent(QContextMenuEvent *event)
QString & insert(int position, QChar ch)
void installEventFilter(QObject *filterObj)
virtual void mousePressEvent(QMouseEvent *e) override
Qt::MouseButton button() const const
CaseInsensitive
void setObjectName(const QString &name)
QString text() const const
QPoint pos() const const
ToolButtonTextBesideIcon
virtual bool eventFilter(QObject *watched, QEvent *event)
void hide()
QMenu(QWidget *parent)
virtual void hideEvent(QHideEvent *) override
Qt::KeyboardModifiers modifiers() const const
int key() const const
void accept()
bool blockSignals(bool block)
const QFont & font() const const
void setValue(const T &value)
Qt::MouseButtons mouseButtons() const
Return the state of the mouse buttons when the last menuitem was activated.
Definition: kmenu.cpp:208
void closeEvent(QCloseEvent *) override
This is re-implemented for keyboard navigation.
Definition: kmenu.cpp:200
const QPoint & pos() const const
QMenu * contextMenu()
Returns the context menu associated with this menu The data property of all actions inserted into the...
Definition: kmenu.cpp:476
void setDefaultWidget(QWidget *widget)
QWidget * activePopupWidget()
QAction * addTitle(const QString &text, QAction *before=nullptr)
Inserts a title item with no icon.
Definition: kmenu.cpp:170
QString left(int n) const const
QAction * menuAction() const const
virtual void closeEvent(QCloseEvent *event)
KMenu(QWidget *parent=nullptr)
Constructs a KMenu.
Definition: kmenu.cpp:151
QContextMenuEvent::Reason reason() const const
DirectConnection
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QList< QAction * > actions() const const
T qobject_cast(QObject *object)
QObject * parent() const const
void setToolButtonStyle(Qt::ToolButtonStyle style)
virtual void mouseReleaseEvent(QMouseEvent *e) override
QMetaMethod method(int index) const const
static void manage(QWidget *widget, bool programmers_mode=false)
Qt::KeyboardModifiers keyboardModifiers() const
Return the state of the keyboard modifiers when the last menuitem was activated.
Definition: kmenu.cpp:213
bool isEnabled() const const
QAction * activeAction() const const
static KMenu * contextMenuFocus()
Returns the KMenu associated with the current context menu.
Definition: kmenu.cpp:549
virtual void keyPressEvent(QKeyEvent *e) override
void mousePressEvent(QMouseEvent *e) override
End keyboard navigation.
Definition: kmenu.cpp:437
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sun Jul 5 2020 22:58:45 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.