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

source code of qtbase/src/widgets/widgets/qmenu.cpp