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