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