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