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

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