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