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