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