Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qmenu.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qmenu.h"
6
7#include <QtWidgets/private/qtwidgetsglobal_p.h>
8#include <QtWidgets/private/qwidgetwindow_p.h>
9
10#include "qactiongroup.h"
11#include "qdebug.h"
12#include "qstyle.h"
13#include "qevent.h"
14#include "qtimer.h"
15#include "qlayout.h"
16#include "qstylepainter.h"
17#include <qpa/qplatformtheme.h>
18#include "qapplication.h"
19#if QT_CONFIG(accessibility)
20# include "qaccessible.h"
21#endif
22#if QT_CONFIG(effects)
23# include <private/qeffects_p.h>
24#endif
25#if QT_CONFIG(whatsthis)
26# include <qwhatsthis.h>
27#endif
28
29#include "qmenu_p.h"
30#if QT_CONFIG(menubar)
31#include "qmenubar_p.h"
32#endif
33#include "qwidgetaction.h"
34#include "qpushbutton.h"
35#if QT_CONFIG(tooltip)
36#include "qtooltip.h"
37#endif
38#include <qwindow.h>
39#include <private/qpushbutton_p.h>
40#include <private/qaction_p.h>
41#include <private/qguiapplication_p.h>
42#include <qpa/qplatformtheme.h>
43#include <private/qstyle_p.h>
44
46
47QMenu *QMenuPrivate::mouseDown = nullptr;
48
49/* QMenu code */
50// internal class used for the torn off popup
51class QTornOffMenu : public QMenu
52{
54 class QTornOffMenuPrivate : public QMenuPrivate
55 {
56 Q_DECLARE_PUBLIC(QTornOffMenu)
57 public:
58 QTornOffMenuPrivate(QMenu *p) : causedMenu(p), initialized(false) {
59 tornoff = 1;
60 causedPopup.widget = nullptr;
61 causedPopup.action = p->d_func()->causedPopup.action;
62 causedStack = p->d_func()->calcCausedStack();
63 }
64
65 void setMenuSize(const QSize &menuSize) {
66 Q_Q(QTornOffMenu);
67 QSize size = menuSize;
68 const QPoint p = (!initialized) ? causedMenu->pos() : q->pos();
69 const QRect screen = popupGeometry(QGuiApplication::screenAt(p));
70 const int desktopFrame = q->style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, nullptr, q);
71 const int titleBarHeight = q->style()->pixelMetric(QStyle::PM_TitleBarHeight, nullptr, q);
72 if (scroll && (size.height() > screen.height() - titleBarHeight || size.width() > screen.width())) {
73 const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, nullptr, q);
74 const int hmargin = q->style()->pixelMetric(QStyle::PM_MenuHMargin, nullptr, q);
75 scroll->scrollFlags |= uint(QMenuPrivate::QMenuScroller::ScrollDown);
76 size.setWidth(qMin(actionRects.at(getLastVisibleAction()).right() + fw + hmargin + rightmargin + 1, screen.width()));
77 size.setHeight(screen.height() - desktopFrame * 2 - titleBarHeight);
78 }
79 q->setFixedSize(size);
80 }
81
82 QList<QPointer<QWidget>> calcCausedStack() const override { return causedStack; }
83 QPointer<QMenu> causedMenu;
84 QList<QPointer<QWidget>> causedStack;
85 bool initialized;
86 };
87
88public:
90 {
91 Q_D(QTornOffMenu);
92 // make the torn-off menu a sibling of p (instead of a child)
93 QWidget *parentWidget = d->causedStack.isEmpty() ? p : d->causedStack.constLast();
94 if (!parentWidget && p)
95 parentWidget = p;
96 if (parentWidget && parentWidget->parentWidget())
97 parentWidget = parentWidget->parentWidget();
98 setParent(parentWidget, Qt::Window | Qt::Tool);
99 setAttribute(Qt::WA_DeleteOnClose, true);
100 setAttribute(Qt::WA_X11NetWmWindowTypeMenu, true);
102 setEnabled(p->isEnabled());
103#if QT_CONFIG(style_stylesheet)
104 setStyleSheet(p->styleSheet());
105#endif
106 if (style() != p->style())
107 setStyle(p->style());
108 setContentsMargins(p->contentsMargins());
109 setLayoutDirection(p->layoutDirection());
110 //QObject::connect(this, SIGNAL(triggered(QAction*)), this, SLOT(onTrigger(QAction*)));
111 //QObject::connect(this, SIGNAL(hovered(QAction*)), this, SLOT(onHovered(QAction*)));
112 QList<QAction*> items = p->actions();
113 for(int i = 0; i < items.size(); i++)
114 addAction(items.at(i));
115 d->setMenuSize(sizeHint());
116 d->initialized = true;
117 }
118 void syncWithMenu(QMenu *menu, QActionEvent *act)
119 {
120 Q_D(QTornOffMenu);
121 if (menu != d->causedMenu)
122 return;
123 auto action = static_cast<QAction *>(act->action());
124 if (act->type() == QEvent::ActionAdded) {
125 insertAction(static_cast<QAction *>(act->before()), action);
126 } else if (act->type() == QEvent::ActionRemoved)
127 removeAction(action);
128 }
129 void actionEvent(QActionEvent *e) override
130 {
131 Q_D(QTornOffMenu);
132 QMenu::actionEvent(e);
133 if (d->initialized) {
134 d->setMenuSize(sizeHint());
135 }
136 }
137
139 {
140 Q_D(QTornOffMenu);
141 if (!d->causedMenu)
142 return;
143 const QString &cleanTitle = QPlatformTheme::removeMnemonics(d->causedMenu->title()).trimmed();
144 setWindowTitle(cleanTitle);
145 }
146
147public slots:
150
151private:
153 friend class QMenuPrivate;
154};
155
157{
158 Q_Q(QMenu);
159#if QT_CONFIG(whatsthis)
160 q->setAttribute(Qt::WA_CustomWhatsThis);
161#endif
162 q->setAttribute(Qt::WA_X11NetWmWindowTypePopupMenu);
163 defaultMenuAction = menuAction = new QAction(q);
164 menuAction->setMenu(q); // this calls setOverrideMenuAction
165 setOverrideMenuAction(nullptr);
166 QObject::connect(menuAction, &QAction::changed, q, [this] {
167 if (!tornPopup.isNull())
168 tornPopup->updateWindowTitle();
169 });
170 q->setMouseTracking(q->style()->styleHint(QStyle::SH_Menu_MouseTracking, nullptr, q));
171 if (q->style()->styleHint(QStyle::SH_Menu_Scrollable, nullptr, q)) {
172 scroll = new QMenuPrivate::QMenuScroller;
173 scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone;
174 }
175
176 sloppyState.initialize(q);
177 delayState.initialize(q);
178 mousePopupDelay = q->style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, nullptr, q);
179}
180
182{
183 Q_Q(QMenu);
184 if (platformMenu.isNull())
185 q->setPlatformMenu(QGuiApplicationPrivate::platformTheme()->createPlatformMenu());
186 return platformMenu.data();
187}
188
189void QMenuPrivate::setPlatformMenu(QPlatformMenu *menu)
190{
191 Q_Q(QMenu);
192 if (!platformMenu.isNull() && !platformMenu->parent())
193 delete platformMenu.data();
194
195 platformMenu = menu;
196 if (!platformMenu.isNull()) {
197 QObject::connect(platformMenu, SIGNAL(aboutToShow()), q, SLOT(_q_platformMenuAboutToShow()));
198 QObject::connect(platformMenu, SIGNAL(aboutToHide()), q, SIGNAL(aboutToHide()));
199 }
200}
201
203{
204 Q_Q(QMenu);
205 if (platformMenu.isNull())
206 return;
207
208 QPlatformMenuItem *beforeItem = nullptr;
209 const QList<QAction*> actions = q->actions();
210 for (QList<QAction*>::const_reverse_iterator it = actions.rbegin(), end = actions.rend(); it != end; ++it) {
211 QPlatformMenuItem *menuItem = insertActionInPlatformMenu(*it, beforeItem);
212 beforeItem = menuItem;
213 }
214 platformMenu->syncSeparatorsCollapsible(collapsibleSeparators);
215 platformMenu->setEnabled(q->isEnabled());
216}
217
218static QWidget *getParentWidget(const QAction *action)
219{
220 auto result = action->parent();
221 while (result && !qobject_cast<QWidget *>(result))
222 result = result->parent();
223 return static_cast<QWidget *>(result);
224}
225
226void QMenuPrivate::copyActionToPlatformItem(const QAction *action, QPlatformMenuItem *item)
227{
228 item->setText(action->text());
229 item->setIsSeparator(action->isSeparator());
230 if (action->isIconVisibleInMenu()) {
231 item->setIcon(action->icon());
232 if (QWidget *w = getParentWidget(action)) {
233 QStyleOption opt;
234 opt.initFrom(w);
235 item->setIconSize(w->style()->pixelMetric(QStyle::PM_SmallIconSize, &opt, w));
236 } else {
237 QStyleOption opt;
238 item->setIconSize(QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize, &opt, nullptr));
239 }
240 } else {
241 item->setIcon(QIcon());
242 }
243 item->setVisible(action->isVisible());
244#if QT_CONFIG(shortcut)
245 item->setShortcut(action->shortcut());
246#endif
247 item->setCheckable(action->isCheckable());
248 item->setChecked(action->isChecked());
249 item->setHasExclusiveGroup(action->actionGroup() && action->actionGroup()->isExclusive());
250 item->setFont(action->font());
251 item->setRole((QPlatformMenuItem::MenuRole) action->menuRole());
252 item->setEnabled(action->isEnabled());
253
254 if (action->menu()) {
255 if (!action->menu()->platformMenu())
256 action->menu()->setPlatformMenu(platformMenu->createSubMenu());
257 item->setMenu(action->menu()->platformMenu());
258 } else {
259 item->setMenu(nullptr);
260 }
261}
262
263QPlatformMenuItem * QMenuPrivate::insertActionInPlatformMenu(const QAction *action, QPlatformMenuItem *beforeItem)
264{
265 QPlatformMenuItem *menuItem = platformMenu->createMenuItem();
266 Q_ASSERT(menuItem);
267
268 menuItem->setTag(reinterpret_cast<quintptr>(action));
269 QObject::connect(menuItem, &QPlatformMenuItem::activated, action, &QAction::trigger, Qt::QueuedConnection);
270 QObject::connect(menuItem, &QPlatformMenuItem::hovered, action, &QAction::hovered, Qt::QueuedConnection);
271 copyActionToPlatformItem(action, menuItem);
272 platformMenu->insertMenuItem(menuItem, beforeItem);
273
274 return menuItem;
275}
276
278{
279 Q_Q(const QMenu);
280 return q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, nullptr, q);
281}
282
283// Windows and KDE allow menus to cover the taskbar, while GNOME and macOS
284// don't. Torn-off menus are again different
286{
287 return !tornoff && QStylePrivate::useFullScreenForPopup();
288}
289
290QRect QMenuPrivate::popupGeometry(QScreen *screen) const
291{
292 Q_Q(const QMenu);
293 if (screen == nullptr
294#if QT_CONFIG(graphicsview)
295 && q->graphicsProxyWidget() == nullptr
296#endif
297 ) {
298 screen = q->isVisible() ? q->screen() : popupScreen.data();
299 }
300 if (useFullScreenForPopup())
301 return screen ? screen->geometry()
302 : QWidgetPrivate::screenGeometry(q);
303 return screen ? screen->availableGeometry()
304 : QWidgetPrivate::availableScreenGeometry(q);
305}
306
308{
309 QList<QPointer<QWidget>> ret;
310 for(QWidget *widget = causedPopup.widget; widget; ) {
311 ret.append(widget);
312 if (QTornOffMenu *qtmenu = qobject_cast<QTornOffMenu*>(widget))
313 ret += qtmenu->d_func()->causedStack;
314 if (QMenu *qmenu = qobject_cast<QMenu*>(widget))
315 widget = qmenu->d_func()->causedPopup.widget;
316 else
317 break;
318 }
319 return ret;
320}
321
323{
324#if QT_CONFIG(menubar)
325 return qobject_cast<const QMenuBar *>(topCausedWidget()) == nullptr;
326#else
327 return true;
328#endif
329}
330
332{
333 updateActionRects(popupGeometry());
334}
335
336void QMenuPrivate::updateActionRects(const QRect &screen) const
337{
338 Q_Q(const QMenu);
339 if (!itemsDirty)
340 return;
341
342 q->ensurePolished();
343
344 //let's reinitialize the buffer
345 actionRects.resize(actions.size());
346 actionRects.fill(QRect());
347
348 int lastVisibleAction = getLastVisibleAction();
349
350 QStyle *style = q->style();
351 QStyleOption opt;
352 opt.initFrom(q);
353 const int hmargin = style->pixelMetric(QStyle::PM_MenuHMargin, &opt, q),
354 vmargin = style->pixelMetric(QStyle::PM_MenuVMargin, &opt, q),
355 icone = style->pixelMetric(QStyle::PM_SmallIconSize, &opt, q);
356 const int fw = style->pixelMetric(QStyle::PM_MenuPanelWidth, &opt, q);
357 const int deskFw = style->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, &opt, q);
358 const int tearoffHeight = tearoff ? style->pixelMetric(QStyle::PM_MenuTearoffHeight, &opt, q) : 0;
359 const int base_y = vmargin + fw + topmargin + (scroll ? scroll->scrollOffset : 0) + tearoffHeight;
360 const int column_max_y = screen.height() - 2 * deskFw - (vmargin + bottommargin + fw);
361 int max_column_width = 0;
362 int y = base_y;
363
364 //for compatibility now - will have to refactor this away
365 tabWidth = 0;
366 maxIconWidth = 0;
367 hasCheckableItems = false;
368 ncols = 1;
369
370 for (int i = 0; i < actions.size(); ++i) {
371 QAction *action = actions.at(i);
372 if (action->isSeparator() || !action->isVisible() || widgetItems.contains(action))
373 continue;
374 //..and some members
375 hasCheckableItems |= action->isCheckable();
376 QIcon is = action->icon();
377 if (!is.isNull()) {
378 maxIconWidth = qMax<uint>(maxIconWidth, icone + 4);
379 }
380 }
381
382 //calculate size
383 QFontMetrics qfm = q->fontMetrics();
384 bool previousWasSeparator = true; // this is true to allow removing the leading separators
385#if QT_CONFIG(shortcut)
386 const bool contextMenu = isContextMenu();
387#endif
388 const bool menuSupportsSections = q->style()->styleHint(QStyle::SH_Menu_SupportsSections, nullptr, q);
389 for(int i = 0; i <= lastVisibleAction; i++) {
390 QAction *action = actions.at(i);
391 const bool isSection = action->isSeparator() && (!action->text().isEmpty() || !action->icon().isNull());
392 const bool isPlainSeparator = (isSection && !menuSupportsSections)
393 || (action->isSeparator() && !isSection);
394
395 if (!action->isVisible() ||
396 (collapsibleSeparators && previousWasSeparator && isPlainSeparator))
397 continue; // we continue, this action will get an empty QRect
398
399 previousWasSeparator = isPlainSeparator;
400
401 //let the style modify the above size..
402 QStyleOptionMenuItem opt;
403 q->initStyleOption(&opt, action);
404 const QFontMetrics &fm = opt.fontMetrics;
405
406 QSize sz;
407 if (QWidget *w = widgetItems.value(action)) {
408 sz = w->sizeHint().expandedTo(w->minimumSize()).expandedTo(w->minimumSizeHint()).boundedTo(w->maximumSize());
409 } else {
410 //calc what I think the size is..
411 if (action->isSeparator() && action->text().isEmpty()) {
412 sz = QSize(2, 2);
413 } else {
414 QString s = action->text();
415 qsizetype t = s.indexOf(u'\t');
416 if (t != -1) {
417 tabWidth = qMax(int(tabWidth), qfm.horizontalAdvance(s.mid(t+1)));
418 s = s.left(t);
419#if QT_CONFIG(shortcut)
420 } else if (action->isShortcutVisibleInContextMenu() || !contextMenu) {
421 QKeySequence seq = action->shortcut();
422 if (!seq.isEmpty())
423 tabWidth = qMax(int(tabWidth), qfm.horizontalAdvance(seq.toString(QKeySequence::NativeText)));
424#endif
425 }
426 sz.setWidth(fm.boundingRect(QRect(), Qt::TextSingleLine | Qt::TextShowMnemonic, s).width());
427 sz.setHeight(qMax(fm.height(), qfm.height()));
428
429 QIcon is = action->icon();
430 if (!is.isNull()) {
431 QSize is_sz = QSize(icone, icone);
432 if (is_sz.height() > sz.height())
433 sz.setHeight(is_sz.height());
434 }
435 }
436 sz = style->sizeFromContents(QStyle::CT_MenuItem, &opt, sz, q);
437 }
438
439
440 if (!sz.isEmpty()) {
441 max_column_width = qMax(max_column_width, sz.width());
442 //wrapping
443 if (!scroll && y + sz.height() > column_max_y) {
444 ncols++;
445 y = base_y;
446 } else {
447 y += sz.height();
448 }
449 //update the item
450 actionRects[i] = QRect(0, 0, sz.width(), sz.height());
451 }
452 }
453
454 max_column_width += tabWidth; //finally add in the tab width
455 if (!tornoff || scroll) { // exclude non-scrollable tear-off menu since the tear-off menu has a fixed size
456 const int sfcMargin = style->sizeFromContents(QStyle::CT_Menu, &opt, QSize(0, 0), q).width();
457 const int min_column_width = q->minimumWidth() - (sfcMargin + leftmargin + rightmargin + 2 * (fw + hmargin));
458 max_column_width = qMax(min_column_width, max_column_width);
459 }
460
461 //calculate position
462 int x = hmargin + fw + leftmargin;
463 y = base_y;
464
465 for(int i = 0; i < actions.size(); i++) {
466 QRect &rect = actionRects[i];
467 if (rect.isNull())
468 continue;
469 if (!scroll && y + rect.height() > column_max_y) {
470 x += max_column_width + hmargin;
471 y = base_y;
472 }
473 rect.translate(x, y); //move
474 rect.setWidth(max_column_width); //uniform width
475
476 //we need to update the widgets geometry
477 if (QWidget *widget = widgetItems.value(actions.at(i))) {
478 widget->setGeometry(rect);
479 widget->setVisible(actions.at(i)->isVisible());
480 }
481
482 y += rect.height();
483 }
484 itemsDirty = 0;
485}
486
488{
489 //let's try to get the last visible action
490 int lastVisibleAction = actions.size() - 1;
491 for (;lastVisibleAction >= 0; --lastVisibleAction) {
492 const QAction *action = actions.at(lastVisibleAction);
493 if (action->isVisible()) {
494 //removing trailing separators
495 if (action->isSeparator() && collapsibleSeparators)
496 continue;
497 break;
498 }
499 }
500 return lastVisibleAction;
501}
502
503
504QRect QMenuPrivate::actionRect(QAction *act) const
505{
506 int index = actions.indexOf(act);
507 if (index == -1)
508 return QRect();
509
511
512 //we found the action
513 return actionRects.at(index);
514}
515
517{
518 Q_Q(QMenu);
519 bool fadeMenus = q->style()->styleHint(QStyle::SH_Menu_FadeOutOnHide, nullptr, q);
520 if (!tornoff) {
521 QWidget *caused = causedPopup.widget;
522 hideMenu(q); //hide after getting causedPopup
523 while(caused) {
524#if QT_CONFIG(menubar)
525 if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) {
526 mb->d_func()->setCurrentAction(nullptr);
527 mb->d_func()->setKeyboardMode(false);
528 caused = nullptr;
529 } else
530#endif
531 if (QMenu *m = qobject_cast<QMenu*>(caused)) {
532 caused = m->d_func()->causedPopup.widget;
533 if (!m->d_func()->tornoff)
534 hideMenu(m);
535 if (!fadeMenus) // Mac doesn't clear the action until after hidden.
536 m->d_func()->setCurrentAction(nullptr);
537 } else { caused = nullptr;
538 }
539 }
540 }
541 setCurrentAction(nullptr);
542}
543
544void QMenuPrivate::hideMenu(QMenu *menu)
545{
546 if (!menu)
547 return;
548
549 // See two execs below. They may trigger an akward situation
550 // when 'menu' (also known as 'q' or 'this' in the many functions
551 // around) to become a dangling pointer if the loop manages
552 // to execute 'deferred delete' ... posted while executing
553 // this same loop. Not good!
554 struct Reposter : QObject
555 {
556 Reposter(QMenu *menu) : q(menu)
557 {
558 Q_ASSERT(q);
559 q->installEventFilter(this);
560 }
561 ~Reposter()
562 {
563 if (deleteLater)
564 q->deleteLater();
565 }
566 bool eventFilter(QObject *obj, QEvent *event) override
567 {
568 if (obj == q && event->type() == QEvent::DeferredDelete)
569 return deleteLater = true;
570
571 return QObject::eventFilter(obj, event);
572 }
573 QMenu *q = nullptr;
574 bool deleteLater = false;
575 };
576
577#if QT_CONFIG(effects)
578 // If deleteLater has been called and the event loop spins, while waiting
579 // for visual effects to happen, menu might become stale.
580 // To prevent a QSignalBlocker from restoring a stale object, block and restore signals manually.
581 QPointer<QMenu> stillAlive(menu);
582 const bool signalsBlocked = menu->signalsBlocked();
583 menu->blockSignals(true);
584
585 aboutToHide = true;
586 // Flash item which is about to trigger (if any).
587 if (menu && menu->style()->styleHint(QStyle::SH_Menu_FlashTriggeredItem, nullptr, stillAlive)
588 && currentAction && currentAction == actionAboutToTrigger
589 && menu->actions().contains(currentAction)) {
590 QEventLoop eventLoop;
591 QAction *activeAction = currentAction;
592
593 menu->setActiveAction(nullptr);
594 const Reposter deleteDeleteLate(menu);
595 QTimer::singleShot(60, &eventLoop, SLOT(quit()));
596 eventLoop.exec();
597
598 if (!stillAlive)
599 return;
600
601 // Select and wait 20 ms.
602 menu->setActiveAction(activeAction);
603 QTimer::singleShot(20, &eventLoop, SLOT(quit()));
604 eventLoop.exec();
605 }
606
607 aboutToHide = false;
608
609 if (stillAlive)
610 menu->blockSignals(signalsBlocked);
611 else
612 return;
613
614#endif // QT_CONFIG(effects)
615 if (activeMenu == menu)
616 activeMenu = nullptr;
617
618 menu->d_func()->causedPopup.action = nullptr;
619 menu->close();
620 menu->d_func()->causedPopup.widget = nullptr;
621}
622
624{
625 Q_Q(const QMenu);
626 if (causedPopup.widget) {
627 if (const QWidget *w = causedPopup.widget.data()) {
628 if (const QWidget *ww = w->window())
629 return ww->windowHandle();
630 }
631 }
632
633 if (const QWidget *parent = q->nativeParentWidget()) {
634 if (parent->windowHandle())
635 return parent->windowHandle();
636 }
637
638 if (const QWindow *w = q->windowHandle()) {
639 if (w->transientParent())
640 return w->transientParent();
641 }
642
643 return nullptr;
644}
645
646void QMenuPrivate::popupAction(QAction *action, int delay, bool activateFirst)
647{
648 Q_Q(QMenu);
649 if (action) {
650 if (action->isEnabled()) {
651 if (!delay)
652 q->internalDelayedPopup();
653 else if (action->menu() && !action->menu()->isVisible())
654 delayState.start(delay, action);
655 else if (!action->menu())
656 delayState.stop();
657 if (activateFirst && action->menu())
658 action->menu()->d_func()->setFirstActionActive();
659 }
660 } else if (QMenu *menu = activeMenu) { //hide the current item
661 hideMenu(menu);
662 }
663}
664
666{
667 Q_Q(QMenu);
668 QAction *current = currentAction;
669 if (current && (!current->isEnabled() || current->menu() || current->isSeparator()))
670 current = nullptr;
671 for(QWidget *caused = q; caused;) {
672 if (QMenu *m = qobject_cast<QMenu*>(caused)) {
673 caused = m->d_func()->causedPopup.widget;
674 if (m->d_func()->eventLoop)
675 m->d_func()->syncAction = current; // synchronous operation
676 } else {
677 break;
678 }
679 }
680}
681
682
684{
686 for(int i = 0, saccum = 0; i < actions.size(); i++) {
687 const QRect &rect = actionRects.at(i);
688 if (rect.isNull())
689 continue;
690 if (scroll && scroll->scrollFlags & QMenuScroller::ScrollUp) {
691 saccum -= rect.height();
692 if (saccum > scroll->scrollOffset - scrollerHeight())
693 continue;
694 }
695 QAction *act = actions.at(i);
696 if (considerAction(act)) {
697 setCurrentAction(act);
698 break;
699 }
700 }
701}
702
703// popup == -1 means do not popup, 0 means immediately, others mean use a timer
704void QMenuPrivate::setCurrentAction(QAction *action, int popup, SelectionReason reason,
705 SelectionDirection direction, bool activateFirst)
706{
707 Q_Q(QMenu);
709
710 if (!considerAction(action))
711 action = nullptr;
712
713 // Reselect the currently active action in case mouse moved over other menu items when
714 // moving from sub menu action to sub menu (QTBUG-20094).
715 if (reason != SelectedFromKeyboard) {
716 if (QMenu *menu = qobject_cast<QMenu*>(causedPopup.widget)) {
717 if (causedPopup.action && menu->d_func()->activeMenu == q)
718 // Reselect parent menu action only if mouse is over a menu and parent menu action is not already selected (QTBUG-47987)
719 if (hasReceievedEnter && menu->d_func()->currentAction != causedPopup.action)
720 menu->d_func()->setCurrentAction(causedPopup.action, 0, reason, direction, false);
721 }
722 }
723
724 if (currentAction)
725 q->update(actionRect(currentAction));
726
727 QMenu *hideActiveMenu = activeMenu;
728 QAction *previousAction = currentAction;
729
730 currentAction = action;
731 if (action) {
732 if (!action->isSeparator()) {
733 activateAction(action, QAction::Hover);
734 if (popup != -1) {
735 // if the menu is visible then activate the required action,
736 // otherwise we just mark the action as currentAction
737 // and activate it when the menu will be popuped.
738 if (q->isVisible())
739 popupAction(currentAction, popup, activateFirst);
740 }
741 q->update(actionRect(action));
742
743 if (reason == SelectedFromKeyboard) {
744 QWidget *widget = widgetItems.value(action);
745 if (widget) {
746 if (widget->focusPolicy() != Qt::NoFocus)
747 widget->setFocus(direction == QMenuPrivate::SelectionDirection::Up ? Qt::BacktabFocusReason : Qt::TabFocusReason);
748 } else {
749 //when the action has no QWidget, the QMenu itself should
750 // get the focus
751 // Since the menu is a pop-up, it uses the popup reason.
752 if (!q->hasFocus()) {
753 q->setFocus(Qt::PopupFocusReason);
754 }
755 }
756 }
757 }
758#if QT_CONFIG(statustip)
759 } else if (previousAction) {
760 previousAction->d_func()->showStatusText(topCausedWidget(), QString());
761#endif
762 }
763 if (hideActiveMenu && previousAction != currentAction) {
764 if (popup == -1) {
765#if QT_CONFIG(effects)
766 // kill any running effect
767 qFadeEffect(nullptr);
768 qScrollEffect(nullptr);
769#endif
770 hideMenu(hideActiveMenu);
771 } else if (!currentAction || !currentAction->menu()) {
772 sloppyState.startTimerIfNotRunning();
773 }
774 }
775}
776
777void QMenuSloppyState::reset()
778{
779 m_enabled = false;
780 m_first_mouse = true;
781 m_init_guard = false;
782 m_use_reset_action = true;
783 m_uni_dir_discarded_count = 0;
784 m_time.stop();
785 m_reset_action = nullptr;
786 m_origin_action = nullptr;
787 m_action_rect = QRect();
788 m_previous_point = QPointF();
789 if (m_sub_menu) {
790 QMenuPrivate::get(m_sub_menu)->sloppyState.m_parent = nullptr;
791 m_sub_menu = nullptr;
792 }
793}
795{
796 QMenuPrivate *menuPriv = QMenuPrivate::get(m_menu);
797
798 if (m_discard_state_when_entering_parent && m_sub_menu == menuPriv->activeMenu) {
799 menuPriv->hideMenu(m_sub_menu);
800 reset();
801 }
802 if (m_parent)
803 m_parent->childEnter();
804}
805
807{
809 if (m_parent)
810 m_parent->childEnter();
811}
812
814{
815 if (!m_dont_start_time_on_leave) {
816 if (m_parent)
817 m_parent->childLeave();
819 }
820}
821
823{
824 if (m_enabled && !QMenuPrivate::get(m_menu)->hasReceievedEnter) {
826 if (m_parent)
827 m_parent->childLeave();
828 }
829}
830
831void QMenuSloppyState::setSubMenuPopup(const QRect &actionRect, QAction *resetAction, QMenu *subMenu)
832{
833 m_enabled = true;
834 m_init_guard = true;
835 m_use_reset_action = true;
836 m_time.stop();
837 m_action_rect = actionRect;
838 if (m_sub_menu)
839 QMenuPrivate::get(m_sub_menu)->sloppyState.m_parent = nullptr;
840 m_sub_menu = subMenu;
841 QMenuPrivate::get(subMenu)->sloppyState.m_parent = this;
842 m_reset_action = resetAction;
843 m_origin_action = resetAction;
844}
845
847{
848 return m_parent && m_parent->m_menu && QMenuPrivate::get(m_parent->m_menu)->delayState.timer.isActive();
849}
850
852{
853public:
854 ResetOnDestroy(QMenuSloppyState *sloppyState, bool *guard)
855 : toReset(sloppyState)
856 , guard(guard)
857 {
858 *guard = false;
859 }
860
862 {
863 if (!*guard)
864 toReset->reset();
865 }
866
868 bool *guard;
869};
870
872{
873 QMenuPrivate *menu_priv = QMenuPrivate::get(m_menu);
874
875 bool reallyHasMouse = menu_priv->hasReceievedEnter;
876 if (!reallyHasMouse) {
877 // Check whether the menu really has a mouse, because only active popup
878 // menu gets the enter/leave events. Currently Cocoa is an exception.
879 const QPoint lastCursorPos = QGuiApplicationPrivate::lastCursorPosition.toPoint();
880 reallyHasMouse = m_menu->frameGeometry().contains(lastCursorPos);
881 }
882
883 if (menu_priv->currentAction == m_reset_action
884 && reallyHasMouse
885 && (menu_priv->currentAction
886 && menu_priv->currentAction->menu() == menu_priv->activeMenu)) {
887 return;
888 }
889
890 ResetOnDestroy resetState(this, &m_init_guard);
891
892 if (hasParentActiveDelayTimer() || !m_menu->isVisible())
893 return;
894
895 if (m_sub_menu)
896 menu_priv->hideMenu(m_sub_menu);
897
898 if (reallyHasMouse) {
899 if (m_use_reset_action)
900 menu_priv->setCurrentAction(m_reset_action, 0);
901 } else {
902 menu_priv->setCurrentAction(nullptr, 0);
903 }
904}
905
906//return the top causedPopup.widget that is not a QMenu
908{
909 QWidget* top = causedPopup.widget;
910 while (QMenu* m = qobject_cast<QMenu *>(top))
911 top = m->d_func()->causedPopup.widget;
912 return top;
913}
914
915QAction *QMenuPrivate::actionAt(QPoint p) const
916{
917 if (!rect().contains(p)) //sanity check
918 return nullptr;
919
920 for(int i = 0; i < actionRects.size(); i++) {
921 if (actionRects.at(i).contains(p))
922 return actions.at(i);
923 }
924 return nullptr;
925}
926
927void QMenuPrivate::setOverrideMenuAction(QAction *a)
928{
929 Q_Q(QMenu);
930 QObject::disconnect(menuAction, SIGNAL(destroyed()), q, SLOT(_q_overrideMenuActionDestroyed()));
931 if (a) {
932 menuAction = a;
933 QObject::connect(a, SIGNAL(destroyed()), q, SLOT(_q_overrideMenuActionDestroyed()));
934 } else { //we revert back to the default action created by the QMenu itself
936 }
937}
938
943
945{
946 Q_Q(QMenu);
947 //we need to mimic the cause of the popup's layout direction
948 //to allow setting it on a mainwindow for example
949 //we call setLayoutDirection_helper to not overwrite a user-defined value
950 if (!q->testAttribute(Qt::WA_SetLayoutDirection)) {
951 if (QWidget *w = causedPopup.widget)
952 setLayoutDirection_helper(w->layoutDirection());
953 else if (QWidget *w = q->parentWidget())
954 setLayoutDirection_helper(w->layoutDirection());
955 else
956 setLayoutDirection_helper(QGuiApplication::layoutDirection());
957 }
958}
959
960void QMenuPrivate::drawScroller(QPainter *painter, QMenuPrivate::ScrollerTearOffItem::Type type, const QRect &rect)
961{
962 if (!painter || rect.isEmpty())
963 return;
964
965 if (!scroll || !(scroll->scrollFlags & (QMenuPrivate::QMenuScroller::ScrollUp
967 return;
968
969 Q_Q(QMenu);
970 QStyleOptionMenuItem menuOpt;
971 menuOpt.initFrom(q);
972 menuOpt.state = QStyle::State_None;
973 menuOpt.checkType = QStyleOptionMenuItem::NotCheckable;
974 menuOpt.maxIconWidth = 0;
975 menuOpt.reservedShortcutWidth = 0;
976 menuOpt.rect = rect;
977 menuOpt.menuItemType = QStyleOptionMenuItem::Scroller;
978 menuOpt.state |= QStyle::State_Enabled;
979 if (type == QMenuPrivate::ScrollerTearOffItem::ScrollDown)
980 menuOpt.state |= QStyle::State_DownArrow;
981
982 painter->setClipRect(menuOpt.rect);
983 q->style()->drawControl(QStyle::CE_MenuScroller, &menuOpt, painter, q);
984}
985
986void QMenuPrivate::drawTearOff(QPainter *painter, const QRect &rect)
987{
988 if (!painter || rect.isEmpty())
989 return;
990
991 if (!tearoff)
992 return;
993
994 Q_Q(QMenu);
995 QStyleOptionMenuItem menuOpt;
996 menuOpt.initFrom(q);
997 menuOpt.state = QStyle::State_None;
998 menuOpt.checkType = QStyleOptionMenuItem::NotCheckable;
999 menuOpt.maxIconWidth = 0;
1000 menuOpt.reservedShortcutWidth = 0;
1001 menuOpt.rect = rect;
1002 menuOpt.menuItemType = QStyleOptionMenuItem::TearOff;
1003 if (tearoffHighlighted)
1004 menuOpt.state |= QStyle::State_Selected;
1005
1006 painter->setClipRect(menuOpt.rect);
1007 q->style()->drawControl(QStyle::CE_MenuTearoff, &menuOpt, painter, q);
1008}
1009
1011{
1012 Q_Q(const QMenu);
1013 QStyle *style = q->style();
1014 QStyleOption opt(0);
1015 opt.initFrom(q);
1016 const int hmargin = style->pixelMetric(QStyle::PM_MenuHMargin, &opt, q);
1017 const int vmargin = style->pixelMetric(QStyle::PM_MenuVMargin, &opt, q);
1018 const int fw = style->pixelMetric(QStyle::PM_MenuPanelWidth, &opt, q);
1019 return (q->rect().adjusted(hmargin + fw + leftmargin, vmargin + fw + topmargin,
1020 -(hmargin + fw + rightmargin), -(vmargin + fw + bottommargin)));
1021}
1022
1023QMenuPrivate::ScrollerTearOffItem::ScrollerTearOffItem(QMenuPrivate::ScrollerTearOffItem::Type type, QMenuPrivate *mPrivate, QWidget *parent, Qt::WindowFlags f)
1024 : QWidget(parent, f), menuPrivate(mPrivate), scrollType(type)
1025{
1026 if (parent)
1027 setMouseTracking(parent->style()->styleHint(QStyle::SH_Menu_MouseTracking, nullptr, parent));
1028}
1029
1030void QMenuPrivate::ScrollerTearOffItem::paintEvent(QPaintEvent *e)
1031{
1032 if (!e->rect().intersects(rect()))
1033 return;
1034
1035 QPainter p(this);
1036 QWidget *parent = parentWidget();
1037
1038 //paint scroll up / down arrows
1039 menuPrivate->drawScroller(&p, scrollType, QRect(0, 0, width(), menuPrivate->scrollerHeight()));
1040 //paint the tear off
1042 QRect rect(0, 0, width(), parent->style()->pixelMetric(QStyle::PM_MenuTearoffHeight, nullptr, parent));
1043 if (menuPrivate->scroll && menuPrivate->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
1044 rect.translate(0, menuPrivate->scrollerHeight());
1045 menuPrivate->drawTearOff(&p, rect);
1046 }
1047}
1048
1049void QMenuPrivate::ScrollerTearOffItem::updateScrollerRects(const QRect &rect)
1050{
1051 if (rect.isEmpty())
1052 setVisible(false);
1053 else {
1054 setGeometry(rect);
1055 raise();
1056 setVisible(true);
1057 }
1058}
1059
1060
1061/*!
1062 Returns the action associated with this menu.
1063*/
1064QAction *QMenu::menuAction() const
1065{
1066 return d_func()->menuAction;
1067}
1068
1069/*!
1070 \fn static QMenu *QMenu::menuInAction(const QAction *action)
1071
1072 Returns the menu contained by \a action, or \nullptr if \a action does not
1073 contain a menu.
1074
1075 In widget applications, actions that contain menus can be used to create menu
1076 items with submenus, or inserted into toolbars to create buttons with popup menus.
1077*/
1078
1079/*!
1080 \property QMenu::title
1081 \brief The title of the menu
1082
1083 This is equivalent to the QAction::text property of the menuAction().
1084
1085 By default, this property contains an empty string.
1086*/
1087QString QMenu::title() const
1088{
1089 return d_func()->menuAction->text();
1090}
1091
1092void QMenu::setTitle(const QString &text)
1093{
1094 d_func()->menuAction->setText(text);
1095}
1096
1097/*!
1098 \property QMenu::icon
1099
1100 \brief The icon of the menu
1101
1102 This is equivalent to the QAction::icon property of the menuAction().
1103
1104 By default, if no icon is explicitly set, this property contains a null icon.
1105*/
1106QIcon QMenu::icon() const
1107{
1108 return d_func()->menuAction->icon();
1109}
1110
1111void QMenu::setIcon(const QIcon &icon)
1112{
1113 d_func()->menuAction->setIcon(icon);
1114}
1115
1116
1117//actually performs the scrolling
1118void QMenuPrivate::scrollMenu(QAction *action, QMenuScroller::ScrollLocation location, bool active)
1119{
1120 Q_Q(QMenu);
1121 if (!scroll || !scroll->scrollFlags)
1122 return;
1124 int newOffset = 0;
1125 const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp) ? scrollerHeight() : 0;
1126 const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollerHeight() : 0;
1127 const int vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, nullptr, q);
1128 const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, nullptr, q);
1129
1130 if (location == QMenuScroller::ScrollTop) {
1131 for(int i = 0, saccum = 0; i < actions.size(); i++) {
1132 if (actions.at(i) == action) {
1133 newOffset = topScroll - saccum;
1134 break;
1135 }
1136 saccum += actionRects.at(i).height();
1137 }
1138 } else {
1139 for(int i = 0, saccum = 0; i < actions.size(); i++) {
1140 saccum += actionRects.at(i).height();
1141 if (actions.at(i) == action) {
1142 if (location == QMenuScroller::ScrollCenter)
1143 newOffset = ((q->height() / 2) - botScroll) - (saccum - topScroll);
1144 else
1145 newOffset = (q->height() - botScroll) - saccum;
1146 break;
1147 }
1148 }
1149 if (newOffset)
1150 newOffset -= fw * 2;
1151 }
1152
1153 //figure out which scroll flags
1154 uint newScrollFlags = QMenuScroller::ScrollNone;
1155 if (newOffset < 0) //easy and cheap one
1156 newScrollFlags |= QMenuScroller::ScrollUp;
1157 int saccum = newOffset;
1158 for(int i = 0; i < actionRects.size(); i++) {
1159 saccum += actionRects.at(i).height();
1160 if (saccum > q->height()) {
1161 newScrollFlags |= QMenuScroller::ScrollDown;
1162 break;
1163 }
1164 }
1165
1166 if (!(newScrollFlags & QMenuScroller::ScrollDown) && (scroll->scrollFlags & QMenuScroller::ScrollDown)) {
1167 newOffset = q->height() - (saccum - newOffset) - fw*2 - vmargin - topmargin - bottommargin; //last item at bottom
1168 if (tearoff)
1169 newOffset -= q->style()->pixelMetric(QStyle::PM_MenuTearoffHeight, nullptr, q);
1170 }
1171
1172 if (!(newScrollFlags & QMenuScroller::ScrollUp) && (scroll->scrollFlags & QMenuScroller::ScrollUp)) {
1173 newOffset = 0; //first item at top
1174 }
1175
1176 if (newScrollFlags & QMenuScroller::ScrollUp)
1177 newOffset -= vmargin;
1178
1179 QRect screen = popupGeometry();
1180 const int desktopFrame = q->style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, nullptr, q);
1181 if (q->height() < screen.height()-(desktopFrame*2)-1) {
1182 QRect geom = q->geometry();
1183 if (newOffset > scroll->scrollOffset && (scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollUp)) { //scroll up
1184 const int newHeight = geom.height()-(newOffset-scroll->scrollOffset);
1185 if (newHeight > geom.height())
1186 geom.setHeight(newHeight);
1187 } else if (scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollDown) {
1188 int newTop = geom.top() + (newOffset-scroll->scrollOffset);
1189 if (newTop < desktopFrame+screen.top())
1190 newTop = desktopFrame+screen.top();
1191 if (newTop < geom.top()) {
1192 geom.setTop(newTop);
1193 newOffset = 0;
1194 newScrollFlags &= ~QMenuScroller::ScrollUp;
1195 }
1196 }
1197 if (geom.bottom() > screen.bottom() - desktopFrame)
1198 geom.setBottom(screen.bottom() - desktopFrame);
1199 if (geom.top() < desktopFrame+screen.top())
1200 geom.setTop(desktopFrame+screen.top());
1201 if (geom != q->geometry()) {
1202#if 0
1203 if (newScrollFlags & QMenuScroller::ScrollDown &&
1204 q->geometry().top() - geom.top() >= -newOffset)
1205 newScrollFlags &= ~QMenuScroller::ScrollDown;
1206#endif
1207 q->setGeometry(geom);
1208 }
1209 }
1210
1211 //actually update flags
1212 const int delta = qMin(0, newOffset) - scroll->scrollOffset; //make sure the new offset is always negative
1213 if (!itemsDirty && delta) {
1214 //we've scrolled so we need to update the action rects
1215 for (int i = 0; i < actionRects.size(); ++i) {
1216 QRect &current = actionRects[i];
1217 current.moveTop(current.top() + delta);
1218
1219 //we need to update the widgets geometry
1220 if (QWidget *w = widgetItems.value(actions.at(i)))
1221 w->setGeometry(current);
1222 }
1223 }
1224 scroll->scrollOffset += delta;
1225 scroll->scrollFlags = newScrollFlags;
1226 if (active)
1227 setCurrentAction(action);
1228
1229 q->update(); //issue an update so we see all the new state..
1230}
1231
1233{
1235 if (location == QMenuScroller::ScrollBottom) {
1236 for(int i = actions.size()-1; i >= 0; --i) {
1237 if (actionRects.at(i).isNull())
1238 continue;
1239 QAction *act = actions.at(i);
1240 if (considerAction(act)) {
1241 if (scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown)
1242 scrollMenu(act, QMenuPrivate::QMenuScroller::ScrollBottom, active);
1243 else if (active)
1245 break;
1246 }
1247 }
1248 } else if (location == QMenuScroller::ScrollTop) {
1249 for(int i = 0; i < actions.size(); ++i) {
1250 if (actionRects.at(i).isNull())
1251 continue;
1252 QAction *act = actions.at(i);
1253 if (considerAction(act)) {
1254 if (scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
1255 scrollMenu(act, QMenuPrivate::QMenuScroller::ScrollTop, active);
1256 else if (active)
1258 break;
1259 }
1260 }
1261 }
1262}
1263
1264//only directional
1265void QMenuPrivate::scrollMenu(QMenuScroller::ScrollDirection direction, bool page, bool active)
1266{
1267 Q_Q(QMenu);
1268 if (!scroll || !(scroll->scrollFlags & direction)) //not really possible...
1269 return;
1271 const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp) ? scrollerHeight() : 0;
1272 const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollerHeight() : 0;
1273 const int vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, nullptr, q);
1274 const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, nullptr, q);
1275 const int offset = topScroll ? topScroll-vmargin : 0;
1276 if (direction == QMenuScroller::ScrollUp) {
1277 for(int i = 0, saccum = 0; i < actions.size(); i++) {
1278 saccum -= actionRects.at(i).height();
1279 if (saccum <= scroll->scrollOffset-offset) {
1280 scrollMenu(actions.at(i), page ? QMenuScroller::ScrollBottom : QMenuScroller::ScrollTop, active);
1281 break;
1282 }
1283 }
1284 } else if (direction == QMenuScroller::ScrollDown) {
1285 bool scrolled = false;
1286 for(int i = 0, saccum = 0; i < actions.size(); i++) {
1287 const int iHeight = actionRects.at(i).height();
1288 saccum -= iHeight;
1289 if (saccum <= scroll->scrollOffset-offset) {
1290 const int scrollerArea = q->height() - botScroll - fw*2;
1291 int visible = (scroll->scrollOffset-offset) - saccum;
1292 for(i++ ; i < actions.size(); i++) {
1293 visible += actionRects.at(i).height();
1294 if (visible > scrollerArea - topScroll) {
1295 scrolled = true;
1296 scrollMenu(actions.at(i), page ? QMenuScroller::ScrollTop : QMenuScroller::ScrollBottom, active);
1297 break;
1298 }
1299 }
1300 break;
1301 }
1302 }
1303 if (!scrolled) {
1304 scroll->scrollFlags &= ~QMenuScroller::ScrollDown;
1305 q->update();
1306 }
1307 }
1308}
1309
1310/* This is poor-mans eventfilters. This avoids the use of
1311 eventFilter (which can be nasty for users of QMenuBar's). */
1312bool QMenuPrivate::mouseEventTaken(QMouseEvent *e)
1313{
1314 Q_Q(QMenu);
1315 QPoint pos = q->mapFromGlobal(e->globalPosition().toPoint());
1316
1317 QStyle *style = q->style();
1318 QStyleOption opt(0);
1319 opt.initFrom(q);
1320 const int hmargin = style->pixelMetric(QStyle::PM_MenuHMargin, &opt, q);
1321 const int vmargin = style->pixelMetric(QStyle::PM_MenuVMargin, &opt, q);
1322 const int fw = style->pixelMetric(QStyle::PM_MenuPanelWidth, &opt, q);
1323
1324 if (scroll && !activeMenu) { //let the scroller "steal" the event
1325 bool isScroll = false;
1326 if (pos.x() >= 0 && pos.x() < q->width()) {
1327 for (int dir = QMenuScroller::ScrollUp; dir <= QMenuScroller::ScrollDown; dir = dir << 1) {
1328 if (scroll->scrollFlags & dir) {
1329 if (dir == QMenuScroller::ScrollUp)
1330 isScroll = (pos.y() <= scrollerHeight() + fw + vmargin + topmargin);
1331 else if (dir == QMenuScroller::ScrollDown)
1332 isScroll = (pos.y() >= q->height() - scrollerHeight() - fw - vmargin - bottommargin);
1333 if (isScroll) {
1334 scroll->scrollDirection = dir;
1335 break;
1336 }
1337 }
1338 }
1339 }
1340 if (isScroll) {
1341 scroll->scrollTimer.start(50, q);
1342 return true;
1343 } else {
1344 scroll->scrollTimer.stop();
1345 }
1346 }
1347
1348 if (tearoff) { //let the tear off thingie "steal" the event..
1349 QRect tearRect(leftmargin + hmargin + fw, topmargin + vmargin + fw, q->width() - fw * 2 - hmargin * 2 -leftmargin - rightmargin,
1350 q->style()->pixelMetric(QStyle::PM_MenuTearoffHeight, &opt, q));
1351 if (scroll && scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
1352 tearRect.translate(0, scrollerHeight());
1353 q->update(tearRect);
1354 if (tearRect.contains(pos) && hasMouseMoved(e->globalPosition().toPoint())) {
1355 setCurrentAction(nullptr);
1357 if (e->type() == QEvent::MouseButtonRelease) {
1358 if (!tornPopup)
1359 tornPopup = new QTornOffMenu(q);
1360 tornPopup->setGeometry(q->geometry());
1361 tornPopup->show();
1363 }
1364 return true;
1365 }
1367 }
1368
1369 if (q->frameGeometry().contains(e->globalPosition().toPoint()))
1370 return false; //otherwise if the event is in our rect we want it..
1371
1372 for(QWidget *caused = causedPopup.widget; caused;) {
1373 bool passOnEvent = false;
1374 QWidget *next_widget = nullptr;
1375 QPointF cpos = caused->mapFromGlobal(e->globalPosition());
1376#if QT_CONFIG(menubar)
1377 if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) {
1378 passOnEvent = mb->rect().contains(cpos.toPoint());
1379 } else
1380#endif
1381 if (QMenu *m = qobject_cast<QMenu*>(caused)) {
1382 passOnEvent = m->rect().contains(cpos.toPoint());
1383 next_widget = m->d_func()->causedPopup.widget;
1384 }
1385 if (passOnEvent) {
1386 if (e->type() != QEvent::MouseButtonRelease || mouseDown == caused) {
1387 QMouseEvent new_e(e->type(), cpos, caused->mapTo(caused->topLevelWidget(), cpos), e->globalPosition(),
1388 e->button(), e->buttons(), e->modifiers(),
1389 e->source(), e->pointingDevice());
1390 QCoreApplication::sendEvent(caused, &new_e);
1391 return true;
1392 }
1393 }
1394 caused = next_widget;
1395 if (!caused)
1396 sloppyState.leave(); // Start timers
1397 }
1398 return false;
1399}
1400
1401void QMenuPrivate::activateCausedStack(const QList<QPointer<QWidget>> &causedStack, QAction *action,
1402 QAction::ActionEvent action_e, bool self)
1403{
1404 Q_Q(QMenu);
1405 // can't use QScopedValueRollback here
1406 const bool activationRecursionGuardReset = activationRecursionGuard;
1408 QPointer<QMenu> guard(q);
1409 if (self)
1410 action->activate(action_e);
1411 if (!guard)
1412 return;
1413 auto boolBlocker = qScopeGuard([this, activationRecursionGuardReset]{
1414 activationRecursionGuard = activationRecursionGuardReset;
1415 });
1416
1417 for(int i = 0; i < causedStack.size(); ++i) {
1418 QPointer<QWidget> widget = causedStack.at(i);
1419 if (!widget)
1420 continue;
1421 //fire
1422 if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) {
1423 widget = qmenu->d_func()->causedPopup.widget;
1424 if (action_e == QAction::Trigger) {
1425 emit qmenu->triggered(action);
1426 } else if (action_e == QAction::Hover) {
1427 emit qmenu->hovered(action);
1428 }
1429#if QT_CONFIG(menubar)
1430 } else if (QMenuBar *qmenubar = qobject_cast<QMenuBar*>(widget)) {
1431 if (action_e == QAction::Trigger) {
1432 emit qmenubar->triggered(action);
1433 } else if (action_e == QAction::Hover) {
1434 emit qmenubar->hovered(action);
1435 }
1436 break; //nothing more..
1437#endif
1438 }
1439 }
1440}
1441
1442void QMenuPrivate::activateAction(QAction *action, QAction::ActionEvent action_e, bool self)
1443{
1444 Q_Q(QMenu);
1445#if QT_CONFIG(whatsthis)
1446 bool inWhatsThisMode = QWhatsThis::inWhatsThisMode();
1447#endif
1448 if (!action || !q->isEnabled()
1449 || (action_e == QAction::Trigger
1450#if QT_CONFIG(whatsthis)
1451 && !inWhatsThisMode
1452#endif
1453 && (action->isSeparator() ||!action->isEnabled())))
1454 return;
1455
1456 /* I have to save the caused stack here because it will be undone after popup execution (ie in the hide).
1457 Then I iterate over the list to actually send the events. --Sam
1458 */
1459 const QList<QPointer<QWidget>> causedStack = calcCausedStack();
1460 if (action_e == QAction::Trigger) {
1461#if QT_CONFIG(whatsthis)
1462 if (!inWhatsThisMode)
1463 actionAboutToTrigger = action;
1464#endif
1465
1466 if (q->testAttribute(Qt::WA_DontShowOnScreen)) {
1468 } else {
1469 for(QWidget *widget = QApplication::activePopupWidget(); widget; ) {
1470 if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) {
1471 if (qmenu == q)
1473 widget = qmenu->d_func()->causedPopup.widget;
1474 } else {
1475 break;
1476 }
1477 }
1478 }
1479
1480#if QT_CONFIG(whatsthis)
1481 if (inWhatsThisMode) {
1482 QString s = action->whatsThis();
1483 if (s.isEmpty())
1484 s = whatsThis;
1485 QWhatsThis::showText(q->mapToGlobal(actionRect(action).center()), s, q);
1486 return;
1487 }
1488#endif
1489 }
1490
1491 QPointer<QMenu> thisGuard(q);
1492 activateCausedStack(causedStack, action, action_e, self);
1493 if (!thisGuard)
1494 return;
1495
1496 if (action_e == QAction::Hover) {
1497#if QT_CONFIG(accessibility)
1498 if (QAccessible::isActive()) {
1499 int actionIndex = indexOf(action);
1500 QAccessibleEvent focusEvent(q, QAccessible::Focus);
1501 focusEvent.setChild(actionIndex);
1502 QAccessible::updateAccessibility(&focusEvent);
1503 }
1504#endif
1505 action->showStatusText(topCausedWidget());
1506 } else {
1507 actionAboutToTrigger = nullptr;
1508 }
1509}
1510
1512{
1513 Q_Q(QMenu);
1514 if (QAction *action = qobject_cast<QAction *>(q->sender())) {
1515 QPointer<QAction> actionGuard = action;
1516 if (platformMenu && widgetItems.value(action))
1517 platformMenu->dismiss();
1518 emit q->triggered(action);
1519 if (!activationRecursionGuard && actionGuard) {
1520 //in case the action has not been activated by the mouse
1521 //we check the parent hierarchy
1522 QList<QPointer<QWidget>> list;
1523 for(QWidget *widget = q->parentWidget(); widget; ) {
1524 if (qobject_cast<QMenu*>(widget)
1525#if QT_CONFIG(menubar)
1526 || qobject_cast<QMenuBar*>(widget)
1527#endif
1528 ) {
1529 list.append(widget);
1530 widget = widget->parentWidget();
1531 } else {
1532 break;
1533 }
1534 }
1535 activateCausedStack(list, action, QAction::Trigger, false);
1536 // if a widget action fires, we need to hide the menu explicitly
1537 if (qobject_cast<QWidgetAction*>(action))
1539 }
1540 }
1541}
1542
1544{
1545 Q_Q(QMenu);
1546 if (QAction * action = qobject_cast<QAction *>(q->sender())) {
1547 emit q->hovered(action);
1548 }
1549}
1550
1552{
1553 Q_Q(QMenu);
1554
1555 emit q->aboutToShow();
1556
1557#ifdef Q_OS_MACOS
1558 if (platformMenu) {
1559 const auto actions = q->actions();
1560 for (QAction *action : actions) {
1561 if (QWidget *widget = widgetItems.value(action))
1562 if (widget->parent() == q) {
1563 QPlatformMenuItem *menuItem = platformMenu->menuItemForTag(reinterpret_cast<quintptr>(action));
1564 moveWidgetToPlatformItem(widget, menuItem);
1565 platformMenu->syncMenuItem(menuItem);
1566 }
1567 }
1568 }
1569#endif
1570}
1571
1572bool QMenuPrivate::hasMouseMoved(const QPoint &globalPos)
1573{
1574 //determines if the mouse has moved (ie its initial position has
1575 //changed by more than QApplication::startDragDistance()
1576 //or if there were at least 6 mouse motions)
1577 return motions > 6 ||
1578 QApplication::startDragDistance() < (mousePopupPos - globalPos).manhattanLength();
1579}
1580
1581
1582/*!
1583 Initialize \a option with the values from this menu and information from \a action. This method
1584 is useful for subclasses when they need a QStyleOptionMenuItem, but don't want
1585 to fill in all the information themselves.
1586
1587 \sa QStyleOption::initFrom(), QMenuBar::initStyleOption()
1588*/
1589void QMenu::initStyleOption(QStyleOptionMenuItem *option, const QAction *action) const
1590{
1591 if (!option || !action)
1592 return;
1593
1594 Q_D(const QMenu);
1595 option->initFrom(this);
1596 option->palette = palette();
1597 option->state = QStyle::State_None;
1598
1599 if (window()->isActiveWindow())
1600 option->state |= QStyle::State_Active;
1601 if (isEnabled() && action->isEnabled()
1602 && (!action->menu() || action->menu()->isEnabled()))
1603 option->state |= QStyle::State_Enabled;
1604 else
1605 option->palette.setCurrentColorGroup(QPalette::Disabled);
1606
1607 option->font = action->font().resolve(font());
1608 option->fontMetrics = QFontMetrics(option->font);
1609
1610 if (d->currentAction && d->currentAction == action && !d->currentAction->isSeparator()) {
1611 option->state |= QStyle::State_Selected
1612 | (QMenuPrivate::mouseDown ? QStyle::State_Sunken : QStyle::State_None);
1613 }
1614
1615 option->menuHasCheckableItems = d->hasCheckableItems;
1616 if (!action->isCheckable()) {
1617 option->checkType = QStyleOptionMenuItem::NotCheckable;
1618 } else {
1619 option->checkType = (action->actionGroup() && action->actionGroup()->isExclusive())
1620 ? QStyleOptionMenuItem::Exclusive : QStyleOptionMenuItem::NonExclusive;
1621 option->checked = action->isChecked();
1622 }
1623 if (action->menu())
1624 option->menuItemType = QStyleOptionMenuItem::SubMenu;
1625 else if (action->isSeparator())
1626 option->menuItemType = QStyleOptionMenuItem::Separator;
1627 else if (d->defaultAction == action)
1628 option->menuItemType = QStyleOptionMenuItem::DefaultItem;
1629 else
1630 option->menuItemType = QStyleOptionMenuItem::Normal;
1631 if (action->isIconVisibleInMenu())
1632 option->icon = action->icon();
1633 QString textAndAccel = action->text();
1634#ifndef QT_NO_SHORTCUT
1635 if ((action->isShortcutVisibleInContextMenu() || !d->isContextMenu())
1636 && textAndAccel.indexOf(u'\t') == -1) {
1637 QKeySequence seq = action->shortcut();
1638 if (!seq.isEmpty())
1639 textAndAccel += u'\t' + seq.toString(QKeySequence::NativeText);
1640 }
1641#endif
1642 option->text = textAndAccel;
1643 option->reservedShortcutWidth = d->tabWidth;
1644 option->maxIconWidth = d->maxIconWidth;
1645 option->menuRect = rect();
1646}
1647
1648/*!
1649 \class QMenu
1650 \brief The QMenu class provides a menu widget for use in menu
1651 bars, context menus, and other popup menus.
1652
1653 \ingroup mainwindow-classes
1654 \ingroup basicwidgets
1655 \inmodule QtWidgets
1656
1657 \image fusion-menu.png {Menu containing several action items}
1658
1659 A menu widget is a selection menu. It can be either a pull-down
1660 menu in a menu bar or a standalone context menu. Pull-down menus
1661 are shown by the menu bar when the user clicks on the respective
1662 item or presses the specified shortcut key. Use
1663 QMenuBar::addMenu() to insert a menu into a menu bar. Context
1664 menus are usually invoked by some special keyboard key or by
1665 right-clicking. They can be executed either asynchronously with
1666 popup() or synchronously with exec(). Menus can also be invoked in
1667 response to button presses; these are just like context menus
1668 except for how they are invoked.
1669
1670 \section1 Actions
1671
1672 A menu consists of a list of action items. Actions are added with
1673 the addAction(), addActions() and insertAction() functions. An action
1674 is represented vertically and rendered by QStyle. In addition, actions
1675 can have a text label, an optional icon drawn on the very left side,
1676 and shortcut key sequence such as "Ctrl+X".
1677
1678 The existing actions held by a menu can be found with actions().
1679
1680 There are four kinds of action items: separators, actions that
1681 show a submenu, widgets, and actions that perform an action.
1682 Separators are inserted with addSeparator(), submenus with addMenu(),
1683 and all other items are considered action items.
1684
1685 When inserting action items you usually specify a receiver and a
1686 slot. The receiver will be notified whenever the item is
1687 \l{QAction::triggered()}{triggered()}. In addition, QMenu provides
1688 two signals, triggered() and hovered(), which signal the
1689 QAction that was triggered from the menu.
1690
1691 You clear a menu with clear() and remove individual action items
1692 with removeAction().
1693
1694 A QMenu can also provide a tear-off menu. A tear-off menu is a
1695 top-level window that contains a copy of the menu. This makes it
1696 possible for the user to "tear off" frequently used menus and
1697 position them in a convenient place on the screen. If you want
1698 this functionality for a particular menu, insert a tear-off handle
1699 with setTearOffEnabled(). When using tear-off menus, bear in mind
1700 that the concept isn't typically used on Microsoft Windows so
1701 some users may not be familiar with it. Consider using a QToolBar
1702 instead.
1703
1704 Widgets can be inserted into menus with the QWidgetAction class.
1705 Instances of this class are used to hold widgets, and are inserted
1706 into menus with the addAction() overload that takes a QAction. If the
1707 QWidgetAction fires the triggered() signal, the menu will close.
1708
1709 \warning To make QMenu visible on the screen, exec() or popup() should be
1710 used instead of show() or setVisible(). To hide or disable the menu in the
1711 menubar, or in another menu to which it was added as a submenu, use the
1712 respective properties of menuAction() instead.
1713
1714 \section1 QMenu on \macos with Qt Build Against Cocoa
1715
1716 QMenu can be inserted only once in a menu/menubar. Subsequent insertions will
1717 have no effect or will result in a disabled menu item.
1718
1719 See the \l{mainwindows/menus}{Menus} example for an example of how
1720 to use QMenuBar and QMenu in your application.
1721
1722 \b{Important inherited functions:} addAction(), removeAction(), clear(),
1723 addSeparator(), and addMenu().
1724
1725 \sa QMenuBar, {Menus Example}
1726*/
1727
1728
1729/*!
1730 Constructs a menu with parent \a parent.
1731
1732 Although a popup menu is always a top-level widget, if a parent is
1733 passed the popup menu will be deleted when that parent is
1734 destroyed (as with any other QObject).
1735*/
1736QMenu::QMenu(QWidget *parent)
1737 : QWidget(*new QMenuPrivate, parent, Qt::Popup)
1738{
1739 Q_D(QMenu);
1740 d->init();
1741}
1742
1743/*!
1744 Constructs a menu with a \a title and a \a parent.
1745
1746 Although a popup menu is always a top-level widget, if a parent is
1747 passed the popup menu will be deleted when that parent is
1748 destroyed (as with any other QObject).
1749
1750 \sa title
1751*/
1752QMenu::QMenu(const QString &title, QWidget *parent)
1753 : QMenu(parent)
1754{
1755 Q_D(QMenu);
1756 d->menuAction->setText(title);
1757}
1758
1759/*! \internal
1760 */
1761QMenu::QMenu(QMenuPrivate &dd, QWidget *parent)
1762 : QWidget(dd, parent, Qt::Popup)
1763{
1764 Q_D(QMenu);
1765 d->init();
1766}
1767
1768/*!
1769 Destroys the menu.
1770*/
1771QMenu::~QMenu()
1772{
1773 Q_D(QMenu);
1774 if (!d->widgetItems.isEmpty()) { // avoid detach on shared null hash
1775 QHash<QAction *, QWidget *>::iterator it = d->widgetItems.begin();
1776 for (; it != d->widgetItems.end(); ++it) {
1777 if (QWidget *widget = it.value()) {
1778 QWidgetAction *action = static_cast<QWidgetAction *>(it.key());
1779 action->releaseWidget(widget);
1780 *it = 0;
1781 }
1782 }
1783 }
1784
1785 if (d->eventLoop)
1786 d->eventLoop->exit();
1787 hideTearOffMenu();
1788}
1789
1790#if QT_DEPRECATED_SINCE(6, 4)
1791/*!
1792 \fn QAction *QMenu::addAction(const QString &text, const QObject *receiver, const char* member, const QKeySequence &shortcut)
1793 \obsolete
1794
1795 Use \c{QWidget::addAction(text, shortcut, receiver, member)} instead.
1796*/
1797#if QT_CONFIG(shortcut)
1798QAction *QMenu::addAction(const QString &text, const QObject *receiver, const char* member, const QKeySequence &shortcut)
1799{
1800 return QWidget::addAction(text, shortcut, receiver, member);
1801}
1802#endif
1803
1804/*!
1805 \fn template<typename Functor> QAction *QMenu::addAction(const QString &text, Functor functor, const QKeySequence &shortcut)
1806
1807 \since 5.6
1808 \obsolete
1809
1810 Use QWidget::addAction(text, shortcut, functor) instead.
1811*/
1812
1813/*!
1814 \fn template<typename Functor> QAction *QMenu::addAction(const QString &text, const QObject *context, Functor functor, const QKeySequence &shortcut)
1815
1816 \since 5.6
1817 \obsolete
1818
1819 Use QWidget::addAction(text, shortcut, context, functor) instead.
1820*/
1821
1822/*!
1823 \fn template<typename Functor> QAction *QMenu::addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
1824
1825 \since 5.6
1826 \obsolete
1827
1828 Use QWidget::addAction(icon, text, shortcut, functor) instead.
1829*/
1830
1831/*!
1832 \fn template<typename Functor> QAction *QMenu::addAction(const QIcon &icon, const QString &text, const QObject *context, Functor functor, const QKeySequence &shortcut)
1833
1834 \since 5.6
1835 \obsolete
1836
1837 Use QWidget::addAction(icon, text, shortcut, context, functor) instead.
1838*/
1839
1840/*!
1841 \fn QAction *QMenu::addAction(const QIcon &icon, const QString &text, const QObject *receiver, const char* member, const QKeySequence &shortcut)
1842
1843 \obsolete
1844
1845 Use QWidget::addAction(icon, text, shortcut, receiver, member) instead.
1846*/
1847#if QT_CONFIG(shortcut)
1848QAction *QMenu::addAction(const QIcon &icon, const QString &text, const QObject *receiver,
1849 const char* member, const QKeySequence &shortcut)
1850{
1851 QAction *action = new QAction(icon, text, this);
1852 action->setShortcut(shortcut);
1853 QObject::connect(action, SIGNAL(triggered(bool)), receiver, member);
1854 addAction(action);
1855 return action;
1856}
1857#endif
1858#endif // QT_DEPRECATED_SINCE(6, 4)
1859
1860/*!
1861 This convenience function adds \a menu as a submenu to this menu.
1862 It returns \a menu's menuAction(). This menu does not take
1863 ownership of \a menu.
1864
1865 \sa QWidget::addAction(), QMenu::menuAction()
1866*/
1867QAction *QMenu::addMenu(QMenu *menu)
1868{
1869 QAction *action = menu->menuAction();
1870 addAction(action);
1871 return action;
1872}
1873
1874/*!
1875 Appends a new QMenu with \a title to the menu. The menu
1876 takes ownership of the menu. Returns the new menu.
1877
1878 \sa QWidget::addAction(), QMenu::menuAction()
1879*/
1880QMenu *QMenu::addMenu(const QString &title)
1881{
1882 QMenu *menu = new QMenu(title, this);
1883 addAction(menu->menuAction());
1884 return menu;
1885}
1886
1887/*!
1888 Appends a new QMenu with \a icon and \a title to the menu. The menu
1889 takes ownership of the menu. Returns the new menu.
1890
1891 \sa QWidget::addAction(), QMenu::menuAction()
1892*/
1893QMenu *QMenu::addMenu(const QIcon &icon, const QString &title)
1894{
1895 QMenu *menu = new QMenu(title, this);
1896 menu->setIcon(icon);
1897 addAction(menu->menuAction());
1898 return menu;
1899}
1900
1901/*!
1902 This convenience function creates a new separator action, i.e. an
1903 action with QAction::isSeparator() returning true, and adds the new
1904 action to this menu's list of actions. It returns the newly
1905 created action.
1906
1907 QMenu takes ownership of the returned QAction.
1908
1909 \sa QWidget::addAction()
1910*/
1911QAction *QMenu::addSeparator()
1912{
1913 QAction *action = new QAction(this);
1914 action->setSeparator(true);
1915 addAction(action);
1916 return action;
1917}
1918
1919/*!
1920 \since 5.1
1921
1922 This convenience function creates a new section action, i.e. an
1923 action with QAction::isSeparator() returning true but also
1924 having \a text hint, and adds the new action to this menu's list
1925 of actions. It returns the newly created action.
1926
1927 The rendering of the hint is style and platform dependent. Widget
1928 styles can use the text information in the rendering for sections,
1929 or can choose to ignore it and render sections like simple separators.
1930
1931 QMenu takes ownership of the returned QAction.
1932
1933 \sa QWidget::addAction()
1934*/
1935QAction *QMenu::addSection(const QString &text)
1936{
1937 QAction *action = new QAction(text, this);
1938 action->setSeparator(true);
1939 addAction(action);
1940 return action;
1941}
1942
1943/*!
1944 \since 5.1
1945
1946 This convenience function creates a new section action, i.e. an
1947 action with QAction::isSeparator() returning true but also
1948 having \a text and \a icon hints, and adds the new action to this menu's
1949 list of actions. It returns the newly created action.
1950
1951 The rendering of the hints is style and platform dependent. Widget
1952 styles can use the text and icon information in the rendering for sections,
1953 or can choose to ignore them and render sections like simple separators.
1954
1955 QMenu takes ownership of the returned QAction.
1956
1957 \sa QWidget::addAction()
1958*/
1959QAction *QMenu::addSection(const QIcon &icon, const QString &text)
1960{
1961 QAction *action = new QAction(icon, text, this);
1962 action->setSeparator(true);
1963 addAction(action);
1964 return action;
1965}
1966
1967/*!
1968 This convenience function inserts \a menu before action \a before
1969 and returns the menus menuAction().
1970
1971 \sa QWidget::insertAction(), addMenu()
1972*/
1973QAction *QMenu::insertMenu(QAction *before, QMenu *menu)
1974{
1975 QAction *action = menu->menuAction();
1976 insertAction(before, action);
1977 return action;
1978}
1979
1980/*!
1981 This convenience function creates a new separator action, i.e. an
1982 action with QAction::isSeparator() returning true. The function inserts
1983 the newly created action into this menu's list of actions before
1984 action \a before and returns it.
1985
1986 QMenu takes ownership of the returned QAction.
1987
1988 \sa QWidget::insertAction(), addSeparator()
1989*/
1990QAction *QMenu::insertSeparator(QAction *before)
1991{
1992 QAction *action = new QAction(this);
1993 action->setSeparator(true);
1994 insertAction(before, action);
1995 return action;
1996}
1997
1998/*!
1999 \since 5.1
2000
2001 This convenience function creates a new title action, i.e. an
2002 action with QAction::isSeparator() returning true but also having
2003 \a text hint. The function inserts the newly created action
2004 into this menu's list of actions before action \a before and
2005 returns it.
2006
2007 The rendering of the hint is style and platform dependent. Widget
2008 styles can use the text information in the rendering for sections,
2009 or can choose to ignore it and render sections like simple separators.
2010
2011 QMenu takes ownership of the returned QAction.
2012
2013 \sa QWidget::insertAction(), addSection()
2014*/
2015QAction *QMenu::insertSection(QAction *before, const QString &text)
2016{
2017 QAction *action = new QAction(text, this);
2018 action->setSeparator(true);
2019 insertAction(before, action);
2020 return action;
2021}
2022
2023/*!
2024 \since 5.1
2025
2026 This convenience function creates a new title action, i.e. an
2027 action with QAction::isSeparator() returning true but also having
2028 \a text and \a icon hints. The function inserts the newly created action
2029 into this menu's list of actions before action \a before and returns it.
2030
2031 The rendering of the hints is style and platform dependent. Widget
2032 styles can use the text and icon information in the rendering for sections,
2033 or can choose to ignore them and render sections like simple separators.
2034
2035 QMenu takes ownership of the returned QAction.
2036
2037 \sa QWidget::insertAction(), addSection()
2038*/
2039QAction *QMenu::insertSection(QAction *before, const QIcon &icon, const QString &text)
2040{
2041 QAction *action = new QAction(icon, text, this);
2042 action->setSeparator(true);
2043 insertAction(before, action);
2044 return action;
2045}
2046
2047/*!
2048 This sets the default action to \a act. The default action may have
2049 a visual cue, depending on the current QStyle. A default action
2050 usually indicates what will happen by default when a drop occurs.
2051
2052 \sa defaultAction()
2053*/
2054void QMenu::setDefaultAction(QAction *act)
2055{
2056 d_func()->defaultAction = act;
2057}
2058
2059/*!
2060 Returns the current default action.
2061
2062 \sa setDefaultAction()
2063*/
2064QAction *QMenu::defaultAction() const
2065{
2066 return d_func()->defaultAction;
2067}
2068
2069/*!
2070 \property QMenu::tearOffEnabled
2071 \brief whether the menu supports being torn off
2072
2073 When true, the menu contains a special tear-off item (often shown as a dashed
2074 line at the top of the menu) that creates a copy of the menu when it is
2075 triggered.
2076
2077 This "torn-off" copy lives in a separate window. It contains the same menu
2078 items as the original menu, with the exception of the tear-off handle.
2079
2080 By default, this property is \c false.
2081*/
2082void QMenu::setTearOffEnabled(bool b)
2083{
2084 Q_D(QMenu);
2085 if (d->tearoff == b)
2086 return;
2087 if (!b)
2088 hideTearOffMenu();
2089 d->tearoff = b;
2090
2091 d->itemsDirty = true;
2092 if (isVisible())
2093 resize(sizeHint());
2094}
2095
2096bool QMenu::isTearOffEnabled() const
2097{
2098 return d_func()->tearoff;
2099}
2100
2101/*!
2102 When a menu is torn off a second menu is shown to display the menu
2103 contents in a new window. When the menu is in this mode and the menu
2104 is visible returns \c true; otherwise false.
2105
2106 \sa showTearOffMenu(), hideTearOffMenu(), isTearOffEnabled()
2107*/
2108bool QMenu::isTearOffMenuVisible() const
2109{
2110 if (d_func()->tornPopup)
2111 return d_func()->tornPopup->isVisible();
2112 return false;
2113}
2114
2115/*!
2116 \since 5.7
2117
2118 This function will forcibly show the torn off menu making it
2119 appear on the user's desktop at the specified \e global position \a pos.
2120
2121 \sa hideTearOffMenu(), isTearOffMenuVisible(), isTearOffEnabled()
2122*/
2123void QMenu::showTearOffMenu(const QPoint &pos)
2124{
2125 Q_D(QMenu);
2126 if (!d->tornPopup)
2127 d->tornPopup = new QTornOffMenu(this);
2128 const QSize &s = sizeHint();
2129 d->tornPopup->setGeometry(pos.x(), pos.y(), s.width(), s.height());
2130 d->tornPopup->show();
2131}
2132
2133/*!
2134 \overload
2135 \since 5.7
2136
2137 This function will forcibly show the torn off menu making it
2138 appear on the user's desktop under the mouse currsor.
2139
2140 \sa hideTearOffMenu(), isTearOffMenuVisible(), isTearOffEnabled()
2141*/
2142void QMenu::showTearOffMenu()
2143{
2144 showTearOffMenu(QCursor::pos());
2145}
2146
2147/*!
2148 This function will forcibly hide the torn off menu making it
2149 disappear from the user's desktop.
2150
2151 \sa showTearOffMenu(), isTearOffMenuVisible(), isTearOffEnabled()
2152*/
2153void QMenu::hideTearOffMenu()
2154{
2155 Q_D(QMenu);
2156 if (d->tornPopup) {
2157 d->tornPopup->close();
2158 // QTornOffMenu sets WA_DeleteOnClose, so we
2159 // should consider the torn-off menu deleted.
2160 // This way showTearOffMenu() will not try to
2161 // reuse the dying torn-off menu.
2162 d->tornPopup = nullptr;
2163 }
2164}
2165
2166
2167/*!
2168 Sets the currently highlighted action to \a act.
2169*/
2170void QMenu::setActiveAction(QAction *act)
2171{
2172 Q_D(QMenu);
2173 d->setCurrentAction(act, 0);
2174 if (d->scroll && act)
2175 d->scrollMenu(act, QMenuPrivate::QMenuScroller::ScrollCenter);
2176}
2177
2178
2179/*!
2180 Returns the currently highlighted action, or \nullptr if no
2181 action is currently highlighted.
2182*/
2183QAction *QMenu::activeAction() const
2184{
2185 return d_func()->currentAction;
2186}
2187
2188/*!
2189 \since 4.2
2190
2191 Returns \c true if there are no visible actions inserted into the menu, false
2192 otherwise.
2193
2194 \sa QWidget::actions()
2195*/
2196
2197bool QMenu::isEmpty() const
2198{
2199 bool ret = true;
2200 for(int i = 0; ret && i < actions().size(); ++i) {
2201 const QAction *action = actions().at(i);
2202 if (!action->isSeparator() && action->isVisible()) {
2203 ret = false;
2204 }
2205 }
2206 return ret;
2207}
2208
2209/*!
2210 Removes all the menu's actions. Actions owned by the menu and not
2211 shown in any other widget are deleted.
2212
2213 \sa removeAction()
2214*/
2215void QMenu::clear()
2216{
2217 QList<QAction*> acts = actions();
2218
2219 for(int i = 0; i < acts.size(); i++) {
2220 removeAction(acts[i]);
2221 if (acts[i]->parent() == this && acts[i]->d_func()->associatedObjects.isEmpty())
2222 delete acts[i];
2223 }
2224}
2225
2226/*!
2227 If a menu does not fit on the screen it lays itself out so that it
2228 does fit. It is style dependent what layout means (for example, on
2229 Windows it will use multiple columns).
2230
2231 This functions returns the number of columns necessary.
2232*/
2233int QMenu::columnCount() const
2234{
2235 return d_func()->ncols;
2236}
2237
2238/*!
2239 Returns the item at \a pt; returns \nullptr if there is no item there.
2240*/
2241QAction *QMenu::actionAt(const QPoint &pt) const
2242{
2243 if (QAction *ret = d_func()->actionAt(pt))
2244 return ret;
2245 return nullptr;
2246}
2247
2248/*!
2249 Returns the geometry of action \a act.
2250*/
2251QRect QMenu::actionGeometry(QAction *act) const
2252{
2253 return d_func()->actionRect(act);
2254}
2255
2256/*!
2257 \reimp
2258*/
2259QSize QMenu::sizeHint() const
2260{
2261 Q_D(const QMenu);
2262 d->updateActionRects();
2263
2264 QSize s;
2265 for (int i = 0; i < d->actionRects.size(); ++i) {
2266 const QRect &rect = d->actionRects.at(i);
2267 if (rect.isNull())
2268 continue;
2269 if (rect.bottom() >= s.height())
2270 s.setHeight(rect.y() + rect.height());
2271 if (rect.right() >= s.width())
2272 s.setWidth(rect.x() + rect.width());
2273 }
2274 // Note that the action rects calculated above already include
2275 // the top and left margins, so we only need to add margins for
2276 // the bottom and right.
2277 QStyleOption opt(0);
2278 opt.initFrom(this);
2279 const int fw = style()->pixelMetric(QStyle::PM_MenuPanelWidth, &opt, this);
2280 s.rwidth() += style()->pixelMetric(QStyle::PM_MenuHMargin, &opt, this) + fw + d->rightmargin;
2281 s.rheight() += style()->pixelMetric(QStyle::PM_MenuVMargin, &opt, this) + fw + d->bottommargin;
2282
2283 return style()->sizeFromContents(QStyle::CT_Menu, &opt, s, this);
2284}
2285
2286/*!
2287 Displays the menu so that the action \a atAction will be at the
2288 specified \e global position \a p. To translate a widget's local
2289 coordinates into global coordinates, use QWidget::mapToGlobal().
2290
2291 When positioning a menu with exec() or popup(), bear in mind that
2292 you cannot rely on the menu's current size(). For performance
2293 reasons, the menu adapts its size only when necessary, so in many
2294 cases, the size before and after the show is different. Instead,
2295 use sizeHint() which calculates the proper size depending on the
2296 menu's current contents.
2297
2298 \sa QWidget::mapToGlobal(), exec()
2299*/
2300void QMenu::popup(const QPoint &p, QAction *atAction)
2301{
2302 Q_D(QMenu);
2303 d->popup(p, atAction);
2304}
2305
2306void QMenuPrivate::popup(const QPoint &p, QAction *atAction, PositionFunction positionFunction)
2307{
2308 Q_Q(QMenu);
2309 popupScreen = QGuiApplication::screenAt(p);
2310 QScopeGuard popupScreenGuard([this](){ popupScreen.clear(); });
2311
2312 if (scroll) { // reset scroll state from last popup
2313 if (scroll->scrollOffset)
2314 itemsDirty = 1; // sizeHint will be incorrect if there is previous scroll
2315 scroll->scrollOffset = 0;
2316 scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone;
2317 }
2319 motions = 0;
2320 doChildEffects = true;
2322
2323 q->ensurePolished(); // Get the right font
2324
2325 // Ensure that we get correct sizeHints by placing this window on the correct screen.
2326 if (!eventLoop) {
2327 bool screenSet = false;
2328 QScreen *screen = topData()->initialScreen;
2329 if (screen) {
2330 if (setScreen(screen))
2331 itemsDirty = true;
2332 screenSet = true;
2333 } else if (QMenu *parentMenu = qobject_cast<QMenu *>(parent)) {
2334 // a submenu is always opened from an open parent menu,
2335 // so show it on the same screen where the parent is. (QTBUG-76162)
2336 if (setScreen(parentMenu->screen()))
2337 itemsDirty = true;
2338 screenSet = true;
2339 }
2340 if (!screenSet && setScreenForPoint(p))
2341 itemsDirty = true;
2342 }
2343
2344 const bool contextMenu = isContextMenu();
2345 if (lastContextMenu != contextMenu) {
2346 itemsDirty = true;
2347 lastContextMenu = contextMenu;
2348 }
2349
2350 // Until QWidget::metric accepts the screen set on a widget (without having a window handle)
2351 // we need to make sure we get a window handle. This must be done near here because
2352 // we want the screen to be correctly set and items to be marked dirty.
2353 // (and screen set could 'fail' on oldscreen == newScreen if created before causing the
2354 // itemsDirty not to be set though needed to get the correct size on first show).
2355 if (!windowHandle()) {
2356 createWinId();
2357 }
2358
2359#if QT_CONFIG(menubar)
2360 // if this menu is part of a chain attached to a QMenuBar, set the
2361 // _NET_WM_WINDOW_TYPE_DROPDOWN_MENU X11 window type
2362 q->setAttribute(Qt::WA_X11NetWmWindowTypeDropDownMenu, qobject_cast<QMenuBar *>(topCausedWidget()) != nullptr);
2363#endif
2364
2365 emit q->aboutToShow();
2366 const bool actionListChanged = itemsDirty;
2367
2368 QRect screen;
2369#if QT_CONFIG(graphicsview)
2370 bool isEmbedded = !bypassGraphicsProxyWidget(q) && QMenuPrivate::nearestGraphicsProxyWidget(q);
2371 if (isEmbedded)
2372 screen = popupGeometry();
2373 else
2374#endif
2375 screen = popupGeometry(QGuiApplication::screenAt(p));
2376 updateActionRects(screen);
2377
2378 QPoint pos;
2379 QPushButton *causedButton = qobject_cast<QPushButton*>(causedPopup.widget);
2380 if (actionListChanged && causedButton)
2381 pos = QPushButtonPrivate::get(causedButton)->adjustedMenuPosition();
2382 else
2383 pos = p;
2384 popupScreen = QGuiApplication::screenAt(pos);
2385
2386 const QSize menuSizeHint(q->sizeHint());
2387 QSize size = menuSizeHint;
2388
2389 if (positionFunction)
2390 pos = positionFunction(menuSizeHint);
2391
2392 const int desktopFrame = q->style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, nullptr, q);
2393 bool adjustToDesktop = !q->window()->testAttribute(Qt::WA_DontShowOnScreen);
2394
2395 // if the screens have very different geometries and the menu is too big, we have to recalculate
2396 if ((size.height() > screen.height() || size.width() > screen.width()) ||
2397 // Layout is not right, we might be able to save horizontal space
2398 (ncols >1 && size.height() < screen.height())) {
2399 size.setWidth(qMin(menuSizeHint.width(), screen.width() - desktopFrame * 2));
2400 size.setHeight(qMin(menuSizeHint.height(), screen.height() - desktopFrame * 2));
2401 adjustToDesktop = true;
2402 }
2403
2404#ifdef QT_KEYPAD_NAVIGATION
2405 if (!atAction && QApplicationPrivate::keypadNavigationEnabled()) {
2406 // Try to have one item activated
2407 if (defaultAction && defaultAction->isEnabled()) {
2408 atAction = defaultAction;
2409 // TODO: This works for first level menus, not yet sub menus
2410 } else {
2411 for (QAction *action : std::as_const(actions))
2412 if (action->isEnabled()) {
2413 atAction = action;
2414 break;
2415 }
2416 }
2417 currentAction = atAction;
2418 }
2419#endif
2420 if (ncols > 1) {
2421 pos.setY(screen.top() + desktopFrame);
2422 } else if (atAction) {
2423 for (int i = 0, above_height = 0; i < actions.size(); i++) {
2424 QAction *action = actions.at(i);
2425 if (action == atAction) {
2426 int newY = pos.y() - above_height;
2427 if (scroll && newY < desktopFrame) {
2428 scroll->scrollFlags = scroll->scrollFlags
2430 scroll->scrollOffset = newY;
2431 newY = desktopFrame;
2432 }
2433 pos.setY(newY);
2434
2435 if (scroll && scroll->scrollFlags != QMenuPrivate::QMenuScroller::ScrollNone
2436 && !q->style()->styleHint(QStyle::SH_Menu_FillScreenWithScroll, nullptr, q)) {
2437 int below_height = above_height + scroll->scrollOffset;
2438 for (int i2 = i; i2 < actionRects.size(); i2++)
2439 below_height += actionRects.at(i2).height();
2440 size.setHeight(below_height);
2441 }
2442 break;
2443 } else {
2444 above_height += actionRects.at(i).height();
2445 }
2446 }
2447 }
2448
2449 // Do nothing if we don't have a valid size, e.g. when all actions are invisible
2450 // and there are no child widgets.
2451 const auto rectIsNull = [](const QRect &rect) { return rect.isNull(); };
2452 if (q->childrenRect().isEmpty()
2453 && std::all_of(actionRects.cbegin(), actionRects.cend(), rectIsNull)) {
2454 eventLoop = nullptr;
2455 syncAction = nullptr;
2456 return;
2457 }
2458
2459 // Note that QGuiApplicationPrivate::lastCursorPosition isn't a QPointF,
2460 // so these two statements can't be simplified...
2461 const QPoint mouse = QGuiApplicationPrivate::lastCursorPosition.toPoint();
2462 mousePopupPos = QGuiApplicationPrivate::lastCursorPosition;
2463 const bool snapToMouse = !causedPopup.widget && (QRect(p.x() - 3, p.y() - 3, 6, 6).contains(mouse));
2464
2465 if (adjustToDesktop) {
2466 // handle popup falling "off screen"
2467 if (q->isRightToLeft()) {
2468 if (snapToMouse) // position flowing left from the mouse
2469 pos.setX(mouse.x() - size.width());
2470
2471#if QT_CONFIG(menubar)
2472 // if the menu is in a menubar or is a submenu, it should be right-aligned
2473 if (qobject_cast<QMenuBar*>(causedPopup.widget) || qobject_cast<QMenu*>(causedPopup.widget))
2474 pos.rx() -= size.width();
2475#endif // QT_CONFIG(menubar)
2476
2477 if (pos.x() < screen.left() + desktopFrame)
2478 pos.setX(qMax(p.x(), screen.left() + desktopFrame));
2479 if (pos.x() + size.width() - 1 > screen.right() - desktopFrame)
2480 pos.setX(qMax(p.x() - size.width(), screen.right() - desktopFrame - size.width() + 1));
2481 } else {
2482 if (pos.x() + size.width() - 1 > screen.right() - desktopFrame)
2483 pos.setX(screen.right() - desktopFrame - size.width() + 1);
2484 if (pos.x() < screen.left() + desktopFrame)
2485 pos.setX(screen.left() + desktopFrame);
2486 }
2487 if (pos.y() + size.height() - 1 > screen.bottom() - desktopFrame) {
2488 if (snapToMouse)
2489 pos.setY(qMin(mouse.y() - (size.height() + desktopFrame), screen.bottom()-desktopFrame-size.height()+1));
2490 else
2491 pos.setY(qMax(p.y() - (size.height() + desktopFrame), screen.bottom()-desktopFrame-size.height()+1));
2492 }
2493
2494 if (pos.y() < screen.top() + desktopFrame)
2495 pos.setY(screen.top() + desktopFrame);
2496 if (pos.y() + menuSizeHint.height() - 1 > screen.bottom() - desktopFrame) {
2497 if (scroll) {
2498 scroll->scrollFlags |= uint(QMenuPrivate::QMenuScroller::ScrollDown);
2499 int y = qMax(screen.y(),pos.y());
2500 size.setHeight(screen.bottom() - (desktopFrame * 2) - y);
2501 } else {
2502 // Too big for screen, bias to see bottom of menu (for some reason)
2503 pos.setY(screen.bottom() - size.height() + 1);
2504 }
2505 }
2506 }
2507
2508 const int subMenuOffset = q->style()->pixelMetric(QStyle::PM_SubMenuOverlap, nullptr, q);
2509 QMenu *caused = qobject_cast<QMenu*>(causedPopup.widget);
2510 if (caused && caused->geometry().width() + menuSizeHint.width() + subMenuOffset < screen.width()) {
2511 QRect parentActionRect(caused->d_func()->actionRect(caused->d_func()->currentAction));
2512 const QPoint actionTopLeft = caused->mapToGlobal(parentActionRect.topLeft());
2513 parentActionRect.moveTopLeft(actionTopLeft);
2514 if (q->isRightToLeft()) {
2515 if ((pos.x() + menuSizeHint.width() > parentActionRect.left() - subMenuOffset)
2516 && (pos.x() < parentActionRect.right()))
2517 {
2518 pos.rx() = parentActionRect.left() - menuSizeHint.width();
2519 if (pos.x() < screen.x())
2520 pos.rx() = parentActionRect.right();
2521 if (pos.x() + menuSizeHint.width() > screen.x() + screen.width())
2522 pos.rx() = screen.x();
2523 }
2524 } else {
2525 if ((pos.x() < parentActionRect.right() + subMenuOffset)
2526 && (pos.x() + menuSizeHint.width() > parentActionRect.left()))
2527 {
2528 pos.rx() = parentActionRect.right();
2529 if (pos.x() + menuSizeHint.width() > screen.x() + screen.width())
2530 pos.rx() = parentActionRect.left() - menuSizeHint.width();
2531 if (pos.x() < screen.x())
2532 pos.rx() = screen.x() + screen.width() - menuSizeHint.width();
2533 }
2534 }
2535 }
2536 popupScreen = QGuiApplication::screenAt(pos);
2537 q->setGeometry(QRect(pos, size));
2538#if QT_CONFIG(effects)
2539 int hGuess = q->isRightToLeft() ? QEffects::LeftScroll : QEffects::RightScroll;
2540 int vGuess = QEffects::DownScroll;
2541 if (q->isRightToLeft()) {
2542 if ((snapToMouse && (pos.x() + size.width() / 2 > mouse.x())) ||
2543 (qobject_cast<QMenu*>(causedPopup.widget) && pos.x() + size.width() / 2 > causedPopup.widget->x()))
2544 hGuess = QEffects::RightScroll;
2545 } else {
2546 if ((snapToMouse && (pos.x() + size.width() / 2 < mouse.x())) ||
2547 (qobject_cast<QMenu*>(causedPopup.widget) && pos.x() + size.width() / 2 < causedPopup.widget->x()))
2548 hGuess = QEffects::LeftScroll;
2549 }
2550
2551#if QT_CONFIG(menubar)
2552 if ((snapToMouse && (pos.y() + size.height() / 2 < mouse.y())) ||
2553 (qobject_cast<QMenuBar*>(causedPopup.widget) &&
2554 pos.y() + size.width() / 2 < causedPopup.widget->mapToGlobal(causedPopup.widget->pos()).y()))
2555 vGuess = QEffects::UpScroll;
2556#endif
2557 if (QApplication::isEffectEnabled(Qt::UI_AnimateMenu)) {
2558 bool doChildEffects = true;
2559#if QT_CONFIG(menubar)
2560 if (QMenuBar *mb = qobject_cast<QMenuBar*>(causedPopup.widget)) {
2561 doChildEffects = mb->d_func()->doChildEffects;
2562 mb->d_func()->doChildEffects = false;
2563 } else
2564#endif
2565 if (QMenu *m = qobject_cast<QMenu*>(causedPopup.widget)) {
2566 doChildEffects = m->d_func()->doChildEffects;
2567 m->d_func()->doChildEffects = false;
2568 }
2569
2570 if (doChildEffects) {
2571 if (QApplication::isEffectEnabled(Qt::UI_FadeMenu))
2572 qFadeEffect(q);
2573 else if (causedPopup.widget)
2574 qScrollEffect(q, qobject_cast<QMenu*>(causedPopup.widget) ? hGuess : vGuess);
2575 else
2576 qScrollEffect(q, hGuess | vGuess);
2577 } else {
2578 // kill any running effect
2579 qFadeEffect(nullptr);
2580 qScrollEffect(nullptr);
2581
2582 q->show();
2583 }
2584 } else
2585#endif
2586 {
2587 q->show();
2588 }
2589
2590#if QT_CONFIG(accessibility)
2591 QAccessibleEvent event(q, QAccessible::PopupMenuStart);
2592 QAccessible::updateAccessibility(&event);
2593#endif
2594}
2595
2596/*!
2597 Executes this menu synchronously.
2598
2599 This is equivalent to \c{exec(pos())}.
2600
2601 This returns the triggered QAction in either the popup menu or one
2602 of its submenus, or \nullptr if no item was triggered (normally
2603 because the user pressed Esc).
2604
2605 In most situations you'll want to specify the position yourself,
2606 for example, the current mouse position:
2607 \snippet code/src_gui_widgets_qmenu.cpp 0
2608 or aligned to a widget:
2609 \snippet code/src_gui_widgets_qmenu.cpp 1
2610 or in reaction to a QMouseEvent *e:
2611 \snippet code/src_gui_widgets_qmenu.cpp 2
2612*/
2613QAction *QMenu::exec()
2614{
2615 return exec(pos());
2616}
2617
2618
2619/*!
2620 \overload
2621
2622 Executes this menu synchronously.
2623
2624 Pops up the menu so that the action \a action will be at the
2625 specified \e global position \a p. To translate a widget's local
2626 coordinates into global coordinates, use QWidget::mapToGlobal().
2627
2628 This returns the triggered QAction in either the popup menu or one
2629 of its submenus, or \nullptr if no item was triggered (normally
2630 because the user pressed Esc).
2631
2632 Note that all signals are emitted as usual. If you connect a
2633 QAction to a slot and call the menu's exec(), you get the result
2634 both via the signal-slot connection and in the return value of
2635 exec().
2636
2637 Common usage is to position the menu at the current mouse
2638 position:
2639 \snippet code/src_gui_widgets_qmenu.cpp 3
2640 or aligned to a widget:
2641 \snippet code/src_gui_widgets_qmenu.cpp 4
2642 or in reaction to a QMouseEvent *e:
2643 \snippet code/src_gui_widgets_qmenu.cpp 5
2644
2645 When positioning a menu with exec() or popup(), bear in mind that
2646 you cannot rely on the menu's current size(). For performance
2647 reasons, the menu adapts its size only when necessary. So in many
2648 cases, the size before and after the show is different. Instead,
2649 use sizeHint() which calculates the proper size depending on the
2650 menu's current contents.
2651
2652 \sa popup(), QWidget::mapToGlobal()
2653*/
2654QAction *QMenu::exec(const QPoint &p, QAction *action)
2655{
2656 Q_D(QMenu);
2657 return d->exec(p, action);
2658}
2659
2660QAction *QMenuPrivate::exec(const QPoint &p, QAction *action, PositionFunction positionFunction)
2661{
2662 Q_Q(QMenu);
2663 q->ensurePolished();
2664 q->createWinId();
2665 QEventLoop evtLoop;
2666 eventLoop = &evtLoop;
2667 popup(p, action, positionFunction);
2668
2669 QPointer<QObject> guard = q;
2670 if (eventLoop) // popup might have reset if there was nothing to show
2671 (void)eventLoop->exec();
2672 if (guard.isNull())
2673 return nullptr;
2674
2675 action = syncAction;
2676 syncAction = nullptr;
2677 eventLoop = nullptr;
2678 popupScreen.clear();
2679 return action;
2680}
2681
2682/*!
2683 \overload
2684
2685 Executes a menu synchronously.
2686
2687 The menu's actions are specified by the list of \a actions. The menu will
2688 pop up so that the specified action, \a at, appears at global position \a
2689 pos. If \a at is not specified then the menu appears at position \a
2690 pos. \a parent is the menu's parent widget; specifying the parent will
2691 provide context when \a pos alone is not enough to decide where the menu
2692 should go (e.g., with multiple desktops or when the parent is embedded in
2693 QGraphicsView).
2694
2695 The function returns the triggered QAction in either the popup
2696 menu or one of its submenus, or \nullptr if no item was triggered
2697 (normally because the user pressed Esc).
2698
2699 This is equivalent to:
2700 \snippet code/src_gui_widgets_qmenu.cpp 6
2701
2702 \sa popup(), QWidget::mapToGlobal()
2703*/
2704QAction *QMenu::exec(const QList<QAction *> &actions, const QPoint &pos, QAction *at, QWidget *parent)
2705{
2706 QMenu menu(parent);
2707 menu.addActions(actions);
2708 return menu.exec(pos, at);
2709}
2710
2711/*!
2712 \reimp
2713*/
2714void QMenu::hideEvent(QHideEvent *)
2715{
2716 Q_D(QMenu);
2717 emit aboutToHide();
2718 if (d->eventLoop)
2719 d->eventLoop->exit();
2720 d->setCurrentAction(nullptr);
2721#if QT_CONFIG(accessibility)
2722 QAccessibleEvent event(this, QAccessible::PopupMenuEnd);
2723 QAccessible::updateAccessibility(&event);
2724#endif
2725#if QT_CONFIG(menubar)
2726 if (QMenuBar *mb = qobject_cast<QMenuBar*>(d->causedPopup.widget))
2727 mb->d_func()->setCurrentAction(nullptr);
2728#endif
2729 if (QMenuPrivate::mouseDown == this)
2730 QMenuPrivate::mouseDown = nullptr;
2731 d->hasHadMouse = false;
2732 if (d->activeMenu)
2733 d->hideMenu(d->activeMenu);
2734 d->causedPopup.widget = nullptr;
2735 d->causedPopup.action = nullptr;
2736 if (d->scroll)
2737 d->scroll->scrollTimer.stop(); //make sure the timer stops
2738}
2739
2740/*!
2741 \reimp
2742*/
2743void QMenu::paintEvent(QPaintEvent *e)
2744{
2745 Q_D(QMenu);
2746 d->updateActionRects();
2747 QStylePainter p(this);
2748 QRegion emptyArea = QRegion(rect());
2749
2750 QStyleOptionMenuItem menuOpt;
2751 menuOpt.initFrom(this);
2752 menuOpt.state = QStyle::State_None;
2753 menuOpt.checkType = QStyleOptionMenuItem::NotCheckable;
2754 menuOpt.maxIconWidth = 0;
2755 menuOpt.reservedShortcutWidth = 0;
2756 p.drawPrimitive(QStyle::PE_PanelMenu, menuOpt);
2757
2758 //calculate the scroll up / down rect
2759 const int fw = style()->pixelMetric(QStyle::PM_MenuPanelWidth, nullptr, this);
2760 const int hmargin = style()->pixelMetric(QStyle::PM_MenuHMargin,nullptr, this);
2761 const int vmargin = style()->pixelMetric(QStyle::PM_MenuVMargin, nullptr, this);
2762
2763 QRect scrollUpRect, scrollDownRect;
2764 const int leftmargin = fw + hmargin + d->leftmargin;
2765 const int topmargin = fw + vmargin + d->topmargin;
2766 const int bottommargin = fw + vmargin + d->bottommargin;
2767 const int contentWidth = width() - (fw + hmargin) * 2 - d->leftmargin - d->rightmargin;
2768 if (d->scroll) {
2769 if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
2770 scrollUpRect.setRect(leftmargin, topmargin, contentWidth, d->scrollerHeight());
2771
2772 if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown)
2773 scrollDownRect.setRect(leftmargin, height() - d->scrollerHeight() - bottommargin,
2774 contentWidth, d->scrollerHeight());
2775 }
2776
2777 //calculate the tear off rect
2778 QRect tearOffRect;
2779 if (d->tearoff) {
2780 tearOffRect.setRect(leftmargin, topmargin, contentWidth,
2781 style()->pixelMetric(QStyle::PM_MenuTearoffHeight, nullptr, this));
2782 if (d->scroll && d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
2783 tearOffRect.translate(0, d->scrollerHeight());
2784 }
2785
2786 //draw the items that need updating..
2787 QRect scrollUpTearOffRect = scrollUpRect.united(tearOffRect);
2788 for (int i = 0; i < d->actions.size(); ++i) {
2789 QAction *action = d->actions.at(i);
2790 QRect actionRect = d->actionRects.at(i);
2791 if (!e->rect().intersects(actionRect)
2792 || d->widgetItems.value(action))
2793 continue;
2794 //set the clip region to be extra safe (and adjust for the scrollers)
2795 emptyArea -= QRegion(actionRect);
2796
2797 QRect adjustedActionRect = actionRect;
2798 if (!scrollUpTearOffRect.isEmpty() && adjustedActionRect.bottom() <= scrollUpTearOffRect.top())
2799 continue;
2800
2801 if (!scrollDownRect.isEmpty() && adjustedActionRect.top() >= scrollDownRect.bottom())
2802 continue;
2803
2804 if (adjustedActionRect.intersects(scrollUpTearOffRect)) {
2805 if (adjustedActionRect.bottom() <= scrollUpTearOffRect.bottom())
2806 continue;
2807 else
2808 adjustedActionRect.setTop(scrollUpTearOffRect.bottom()+1);
2809 }
2810
2811 if (adjustedActionRect.intersects(scrollDownRect)) {
2812 if (adjustedActionRect.top() >= scrollDownRect.top())
2813 continue;
2814 else
2815 adjustedActionRect.setBottom(scrollDownRect.top()-1);
2816 }
2817
2818 QRegion adjustedActionReg(adjustedActionRect);
2819 p.setClipRegion(adjustedActionReg);
2820
2821 QStyleOptionMenuItem opt;
2822 initStyleOption(&opt, action);
2823 opt.rect = actionRect;
2824 p.drawControl(QStyle::CE_MenuItem, opt);
2825 }
2826
2827 emptyArea -= QRegion(scrollUpTearOffRect);
2828 emptyArea -= QRegion(scrollDownRect);
2829
2830 if (d->scrollUpTearOffItem || d->scrollDownItem) {
2831 if (d->scrollUpTearOffItem)
2832 d->scrollUpTearOffItem->updateScrollerRects(scrollUpTearOffRect);
2833 if (d->scrollDownItem)
2834 d->scrollDownItem->updateScrollerRects(scrollDownRect);
2835 } else {
2836 //paint scroll up /down
2837 d->drawScroller(&p, QMenuPrivate::ScrollerTearOffItem::ScrollUp, scrollUpRect);
2838 d->drawScroller(&p, QMenuPrivate::ScrollerTearOffItem::ScrollDown, scrollDownRect);
2839 //paint the tear off..
2840 d->drawTearOff(&p, tearOffRect);
2841 }
2842
2843 //draw border
2844 if (fw) {
2845 QRegion borderReg;
2846 borderReg += QRect(0, 0, fw, height()); //left
2847 borderReg += QRect(width()-fw, 0, fw, height()); //right
2848 borderReg += QRect(0, 0, width(), fw); //top
2849 borderReg += QRect(0, height()-fw, width(), fw); //bottom
2850 p.setClipRegion(borderReg);
2851 emptyArea -= borderReg;
2852 QStyleOptionFrame frame;
2853 frame.rect = rect();
2854 frame.palette = palette();
2855 frame.state = QStyle::State_None;
2856 frame.lineWidth = style()->pixelMetric(QStyle::PM_MenuPanelWidth, &frame, this);
2857 frame.midLineWidth = 0;
2858 p.drawPrimitive(QStyle::PE_FrameMenu, frame);
2859 }
2860
2861 //finally the rest of the spaces
2862 p.setClipRegion(emptyArea);
2863 menuOpt.state = QStyle::State_None;
2864 menuOpt.menuItemType = QStyleOptionMenuItem::EmptyArea;
2865 menuOpt.checkType = QStyleOptionMenuItem::NotCheckable;
2866 menuOpt.rect = rect();
2867 menuOpt.menuRect = rect();
2868 p.drawControl(QStyle::CE_MenuEmptyArea, menuOpt);
2869}
2870
2871#if QT_CONFIG(wheelevent)
2872/*!
2873 \reimp
2874*/
2875void QMenu::wheelEvent(QWheelEvent *e)
2876{
2877 Q_D(QMenu);
2878 if (d->scroll && rect().contains(e->position().toPoint()))
2879 d->scrollMenu(e->angleDelta().y() > 0 ?
2880 QMenuPrivate::QMenuScroller::ScrollUp : QMenuPrivate::QMenuScroller::ScrollDown);
2881}
2882#endif
2883
2884/*!
2885 \reimp
2886*/
2887void QMenu::mousePressEvent(QMouseEvent *e)
2888{
2889 Q_D(QMenu);
2890 if (d->aboutToHide || d->mouseEventTaken(e))
2891 return;
2892 // Workaround for XCB on multiple screens which doesn't have offset. If the menu is open on one screen
2893 // and mouse clicks on second screen, e->pos() is QPoint(0,0) and the menu doesn't hide. This trick makes
2894 // possible to hide the menu when mouse clicks on another screen (e->screenPos() returns correct value).
2895 // Only when mouse clicks in QPoint(0,0) on second screen, the menu doesn't hide.
2896 if ((e->position().toPoint().isNull() && !e->globalPosition().isNull())
2897 || !rect().contains(e->position().toPoint())
2898 || !d->hasMouseMoved(e->globalPosition().toPoint())) {
2899 if (d->noReplayFor
2900 && QRect(d->noReplayFor->mapToGlobal(QPoint()), d->noReplayFor->size()).contains(e->globalPosition().toPoint()))
2901 setAttribute(Qt::WA_NoMouseReplay);
2902 if (d->eventLoop) // synchronous operation
2903 d->syncAction = nullptr;
2904 d->hideUpToMenuBar();
2905 return;
2906 }
2907 QMenuPrivate::mouseDown = this;
2908
2909 QAction *action = d->actionAt(e->position().toPoint());
2910 d->setCurrentAction(action, 20);
2911 update();
2912}
2913
2914/*!
2915 \reimp
2916*/
2917void QMenu::mouseReleaseEvent(QMouseEvent *e)
2918{
2919 Q_D(QMenu);
2920 if (d->aboutToHide || d->mouseEventTaken(e))
2921 return;
2922#if QT_CONFIG(menubar)
2923 if (e->button() == Qt::LeftButton) {
2924 // the QMenu popup steals the mouse release event from the QMenuBar
2925 // so we need to inform it for a redraw with the new state
2926 QMenuBar *mb = qobject_cast<QMenuBar *>(d->causedPopup.widget);
2927 if (mb)
2928 mb->d_func()->mouseRelaseEventFromQMenu();
2929 }
2930#endif
2931 if (QMenuPrivate::mouseDown != this) {
2932 QMenuPrivate::mouseDown = nullptr;
2933 return;
2934 }
2935
2936 QMenuPrivate::mouseDown = nullptr;
2937 d->setSyncAction();
2938
2939 if (!d->hasMouseMoved(e->globalPosition().toPoint())) {
2940 // We don't want to trigger a menu item if the mouse hasn't moved
2941 // since the popup was opened. Instead we want to close the menu.
2942 d->hideUpToMenuBar();
2943 return;
2944 }
2945
2946 QAction *action = d->actionAt(e->position().toPoint());
2947 if (action && action == d->currentAction) {
2948 if (!action->menu()) {
2949#if defined(Q_OS_WIN)
2950 //On Windows only context menus can be activated with the right button
2951 if (e->button() == Qt::LeftButton || d->topCausedWidget() == 0)
2952#endif
2953 d->activateAction(action, QAction::Trigger);
2954 }
2955 } else if (!action || action->isEnabled()) {
2956 d->hideUpToMenuBar();
2957 }
2958}
2959
2960/*!
2961 \reimp
2962*/
2963void QMenu::changeEvent(QEvent *e)
2964{
2965 Q_D(QMenu);
2966 if (e->type() == QEvent::StyleChange || e->type() == QEvent::FontChange ||
2967 e->type() == QEvent::LayoutDirectionChange) {
2968 d->itemsDirty = 1;
2969 setMouseTracking(style()->styleHint(QStyle::SH_Menu_MouseTracking, nullptr, this));
2970 if (isVisible())
2971 resize(sizeHint());
2972 if (!style()->styleHint(QStyle::SH_Menu_Scrollable, nullptr, this)) {
2973 delete d->scroll;
2974 d->scroll = nullptr;
2975 } else if (!d->scroll) {
2976 d->scroll = new QMenuPrivate::QMenuScroller;
2977 d->scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone;
2978 }
2979 } else if (e->type() == QEvent::EnabledChange) {
2980 if (d->tornPopup) // torn-off menu
2981 d->tornPopup->setEnabled(isEnabled());
2982 d->menuAction->setEnabled(isEnabled());
2983 if (!d->platformMenu.isNull())
2984 d->platformMenu->setEnabled(isEnabled());
2985 }
2986 QWidget::changeEvent(e);
2987}
2988
2989
2990/*!
2991 \reimp
2992*/
2993bool QMenu::event(QEvent *e)
2994{
2995 Q_D(QMenu);
2996 switch (e->type()) {
2997 case QEvent::Polish:
2998 d->updateLayoutDirection();
2999 break;
3000 case QEvent::ShortcutOverride: {
3001 QKeyEvent *kev = static_cast<QKeyEvent *>(e);
3002 if (kev->key() == Qt::Key_Up || kev->key() == Qt::Key_Down
3003 || kev->key() == Qt::Key_Left || kev->key() == Qt::Key_Right
3004 || kev->key() == Qt::Key_Enter || kev->key() == Qt::Key_Return
3005#ifndef QT_NO_SHORTCUT
3006 || kev->matches(QKeySequence::Cancel)
3007#endif
3008 ) {
3009 e->accept();
3010 return true;
3011 }
3012 }
3013 break;
3014 case QEvent::KeyPress: {
3015 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
3016 if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
3017 keyPressEvent(ke);
3018 return true;
3019 }
3020 } break;
3021 case QEvent::MouseButtonPress:
3022 case QEvent::ContextMenu: {
3023 bool canPopup = true;
3024 if (e->type() == QEvent::MouseButtonPress)
3025 canPopup = (static_cast<QMouseEvent*>(e)->button() == Qt::LeftButton);
3026 if (canPopup && d->delayState.timer.isActive()) {
3027 d->delayState.stop();
3028 internalDelayedPopup();
3029 }
3030 }
3031 break;
3032 case QEvent::Resize: {
3033 QStyleHintReturnMask menuMask;
3034 QStyleOption option;
3035 option.initFrom(this);
3036 if (style()->styleHint(QStyle::SH_Menu_Mask, &option, this, &menuMask)) {
3037 setMask(menuMask.region);
3038 }
3039 d->itemsDirty = 1;
3040 d->updateActionRects();
3041 break; }
3042 case QEvent::Show:
3043 QMenuPrivate::mouseDown = nullptr;
3044 d->updateActionRects();
3045 d->sloppyState.reset();
3046 if (d->currentAction)
3047 d->popupAction(d->currentAction, 0, false);
3048 if (isWindow() && window() && window()->windowHandle())
3049 window()->windowHandle()->setTransientParent(d->transientParentWindow());
3050 break;
3051#if QT_CONFIG(tooltip)
3052 case QEvent::ToolTip:
3053 if (d->toolTipsVisible) {
3054 const QHelpEvent *ev = static_cast<const QHelpEvent*>(e);
3055 if (const QAction *action = actionAt(ev->pos())) {
3056 const QString toolTip = action->d_func()->tooltip;
3057 if (!toolTip.isEmpty())
3058 QToolTip::showText(ev->globalPos(), toolTip, this);
3059 else
3060 QToolTip::hideText();
3061 return true;
3062 }
3063 }
3064 break;
3065#endif // QT_CONFIG(tooltip)
3066#if QT_CONFIG(whatsthis)
3067 case QEvent::QueryWhatsThis:
3068 e->setAccepted(d->whatsThis.size());
3069 if (QAction *action = d->actionAt(static_cast<QHelpEvent*>(e)->pos())) {
3070 if (action->whatsThis().size() || action->menu())
3071 e->accept();
3072 }
3073 return true;
3074#endif
3075 default:
3076 break;
3077 }
3078 return QWidget::event(e);
3079}
3080
3081/*!
3082 \reimp
3083*/
3084bool QMenu::focusNextPrevChild(bool next)
3085{
3086 setFocus();
3087 QKeyEvent ev(QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier);
3088 keyPressEvent(&ev);
3089 return true;
3090}
3091
3092/*!
3093 \reimp
3094*/
3095void QMenu::keyPressEvent(QKeyEvent *e)
3096{
3097 Q_D(QMenu);
3098 d->updateActionRects();
3099 int key = e->key();
3100 if (isRightToLeft()) { // in reverse mode open/close key for submenues are reversed
3101 if (key == Qt::Key_Left)
3102 key = Qt::Key_Right;
3103 else if (key == Qt::Key_Right)
3104 key = Qt::Key_Left;
3105 }
3106#ifndef Q_OS_MAC
3107 if (key == Qt::Key_Tab) //means down
3108 key = Qt::Key_Down;
3109 if (key == Qt::Key_Backtab) //means up
3110 key = Qt::Key_Up;
3111#endif
3112
3113 bool key_consumed = false;
3114 switch(key) {
3115 case Qt::Key_Home:
3116 key_consumed = true;
3117 if (d->scroll)
3118 d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollTop, true);
3119 break;
3120 case Qt::Key_End:
3121 key_consumed = true;
3122 if (d->scroll)
3123 d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollBottom, true);
3124 break;
3125 case Qt::Key_PageUp:
3126 key_consumed = true;
3127 if (d->currentAction && d->scroll) {
3128 if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
3129 d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollUp, true, true);
3130 else
3131 d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollTop, true);
3132 }
3133 break;
3134 case Qt::Key_PageDown:
3135 key_consumed = true;
3136 if (d->currentAction && d->scroll) {
3137 if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown)
3138 d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollDown, true, true);
3139 else
3140 d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollBottom, true);
3141 }
3142 break;
3143 case Qt::Key_Up:
3144 case Qt::Key_Down: {
3145 key_consumed = true;
3146 QAction *nextAction = nullptr;
3147 QMenuPrivate::QMenuScroller::ScrollLocation scroll_loc = QMenuPrivate::QMenuScroller::ScrollStay;
3148 if (!d->currentAction) {
3149 if (key == Qt::Key_Down) {
3150 for(int i = 0; i < d->actions.size(); ++i) {
3151 if (d->actionRects.at(i).isNull())
3152 continue;
3153 QAction *act = d->actions.at(i);
3154 if (d->considerAction(act)) {
3155 nextAction = act;
3156 break;
3157 }
3158 }
3159 } else {
3160 for(int i = d->actions.size()-1; i >= 0; --i) {
3161 if (d->actionRects.at(i).isNull())
3162 continue;
3163 QAction *act = d->actions.at(i);
3164 if (d->considerAction(act)) {
3165 nextAction = act;
3166 break;
3167 }
3168 }
3169 }
3170 } else {
3171 for(int i = 0, y = 0; !nextAction && i < d->actions.size(); i++) {
3172 QAction *act = d->actions.at(i);
3173 if (act == d->currentAction) {
3174 if (key == Qt::Key_Up) {
3175 for(int next_i = i-1; true; next_i--) {
3176 if (next_i == -1) {
3177 if (!style()->styleHint(QStyle::SH_Menu_SelectionWrap, nullptr, this))
3178 break;
3179 if (d->scroll)
3180 scroll_loc = QMenuPrivate::QMenuScroller::ScrollBottom;
3181 next_i = d->actionRects.size()-1;
3182 }
3183 QAction *next = d->actions.at(next_i);
3184 if (next == d->currentAction)
3185 break;
3186 if (d->actionRects.at(next_i).isNull())
3187 continue;
3188 if (!d->considerAction(next))
3189 continue;
3190 nextAction = next;
3191 if (d->scroll && (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)) {
3192 int topVisible = d->scrollerHeight();
3193 if (d->tearoff)
3194 topVisible += style()->pixelMetric(QStyle::PM_MenuTearoffHeight, nullptr, this);
3195 if (((y + d->scroll->scrollOffset) - topVisible) <= d->actionRects.at(next_i).height())
3196 scroll_loc = QMenuPrivate::QMenuScroller::ScrollTop;
3197 }
3198 break;
3199 }
3200 if (!nextAction && d->tearoff)
3201 d->tearoffHighlighted = 1;
3202 } else {
3203 y += d->actionRects.at(i).height();
3204 for(int next_i = i+1; true; next_i++) {
3205 if (next_i == d->actionRects.size()) {
3206 if (!style()->styleHint(QStyle::SH_Menu_SelectionWrap, nullptr, this))
3207 break;
3208 if (d->scroll)
3209 scroll_loc = QMenuPrivate::QMenuScroller::ScrollTop;
3210 next_i = 0;
3211 }
3212 QAction *next = d->actions.at(next_i);
3213 if (next == d->currentAction)
3214 break;
3215 if (d->actionRects.at(next_i).isNull())
3216 continue;
3217 if (!d->considerAction(next))
3218 continue;
3219 nextAction = next;
3220 if (d->scroll && (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown)) {
3221 int bottomVisible = height() - d->scrollerHeight();
3222 if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
3223 bottomVisible -= d->scrollerHeight();
3224 if (d->tearoff)
3225 bottomVisible -= style()->pixelMetric(QStyle::PM_MenuTearoffHeight, nullptr, this);
3226 if ((y + d->scroll->scrollOffset + d->actionRects.at(next_i).height()) > bottomVisible)
3227 scroll_loc = QMenuPrivate::QMenuScroller::ScrollBottom;
3228 }
3229 break;
3230 }
3231 }
3232 break;
3233 }
3234 y += d->actionRects.at(i).height();
3235 }
3236 }
3237 if (nextAction) {
3238 if (d->scroll && scroll_loc != QMenuPrivate::QMenuScroller::ScrollStay) {
3239 d->scroll->scrollTimer.stop();
3240 d->scrollMenu(nextAction, scroll_loc);
3241 }
3242 d->setCurrentAction(nextAction, /*popup*/-1, QMenuPrivate::SelectedFromKeyboard, key == Qt::Key_Up ? QMenuPrivate::SelectionDirection::Up : QMenuPrivate::SelectionDirection::Down);
3243 }
3244 break; }
3245
3246 case Qt::Key_Right:
3247 if (d->currentAction && d->currentAction->isEnabled() && d->currentAction->menu()) {
3248 d->popupAction(d->currentAction, 0, true);
3249 key_consumed = true;
3250 break;
3251 }
3252 Q_FALLTHROUGH();
3253 case Qt::Key_Left: {
3254 if (d->currentAction && !d->scroll) {
3255 QAction *nextAction = nullptr;
3256 if (key == Qt::Key_Left) {
3257 QRect actionR = d->actionRect(d->currentAction);
3258 for(int x = actionR.left()-1; !nextAction && x >= 0; x--)
3259 nextAction = d->actionAt(QPoint(x, actionR.center().y()));
3260 } else {
3261 QRect actionR = d->actionRect(d->currentAction);
3262 for(int x = actionR.right()+1; !nextAction && x < width(); x++)
3263 nextAction = d->actionAt(QPoint(x, actionR.center().y()));
3264 }
3265 if (nextAction) {
3266 d->setCurrentAction(nextAction, /*popup*/-1, QMenuPrivate::SelectedFromKeyboard, QMenuPrivate::SelectionDirection::Up);
3267 key_consumed = true;
3268 }
3269 }
3270 if (!key_consumed && key == Qt::Key_Left && qobject_cast<QMenu*>(d->causedPopup.widget)) {
3271 QPointer<QWidget> caused = d->causedPopup.widget;
3272 d->hideMenu(this);
3273 if (caused)
3274 caused->setFocus();
3275 key_consumed = true;
3276 }
3277 break; }
3278
3279 case Qt::Key_Alt:
3280 if (d->tornoff)
3281 break;
3282
3283 key_consumed = true;
3284 if (style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, nullptr, this))
3285 {
3286 d->hideMenu(this);
3287#if QT_CONFIG(menubar)
3288 if (QMenuBar *mb = qobject_cast<QMenuBar*>(QApplication::focusWidget())) {
3289 mb->d_func()->setKeyboardMode(false);
3290 }
3291#endif
3292 }
3293 break;
3294
3295 case Qt::Key_Space:
3296 if (!style()->styleHint(QStyle::SH_Menu_SpaceActivatesItem, nullptr, this))
3297 break;
3298 // for motif, fall through
3299 Q_FALLTHROUGH();
3300#ifdef QT_KEYPAD_NAVIGATION
3301 case Qt::Key_Select:
3302#endif
3303 case Qt::Key_Return:
3304 case Qt::Key_Enter: {
3305 if (!d->currentAction) {
3306 d->setFirstActionActive();
3307 key_consumed = true;
3308 break;
3309 }
3310
3311 d->setSyncAction();
3312
3313 if (d->currentAction->menu())
3314 d->popupAction(d->currentAction, 0, true);
3315 else
3316 d->activateAction(d->currentAction, QAction::Trigger);
3317 key_consumed = true;
3318 break; }
3319
3320#if QT_CONFIG(whatsthis)
3321 case Qt::Key_F1:
3322 if (!d->currentAction || d->currentAction->whatsThis().isNull())
3323 break;
3324 QWhatsThis::enterWhatsThisMode();
3325 d->activateAction(d->currentAction, QAction::Trigger);
3326 return;
3327#endif
3328 default:
3329 key_consumed = false;
3330 }
3331
3332 if (!key_consumed && (
3333 false
3334#ifndef QT_NO_SHORTCUT
3335 || e->matches(QKeySequence::Cancel)
3336#endif
3337#ifdef QT_KEYPAD_NAVIGATION
3338 || e->key() == Qt::Key_Back
3339#endif
3340 )) {
3341 key_consumed = true;
3342 if (d->tornoff) {
3343 close();
3344 return;
3345 }
3346 {
3347 QPointer<QWidget> caused = d->causedPopup.widget;
3348 d->hideMenu(this); // hide after getting causedPopup
3349#if QT_CONFIG(menubar)
3350 if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) {
3351 mb->d_func()->setCurrentAction(d->menuAction);
3352 mb->d_func()->setKeyboardMode(true);
3353 }
3354#endif
3355 }
3356 }
3357
3358 if (!key_consumed) { // send to menu bar
3359 const Qt::KeyboardModifiers modifiers = e->modifiers();
3360 if ((!modifiers || modifiers == Qt::AltModifier || modifiers == Qt::ShiftModifier
3361 || modifiers == Qt::KeypadModifier
3362 || modifiers == (Qt::KeypadModifier | Qt::AltModifier))
3363 && e->text().size() == 1) {
3364 bool activateAction = false;
3365 QAction *nextAction = nullptr;
3366 if (style()->styleHint(QStyle::SH_Menu_KeyboardSearch, nullptr, this) && !e->modifiers()) {
3367 int best_match_count = 0;
3368 d->searchBufferTimer.start(2000, this);
3369 d->searchBuffer += e->text();
3370 for(int i = 0; i < d->actions.size(); ++i) {
3371 int match_count = 0;
3372 if (d->actionRects.at(i).isNull())
3373 continue;
3374 QAction *act = d->actions.at(i);
3375 const QString act_text = act->text();
3376 for(int c = 0; c < d->searchBuffer.size(); ++c) {
3377 if (act_text.indexOf(d->searchBuffer.at(c), 0, Qt::CaseInsensitive) != -1)
3378 ++match_count;
3379 }
3380 if (match_count > best_match_count) {
3381 best_match_count = match_count;
3382 nextAction = act;
3383 }
3384 }
3385 }
3386#ifndef QT_NO_SHORTCUT
3387 else {
3388 int clashCount = 0;
3389 QAction *first = nullptr, *currentSelected = nullptr, *firstAfterCurrent = nullptr;
3390 QChar c = e->text().at(0).toUpper();
3391 for(int i = 0; i < d->actions.size(); ++i) {
3392 if (d->actionRects.at(i).isNull())
3393 continue;
3394 QAction *act = d->actions.at(i);
3395 if (!act->isEnabled() || act->isSeparator())
3396 continue;
3397 QKeySequence sequence = QKeySequence::mnemonic(act->text());
3398 int key = sequence[0].toCombined() & 0xffff; // suspicious
3399 if (key == c.unicode()) {
3400 clashCount++;
3401 if (!first)
3402 first = act;
3403 if (act == d->currentAction)
3404 currentSelected = act;
3405 else if (!firstAfterCurrent && currentSelected)
3406 firstAfterCurrent = act;
3407 }
3408 }
3409 if (clashCount == 1)
3410 activateAction = true;
3411 if (clashCount >= 1) {
3412 if (clashCount == 1 || !currentSelected || !firstAfterCurrent)
3413 nextAction = first;
3414 else
3415 nextAction = firstAfterCurrent;
3416 }
3417 }
3418#endif
3419 if (nextAction) {
3420 key_consumed = true;
3421 if (d->scroll)
3422 d->scrollMenu(nextAction, QMenuPrivate::QMenuScroller::ScrollCenter, false);
3423 d->setCurrentAction(nextAction, 0, QMenuPrivate::SelectedFromElsewhere, QMenuPrivate::SelectionDirection::Down, true);
3424 if (!nextAction->menu() && activateAction) {
3425 d->setSyncAction();
3426 d->activateAction(nextAction, QAction::Trigger);
3427 }
3428 }
3429 }
3430 if (!key_consumed) {
3431#if QT_CONFIG(menubar)
3432 if (QMenuBar *mb = qobject_cast<QMenuBar*>(d->topCausedWidget())) {
3433 QAction *oldAct = mb->d_func()->currentAction;
3434 QCoreApplication::sendEvent(mb, e);
3435 if (mb->d_func()->currentAction != oldAct)
3436 key_consumed = true;
3437 }
3438#endif
3439 }
3440
3441#ifdef Q_OS_WIN32
3442 if (key_consumed && (e->key() == Qt::Key_Control || e->key() == Qt::Key_Shift || e->key() == Qt::Key_Meta))
3443 QApplication::beep();
3444#endif // Q_OS_WIN32
3445 }
3446 if (key_consumed)
3447 e->accept();
3448 else
3449 e->ignore();
3450}
3451
3452/*!
3453 \reimp
3454*/
3455void QMenu::mouseMoveEvent(QMouseEvent *e)
3456{
3457 Q_D(QMenu);
3458 if (!isVisible() || d->aboutToHide || d->mouseEventTaken(e))
3459 return;
3460
3461 d->motions++;
3462 if (!d->hasMouseMoved(e->globalPosition().toPoint()))
3463 return;
3464
3465 d->hasHadMouse = d->hasHadMouse || rect().contains(e->position().toPoint());
3466
3467 QAction *action = d->actionAt(e->position().toPoint());
3468 if ((!action || action->isSeparator()) && !d->sloppyState.enabled()) {
3469 if (d->hasHadMouse
3470 || (!d->currentAction || !d->currentAction->menu() || !d->currentAction->menu()->isVisible())) {
3471 d->setCurrentAction(action);
3472 }
3473 return;
3474 }
3475
3476 if (e->buttons())
3477 QMenuPrivate::mouseDown = this;
3478
3479 if (d->activeMenu)
3480 d->activeMenu->d_func()->setCurrentAction(nullptr);
3481
3482 QMenuSloppyState::MouseEventResult sloppyEventResult = d->sloppyState.processMouseEvent(e->position(), action, d->currentAction);
3483 if (sloppyEventResult == QMenuSloppyState::EventShouldBePropagated) {
3484 d->setCurrentAction(action, d->mousePopupDelay);
3485 } else if (sloppyEventResult == QMenuSloppyState::EventDiscardsSloppyState) {
3486 d->sloppyState.reset();
3487 d->hideMenu(d->activeMenu);
3488 }
3489}
3490
3491/*!
3492 \reimp
3493*/
3494void QMenu::enterEvent(QEnterEvent *)
3495{
3496 Q_D(QMenu);
3497 d->hasReceievedEnter = true;
3498 d->sloppyState.enter();
3499 d->motions = -1; // force us to ignore the generate mouse move in mouseMoveEvent()
3500}
3501
3502/*!
3503 \reimp
3504*/
3505void QMenu::leaveEvent(QEvent *)
3506{
3507 Q_D(QMenu);
3508 d->hasReceievedEnter = false;
3509 if (!d->activeMenu && d->currentAction)
3510 setActiveAction(nullptr);
3511}
3512
3513/*!
3514 \reimp
3515*/
3516void
3517QMenu::timerEvent(QTimerEvent *e)
3518{
3519 Q_D(QMenu);
3520 if (d->scroll && d->scroll->scrollTimer.timerId() == e->timerId()) {
3521 d->scrollMenu((QMenuPrivate::QMenuScroller::ScrollDirection)d->scroll->scrollDirection);
3522 if (d->scroll->scrollFlags == QMenuPrivate::QMenuScroller::ScrollNone)
3523 d->scroll->scrollTimer.stop();
3524 } else if (d->delayState.timer.timerId() == e->timerId()) {
3525 if (d->currentAction && !d->currentAction->menu())
3526 return;
3527 d->delayState.stop();
3528 d->sloppyState.stopTimer();
3529 internalDelayedPopup();
3530 } else if (d->sloppyState.isTimerId(e->timerId())) {
3531 d->sloppyState.timeout();
3532 } else if (d->searchBufferTimer.timerId() == e->timerId()) {
3533 d->searchBuffer.clear();
3534 }
3535}
3536
3537/*!
3538 \reimp
3539*/
3540void QMenu::actionEvent(QActionEvent *e)
3541{
3542 Q_D(QMenu);
3543 d->itemsDirty = 1;
3544 setAttribute(Qt::WA_Resized, false);
3545 if (d->tornPopup)
3546 d->tornPopup->syncWithMenu(this, e);
3547 if (e->type() == QEvent::ActionAdded) {
3548
3549 if (!d->tornoff
3550#if QT_CONFIG(menubar)
3551 && !qobject_cast<QMenuBar*>(e->action()->parent())
3552#endif
3553 ) {
3554 // Only connect if the action was not directly added by QMenuBar::addAction(const QString &text)
3555 // to avoid the signal being emitted twice
3556 connect(e->action(), SIGNAL(triggered()), this, SLOT(_q_actionTriggered()), Qt::UniqueConnection);
3557 connect(e->action(), SIGNAL(hovered()), this, SLOT(_q_actionHovered()), Qt::UniqueConnection);
3558 }
3559 if (QWidgetAction *wa = qobject_cast<QWidgetAction *>(e->action())) {
3560 QWidget *widget = wa->requestWidget(this);
3561 if (widget) {
3562 d->widgetItems.insert(wa, widget);
3563 if (d->scroll) {
3564 if (!d->scrollUpTearOffItem)
3565 d->scrollUpTearOffItem =
3566 new QMenuPrivate::ScrollerTearOffItem(QMenuPrivate::ScrollerTearOffItem::ScrollUp, d, this);
3567 if (!d->scrollDownItem)
3568 d->scrollDownItem =
3569 new QMenuPrivate::ScrollerTearOffItem(QMenuPrivate::ScrollerTearOffItem::ScrollDown, d, this);
3570 }
3571 }
3572 }
3573 } else if (e->type() == QEvent::ActionRemoved) {
3574 e->action()->disconnect(this);
3575 if (e->action() == d->currentAction)
3576 d->currentAction = nullptr;
3577 if (QWidgetAction *wa = qobject_cast<QWidgetAction *>(e->action())) {
3578 if (QWidget *widget = d->widgetItems.value(wa))
3579 wa->releaseWidget(widget);
3580 }
3581 d->widgetItems.remove(static_cast<QAction *>(e->action()));
3582 }
3583
3584 if (!d->platformMenu.isNull()) {
3585 auto action = static_cast<QAction *>(e->action());
3586 if (e->type() == QEvent::ActionAdded) {
3587 QPlatformMenuItem *beforeItem = e->before()
3588 ? d->platformMenu->menuItemForTag(reinterpret_cast<quintptr>(e->before()))
3589 : nullptr;
3590 d->insertActionInPlatformMenu(action, beforeItem);
3591 } else if (e->type() == QEvent::ActionRemoved) {
3592 QPlatformMenuItem *menuItem = d->platformMenu->menuItemForTag(reinterpret_cast<quintptr>(e->action()));
3593 d->platformMenu->removeMenuItem(menuItem);
3594 delete menuItem;
3595 } else if (e->type() == QEvent::ActionChanged) {
3596 QPlatformMenuItem *menuItem = d->platformMenu->menuItemForTag(reinterpret_cast<quintptr>(e->action()));
3597 if (menuItem) {
3598 d->copyActionToPlatformItem(action, menuItem);
3599 d->platformMenu->syncMenuItem(menuItem);
3600 }
3601 }
3602
3603 d->platformMenu->syncSeparatorsCollapsible(d->collapsibleSeparators);
3604 }
3605
3606 if (isVisible()) {
3607 resize(sizeHint());
3608 update();
3609 }
3610}
3611
3612/*!
3613 \internal
3614*/
3615void QMenu::internalDelayedPopup()
3616{
3617 Q_D(QMenu);
3618 //hide the current item
3619 if (QMenu *menu = d->activeMenu) {
3620 if (d->activeMenu->menuAction() != d->currentAction)
3621 d->hideMenu(menu);
3622 }
3623
3624 if (!d->currentAction || !d->currentAction->isEnabled() || !d->currentAction->menu() ||
3625 !d->currentAction->menu()->isEnabled() || d->currentAction->menu()->isVisible())
3626 return;
3627
3628 //setup
3629 d->activeMenu = d->currentAction->menu();
3630 d->activeMenu->d_func()->causedPopup.widget = this;
3631 d->activeMenu->d_func()->causedPopup.action = d->currentAction;
3632
3633 QRect screen;
3634#if QT_CONFIG(graphicsview)
3635 bool isEmbedded = !bypassGraphicsProxyWidget(this) && QMenuPrivate::nearestGraphicsProxyWidget(this);
3636 if (isEmbedded)
3637 screen = d->popupGeometry();
3638 else
3639#endif
3640 screen = d->popupGeometry(QGuiApplication::screenAt(pos()));
3641
3642 int subMenuOffset = style()->pixelMetric(QStyle::PM_SubMenuOverlap, nullptr, this);
3643 const QRect actionRect(d->actionRect(d->currentAction));
3644 const auto ofs = isRightToLeft() ? (-subMenuOffset - actionRect.width() + 1) : subMenuOffset;
3645 QPoint subMenuPos(mapToGlobal(QPoint(actionRect.right() + ofs, actionRect.top())));
3646 if (subMenuPos.x() > screen.right())
3647 subMenuPos.setX(geometry().left());
3648
3649 const auto &subMenuActions = d->activeMenu->actions();
3650 if (!subMenuActions.isEmpty()) {
3651 // Offset by the submenu's 1st action position to align with the current action
3652 const auto subMenuActionRect = d->activeMenu->actionGeometry(subMenuActions.first());
3653 subMenuPos.ry() -= subMenuActionRect.top();
3654 }
3655
3656 d->activeMenu->popup(subMenuPos);
3657 d->sloppyState.setSubMenuPopup(actionRect, d->currentAction, d->activeMenu);
3658
3659#if !defined(Q_OS_DARWIN)
3660 // Send the leave event to the current menu - only active popup menu gets
3661 // mouse enter/leave events. Currently Cocoa is an exception, so disable
3662 // it there to avoid event duplication.
3663 if (underMouse()) {
3664 QEvent leaveEvent(QEvent::Leave);
3665 QCoreApplication::sendEvent(this, &leaveEvent);
3666 }
3667#endif
3668}
3669
3670/*!
3671 \fn void QMenu::aboutToHide()
3672 \since 4.2
3673
3674 This signal is emitted just before the menu is hidden from the user.
3675
3676 \sa aboutToShow(), hide()
3677*/
3678
3679/*!
3680 \fn void QMenu::aboutToShow()
3681
3682 This signal is emitted just before the menu is shown to the user.
3683
3684 \sa aboutToHide(), show()
3685*/
3686
3687/*!
3688 \fn void QMenu::triggered(QAction *action)
3689
3690 This signal is emitted when an action in this menu is triggered.
3691
3692 \a action is the action that caused the signal to be emitted.
3693
3694 Normally, you connect each menu action's \l{QAction::}{triggered()} signal
3695 to its own custom slot, but sometimes you will want to connect several
3696 actions to a single slot, for example, when you have a group of closely
3697 related actions, such as "left justify", "center", "right justify".
3698
3699 \note This signal is emitted for the main parent menu in a hierarchy.
3700 Hence, only the parent menu needs to be connected to a slot; sub-menus need
3701 not be connected.
3702
3703 \sa hovered(), QAction::triggered()
3704*/
3705
3706/*!
3707 \fn void QMenu::hovered(QAction *action)
3708
3709 This signal is emitted when a menu action is highlighted; \a action
3710 is the action that caused the signal to be emitted.
3711
3712 Often this is used to update status information.
3713
3714 \sa triggered(), QAction::hovered()
3715*/
3716
3717
3718/*!\internal
3719*/
3720void QMenu::setNoReplayFor(QWidget *noReplayFor)
3721{
3722 d_func()->noReplayFor = noReplayFor;
3723}
3724
3725/*!\internal
3726*/
3727QPlatformMenu *QMenu::platformMenu()
3728{
3729
3730 return d_func()->platformMenu;
3731}
3732
3733/*!\internal
3734*/
3735void QMenu::setPlatformMenu(QPlatformMenu *platformMenu)
3736{
3737 d_func()->setPlatformMenu(platformMenu);
3738 d_func()->syncPlatformMenu();
3739}
3740
3741/*!
3742 \property QMenu::separatorsCollapsible
3743 \since 4.2
3744
3745 \brief whether consecutive separators should be collapsed
3746
3747 This property specifies whether consecutive separators in the menu
3748 should be visually collapsed to a single one. Separators at the
3749 beginning or the end of the menu are also hidden.
3750
3751 By default, this property is \c true.
3752*/
3753bool QMenu::separatorsCollapsible() const
3754{
3755 Q_D(const QMenu);
3756 return d->collapsibleSeparators;
3757}
3758
3759void QMenu::setSeparatorsCollapsible(bool collapse)
3760{
3761 Q_D(QMenu);
3762 if (d->collapsibleSeparators == collapse)
3763 return;
3764
3765 d->collapsibleSeparators = collapse;
3766 d->itemsDirty = 1;
3767 if (isVisible()) {
3768 d->updateActionRects();
3769 update();
3770 }
3771 if (!d->platformMenu.isNull())
3772 d->platformMenu->syncSeparatorsCollapsible(collapse);
3773}
3774
3775/*!
3776 \property QMenu::toolTipsVisible
3777 \since 5.1
3778
3779 \brief whether tooltips of menu actions should be visible
3780
3781 This property specifies whether action menu entries show
3782 their tooltip.
3783
3784 By default, this property is \c false.
3785*/
3786bool QMenu::toolTipsVisible() const
3787{
3788 Q_D(const QMenu);
3789 return d->toolTipsVisible;
3790}
3791
3792void QMenu::setToolTipsVisible(bool visible)
3793{
3794 Q_D(QMenu);
3795 if (d->toolTipsVisible == visible)
3796 return;
3797
3798 d->toolTipsVisible = visible;
3799}
3800
3801QT_END_NAMESPACE
3802
3803// for private slots
3804#include "moc_qmenu.cpp"
3805#include "qmenu.moc"
void updateLayoutDirection()
Definition qmenu.cpp:944
void init()
Definition qmenu.cpp:156
QEventLoop * eventLoop
Definition qmenu_p.h:397
QAction * menuAction
Definition qmenu_p.h:431
uint maxIconWidth
Definition qmenu_p.h:485
void _q_actionTriggered()
Definition qmenu.cpp:1511
void hideUpToMenuBar()
Definition qmenu.cpp:516
@ SelectedFromKeyboard
Definition qmenu_p.h:364
void scrollMenu(QMenuScroller::ScrollLocation location, bool active=false)
Definition qmenu.cpp:1232
int getLastVisibleAction() const
Definition qmenu.cpp:487
static QMenu * mouseDown
Definition qmenu_p.h:330
void updateActionRects() const
Definition qmenu.cpp:331
bool tearoffHighlighted
Definition qmenu_p.h:512
void setSyncAction()
Definition qmenu.cpp:665
void syncPlatformMenu()
Definition qmenu.cpp:202
bool lastContextMenu
Definition qmenu_p.h:501
int scrollerHeight() const
Definition qmenu.cpp:277
QPlatformMenu * createPlatformMenu()
Definition qmenu.cpp:181
bool hasCheckableItems
Definition qmenu_p.h:500
void setFirstActionActive()
Definition qmenu.cpp:683
void _q_overrideMenuActionDestroyed()
Definition qmenu.cpp:939
bool isContextMenu() const
Definition qmenu.cpp:322
void _q_platformMenuAboutToShow()
Definition qmenu.cpp:1551
virtual QList< QPointer< QWidget > > calcCausedStack() const
Definition qmenu.cpp:307
QRect rect() const
Definition qmenu.cpp:1010
bool useFullScreenForPopup() const
Definition qmenu.cpp:285
bool collapsibleSeparators
Definition qmenu_p.h:502
bool tearoff
Definition qmenu_p.h:510
bool itemsDirty
Definition qmenu_p.h:499
bool activationRecursionGuard
Definition qmenu_p.h:490
QAction * actionAt(QPoint p) const
Definition qmenu.cpp:915
QWidget * topCausedWidget() const
Definition qmenu.cpp:907
void scrollMenu(QMenuScroller::ScrollDirection direction, bool page=false, bool active=false)
Definition qmenu.cpp:1265
QWindow * transientParentWindow() const
Definition qmenu.cpp:623
bool tornoff
Definition qmenu_p.h:511
QAction * currentAction
Definition qmenu_p.h:333
uint tabWidth
Definition qmenu_p.h:486
void _q_actionHovered()
Definition qmenu.cpp:1543
bool doChildEffects
Definition qmenu_p.h:514
bool hasReceievedEnter
Definition qmenu_p.h:505
bool hasMouseMoved(const QPoint &globalPos)
Definition qmenu.cpp:1572
QAction * defaultMenuAction
Definition qmenu_p.h:432
void childEnter()
Definition qmenu.cpp:806
bool hasParentActiveDelayTimer() const
Definition qmenu.cpp:846
void childLeave()
Definition qmenu.cpp:822
void stopTimer()
Definition qmenu_p.h:139
void startTimerIfNotRunning()
Definition qmenu_p.h:133
void updateWindowTitle()
Definition qmenu.cpp:138
void actionEvent(QActionEvent *e) override
\reimp
Definition qmenu.cpp:129
QTornOffMenu(QMenu *p)
Definition qmenu.cpp:89
void syncWithMenu(QMenu *menu, QActionEvent *act)
Definition qmenu.cpp:118
ResetOnDestroy(QMenuSloppyState *sloppyState, bool *guard)
Definition qmenu.cpp:854
bool * guard
Definition qmenu.cpp:868
QMenuSloppyState * toReset
Definition qmenu.cpp:867
static QWidget * getParentWidget(const QAction *action)
Definition qmenu.cpp:218