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