source: trunk/src/gui/widgets/qmenu.cpp@ 349

Last change on this file since 349 was 305, checked in by Dmitry A. Kuminov, 16 years ago

gui: QMenu: Don't replay mouse events for a widget that is an origin of the popup (like QPushbutton with setMenu()) to behave more native (similar to Win32). Prior to this pressing a button that was already pressed and displaying a popup would trigger button releases and presses again which doesn't look nice.

File size: 110.2 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qmenu.h"
43
44#ifndef QT_NO_MENU
45
46#include "qdebug.h"
47#include "qstyle.h"
48#include "qevent.h"
49#include "qtimer.h"
50#include "qlayout.h"
51#include "qpainter.h"
52#include "qapplication.h"
53#include "qdesktopwidget.h"
54#ifndef QT_NO_ACCESSIBILITY
55# include "qaccessible.h"
56#endif
57#ifndef QT_NO_EFFECTS
58# include <private/qeffects_p.h>
59#endif
60#ifndef QT_NO_WHATSTHIS
61# include <qwhatsthis.h>
62#endif
63
64#include "qmenu_p.h"
65#include "qmenubar_p.h"
66#include "qwidgetaction.h"
67#include "qtoolbutton.h"
68#include <private/qaction_p.h>
69#ifdef QT3_SUPPORT
70#include <qmenudata.h>
71#endif // QT3_SUPPORT
72
73#ifdef Q_WS_X11
74# include <private/qt_x11_p.h>
75#endif
76
77#if defined(Q_WS_MAC) && !defined(QT_NO_EFFECTS)
78# include <private/qcore_mac_p.h>
79# include <private/qt_cocoa_helpers_mac_p.h>
80#endif
81
82
83QT_BEGIN_NAMESPACE
84
85QPointer<QMenu> QMenuPrivate::mouseDown;
86QBasicTimer QMenuPrivate::menuDelayTimer;
87QBasicTimer QMenuPrivate::sloppyDelayTimer;
88
89/* QMenu code */
90// internal class used for the torn off popup
91class QTornOffMenu : public QMenu
92{
93 Q_OBJECT
94 class QTornOffMenuPrivate : public QMenuPrivate
95 {
96 Q_DECLARE_PUBLIC(QMenu)
97 public:
98 QTornOffMenuPrivate(QMenu *p) : causedMenu(p) {
99 tornoff = 1;
100 causedPopup.widget = 0;
101 causedPopup.action = ((QTornOffMenu*)p)->d_func()->causedPopup.action;
102 causedStack = ((QTornOffMenu*)p)->d_func()->calcCausedStack();
103 }
104 QList<QPointer<QWidget> > calcCausedStack() const { return causedStack; }
105 QPointer<QMenu> causedMenu;
106 QList<QPointer<QWidget> > causedStack;
107 };
108public:
109 QTornOffMenu(QMenu *p) : QMenu(*(new QTornOffMenuPrivate(p)))
110 {
111 Q_D(QTornOffMenu);
112 // make the torn-off menu a sibling of p (instead of a child)
113 QWidget *parentWidget = d->causedStack.isEmpty() ? p : d->causedStack.last();
114 if (parentWidget->parentWidget())
115 parentWidget = parentWidget->parentWidget();
116 setParent(parentWidget, Qt::Window | Qt::Tool);
117 setAttribute(Qt::WA_DeleteOnClose, true);
118 setAttribute(Qt::WA_X11NetWmWindowTypeMenu, true);
119 setWindowTitle(p->windowTitle());
120 setEnabled(p->isEnabled());
121 //QObject::connect(this, SIGNAL(triggered(QAction*)), this, SLOT(onTrigger(QAction*)));
122 //QObject::connect(this, SIGNAL(hovered(QAction*)), this, SLOT(onHovered(QAction*)));
123 QList<QAction*> items = p->actions();
124 for(int i = 0; i < items.count(); i++)
125 addAction(items.at(i));
126 }
127 void syncWithMenu(QMenu *menu, QActionEvent *act)
128 {
129 Q_D(QTornOffMenu);
130 if(menu != d->causedMenu)
131 return;
132 if (act->type() == QEvent::ActionAdded) {
133 insertAction(act->before(), act->action());
134 } else if (act->type() == QEvent::ActionRemoved)
135 removeAction(act->action());
136 }
137 void actionEvent(QActionEvent *e)
138 {
139 QMenu::actionEvent(e);
140 setFixedSize(sizeHint());
141 }
142public slots:
143 void onTrigger(QAction *action) { d_func()->activateAction(action, QAction::Trigger, false); }
144 void onHovered(QAction *action) { d_func()->activateAction(action, QAction::Hover, false); }
145private:
146 Q_DECLARE_PRIVATE(QTornOffMenu)
147 friend class QMenuPrivate;
148};
149
150void QMenuPrivate::init()
151{
152 Q_Q(QMenu);
153 activationRecursionGuard = false;
154#ifndef QT_NO_WHATSTHIS
155 q->setAttribute(Qt::WA_CustomWhatsThis);
156#endif
157 q->setAttribute(Qt::WA_X11NetWmWindowTypePopupMenu);
158 defaultMenuAction = menuAction = new QAction(q);
159 menuAction->d_func()->menu = q;
160 q->setMouseTracking(q->style()->styleHint(QStyle::SH_Menu_MouseTracking, 0, q));
161 if (q->style()->styleHint(QStyle::SH_Menu_Scrollable, 0, q)) {
162 scroll = new QMenuPrivate::QMenuScroller;
163 scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone;
164 }
165}
166
167// Windows, OS/2 and KDE allows menus to cover the taskbar, while GNOME and Mac don't
168QRect QMenuPrivate::popupGeometry(int screen) const
169{
170#if defined(Q_WS_WIN) || defined(Q_WS_PM)
171 return QApplication::desktop()->screenGeometry(screen);
172#elif defined Q_WS_X11
173 if (X11->desktopEnvironment == DE_KDE)
174 return QApplication::desktop()->screenGeometry(screen);
175 else
176 return QApplication::desktop()->availableGeometry(screen);
177#else
178 return QApplication::desktop()->availableGeometry(screen);
179#endif
180}
181
182QList<QPointer<QWidget> > QMenuPrivate::calcCausedStack() const
183{
184 QList<QPointer<QWidget> > ret;
185 for(QWidget *widget = causedPopup.widget; widget; ) {
186 ret.append(widget);
187 if (QTornOffMenu *qtmenu = qobject_cast<QTornOffMenu*>(widget))
188 ret += qtmenu->d_func()->causedStack;
189 if (QMenu *qmenu = qobject_cast<QMenu*>(widget))
190 widget = qmenu->d_func()->causedPopup.widget;
191 else
192 break;
193 }
194 return ret;
195}
196
197void QMenuPrivate::calcActionRects(QMap<QAction*, QRect> &actionRects, QList<QAction*> &actionList) const
198{
199 Q_Q(const QMenu);
200 if (!itemsDirty) {
201 actionRects = this->actionRects;
202 actionList = this->actionList;
203 return;
204 }
205
206 actionRects.clear();
207 actionList.clear();
208 QList<QAction*> items = filterActions(q->actions());
209 int max_column_width = 0,
210 dh = popupGeometry(QApplication::desktop()->screenNumber(q)).height(),
211 ncols = 1,
212 y = 0;
213 const int hmargin = q->style()->pixelMetric(QStyle::PM_MenuHMargin, 0, q),
214 vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q),
215 icone = q->style()->pixelMetric(QStyle::PM_SmallIconSize, 0, q);
216
217 //for compatability now - will have to refactor this away..
218 tabWidth = 0;
219 maxIconWidth = 0;
220 hasCheckableItems = false;
221 for(int i = 0; i < items.count(); i++) {
222 QAction *action = items.at(i);
223 if (widgetItems.value(action))
224 continue;
225 hasCheckableItems |= action->isCheckable();
226 QIcon is = action->icon();
227 if (!is.isNull()) {
228 uint miw = maxIconWidth;
229 maxIconWidth = qMax<uint>(miw, icone + 4);
230 }
231 }
232
233 //calculate size
234 QFontMetrics qfm = q->fontMetrics();
235 for(int i = 0; i < items.count(); i++) {
236 QAction *action = items.at(i);
237
238 QFontMetrics fm(action->font().resolve(q->font()));
239 QSize sz;
240
241 //let the style modify the above size..
242 QStyleOptionMenuItem opt;
243 q->initStyleOption(&opt, action);
244 opt.rect = q->rect();
245
246 if (QWidget *w = widgetItems.value(action)) {
247 sz=w->sizeHint().expandedTo(w->minimumSize()).expandedTo(w->minimumSizeHint()).boundedTo(w->maximumSize());
248 } else {
249 //calc what I think the size is..
250 if (action->isSeparator()) {
251 sz = QSize(2, 2);
252 } else {
253 QString s = action->text();
254 int t = s.indexOf(QLatin1Char('\t'));
255 if (t != -1) {
256 tabWidth = qMax(int(tabWidth), qfm.width(s.mid(t+1)));
257 s = s.left(t);
258 #ifndef QT_NO_SHORTCUT
259 } else {
260 QKeySequence seq = action->shortcut();
261 if (!seq.isEmpty())
262 tabWidth = qMax(int(tabWidth), qfm.width(seq));
263 #endif
264 }
265 int w = fm.boundingRect(QRect(), Qt::TextSingleLine, s).width();
266 w -= s.count(QLatin1Char('&')) * fm.width(QLatin1Char('&'));
267 w += s.count(QLatin1String("&&")) * fm.width(QLatin1Char('&'));
268 sz.setWidth(w);
269 sz.setHeight(qMax(fm.height(), qfm.height()));
270
271 QIcon is = action->icon();
272 if (!is.isNull()) {
273 QSize is_sz = QSize(icone, icone);
274 if (is_sz.height() > sz.height())
275 sz.setHeight(is_sz.height());
276 }
277 }
278 sz = q->style()->sizeFromContents(QStyle::CT_MenuItem, &opt, sz, q);
279 }
280
281
282 if (!sz.isEmpty()) {
283 max_column_width = qMax(max_column_width, sz.width());
284 //wrapping
285 if (!scroll &&
286 y+sz.height()+vmargin > dh - (q->style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, 0, q) * 2)) {
287 ncols++;
288 y = vmargin;
289 }
290 y += sz.height();
291 //append item
292 actionRects.insert(action, QRect(0, 0, sz.width(), sz.height()));
293 actionList.append(action);
294 }
295 }
296
297 if (tabWidth)
298 max_column_width += tabWidth; //finally add in the tab width
299
300 //calculate position
301 int x = hmargin;
302 y = vmargin;
303
304 for(int i = 0; i < actionList.count(); i++) {
305 QAction *action = actionList.at(i);
306 QRect &rect = actionRects[action];
307 if (rect.isNull())
308 continue;
309 if (!scroll &&
310 y+rect.height() > dh - (q->style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, 0, q) * 2)) {
311 ncols--;
312 if (ncols < 0)
313 qWarning("QMenu: Column calculation mismatch (%d)", ncols);
314 x += max_column_width + hmargin;
315 y = vmargin;
316 }
317 rect.translate(x, y); //move
318 rect.setWidth(max_column_width); //uniform width
319 y += rect.height();
320 }
321}
322
323void QMenuPrivate::updateActions()
324{
325 Q_Q(const QMenu);
326 if (!itemsDirty)
327 return;
328 sloppyAction = 0;
329 calcActionRects(actionRects, actionList);
330 for (QHash<QAction *, QWidget *>::ConstIterator item = widgetItems.constBegin(),
331 end = widgetItems.constEnd(); item != end; ++item) {
332 QAction *action = item.key();
333 QWidget *widget = item.value();
334 widget->setGeometry(actionRect(action));
335 widget->setVisible(action->isVisible());
336 }
337 ncols = 1;
338 int last_left = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q);
339 if (!scroll) {
340 for(int i = 0; i < actionList.count(); i++) {
341 int left = actionRects.value(actionList.at(i)).left();
342 if (left > last_left) {
343 last_left = left;
344 ncols++;
345 }
346 }
347 }
348 itemsDirty = 0;
349}
350
351QList<QAction *> QMenuPrivate::filterActions(const QList<QAction *> &actions) const
352{
353 QList<QAction *> visibleActions;
354 int i = 0;
355 while (i < actions.count()) {
356 QAction *action = actions.at(i);
357 if (!action->isVisible()) {
358 ++i;
359 continue;
360 }
361 if (!action->isSeparator() || !collapsibleSeparators) {
362 visibleActions.append(action);
363 ++i;
364 continue;
365 }
366
367 // no leading separators
368 if (!visibleActions.isEmpty())
369 visibleActions.append(action);
370
371 // skip double/tripple/etc. separators
372 while (i < actions.count()
373 && (!actions.at(i)->isVisible() || actions.at(i)->isSeparator()))
374 ++i;
375 }
376
377 if (collapsibleSeparators) {
378 // remove trailing separators
379 while (!visibleActions.isEmpty() && visibleActions.last()->isSeparator())
380 visibleActions.removeLast();
381 }
382
383 return visibleActions;
384}
385
386QRect QMenuPrivate::actionRect(QAction *act) const
387{
388 Q_Q(const QMenu);
389 QRect ret = actionRects.value(act);
390 if (ret.isNull())
391 return ret;
392 if (scroll)
393 ret.translate(0, scroll->scrollOffset);
394 if (tearoff)
395 ret.translate(0, q->style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, q));
396 const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q);
397 ret.translate(fw+leftmargin, fw+topmargin);
398 return ret;
399}
400
401void QMenuPrivate::hideUpToMenuBar()
402{
403 Q_Q(QMenu);
404 if (!tornoff) {
405 QWidget *caused = causedPopup.widget;
406 hideMenu(q); //hide after getting causedPopup
407 while(caused) {
408#ifndef QT_NO_MENUBAR
409 if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) {
410 mb->d_func()->setCurrentAction(0);
411 mb->d_func()->setKeyboardMode(false);
412 caused = 0;
413 } else
414#endif
415 if (QMenu *m = qobject_cast<QMenu*>(caused)) {
416 caused = m->d_func()->causedPopup.widget;
417 if (!m->d_func()->tornoff)
418 hideMenu(m);
419 m->d_func()->setCurrentAction(0);
420 } else {
421#ifndef QT_NO_TOOLBUTTON
422 if (qobject_cast<QToolButton*>(caused) == 0)
423#endif
424 qWarning("QMenu: Internal error");
425 caused = 0;
426 }
427 }
428 }
429 setCurrentAction(0);
430}
431
432void QMenuPrivate::hideMenu(QMenu *menu)
433{
434 if (!menu)
435 return;
436
437#if !defined(QT_NO_EFFECTS)
438 menu->blockSignals(true);
439 aboutToHide = true;
440 // Flash item which is about to trigger (if any).
441 if (menu->style()->styleHint(QStyle::SH_Menu_FlashTriggeredItem)
442 && currentAction && currentAction == actionAboutToTrigger) {
443
444 QEventLoop eventLoop;
445 QAction *activeAction = currentAction;
446
447 // Deselect and wait 60 ms.
448 menu->setActiveAction(0);
449 QTimer::singleShot(60, &eventLoop, SLOT(quit()));
450 eventLoop.exec();
451
452 // Select and wait 20 ms.
453 menu->setActiveAction(activeAction);
454 QTimer::singleShot(20, &eventLoop, SLOT(quit()));
455 eventLoop.exec();
456 }
457
458 // Fade out.
459 if (menu->style()->styleHint(QStyle::SH_Menu_FadeOutOnHide)) {
460 // ### Qt 4.4:
461 // Should be something like: q->transitionWindow(Qt::FadeOutTransition, 150);
462 // Hopefully we'll integrate qt/research/windowtransitions into main before 4.4.
463 // Talk to Richard, Trenton or Bjoern.
464#if defined(Q_WS_MAC)
465 macWindowFade(qt_mac_window_for(menu)); // FIXME - what is the default duration for view animations
466
467 // Wait for the transition to complete.
468 QEventLoop eventLoop;
469 QTimer::singleShot(150, &eventLoop, SLOT(quit()));
470 eventLoop.exec();
471#endif // Q_WS_MAC
472 }
473 aboutToHide = false;
474 menu->blockSignals(false);
475#endif // QT_NO_EFFECTS
476 menu->hide();
477}
478
479void QMenuPrivate::popupAction(QAction *action, int delay, bool activateFirst)
480{
481 Q_Q(QMenu);
482 if (action && action->isEnabled()) {
483 if (!delay)
484 q->internalDelayedPopup();
485 else
486 QMenuPrivate::menuDelayTimer.start(delay, q);
487 if (activateFirst && action->menu())
488 action->menu()->d_func()->setFirstActionActive();
489 } else if (QMenu *menu = activeMenu) { //hide the current item
490 activeMenu = 0;
491 hideMenu(menu);
492 }
493}
494
495void QMenuPrivate::setSyncAction()
496{
497 Q_Q(QMenu);
498 QAction *current = currentAction;
499 if(current && (!current->isEnabled() || current->menu() || current->isSeparator()))
500 current = 0;
501 for(QWidget *caused = q; caused;) {
502 if (QMenu *m = qobject_cast<QMenu*>(caused)) {
503 caused = m->d_func()->causedPopup.widget;
504 if (m->d_func()->eventLoop)
505 m->d_func()->syncAction = current; // synchronous operation
506 } else {
507 break;
508 }
509 }
510}
511
512
513void QMenuPrivate::setFirstActionActive()
514{
515 Q_Q(QMenu);
516 const int scrollerHeight = q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q);
517 for(int i = 0, saccum = 0; i < actionList.count(); i++) {
518 QAction *act = actionList[i];
519 if (scroll && scroll->scrollFlags & QMenuScroller::ScrollUp) {
520 saccum -= actionRects.value(act).height();
521 if (saccum > scroll->scrollOffset-scrollerHeight)
522 continue;
523 }
524 if (!act->isSeparator() &&
525 (q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q)
526 || act->isEnabled())) {
527 setCurrentAction(act);
528 break;
529 }
530 }
531}
532
533// popup == -1 means do not popup, 0 means immediately, others mean use a timer
534void QMenuPrivate::setCurrentAction(QAction *action, int popup, SelectionReason reason, bool activateFirst)
535{
536 Q_Q(QMenu);
537 tearoffHighlighted = 0;
538 if (action == currentAction && !(action && action->menu() && action->menu() != activeMenu)) {
539 if(QMenu *menu = qobject_cast<QMenu*>(causedPopup.widget)) {
540 if(causedPopup.action && menu->d_func()->activeMenu == q)
541 menu->d_func()->setCurrentAction(causedPopup.action, 0, reason, false);
542 }
543 return;
544 }
545 if (currentAction)
546 q->update(actionRect(currentAction));
547
548 sloppyAction = 0;
549 if (!sloppyRegion.isEmpty())
550 sloppyRegion = QRegion();
551 QMenu *hideActiveMenu = activeMenu;
552#ifndef QT_NO_STATUSTIP
553 QAction *previousAction = currentAction;
554#endif
555#ifdef QT3_SUPPORT
556 emitHighlighted = (action && action != currentAction);
557#endif
558 currentAction = action;
559 if (action) {
560 if (!action->isSeparator()) {
561 activateAction(action, QAction::Hover);
562 if (popup != -1) {
563 hideActiveMenu = 0; //will be done "later"
564 // if the menu is visible then activate the required action,
565 // otherwise we just mark the action as currentAction
566 // and activate it when the menu will be popuped.
567 if (q->isVisible())
568 popupAction(currentAction, popup, activateFirst);
569 }
570 q->update(actionRect(action));
571 QWidget *widget = widgetItems.value(action);
572
573 if (reason == SelectedFromKeyboard) {
574 if (widget) {
575 if (widget->focusPolicy() != Qt::NoFocus)
576 widget->setFocus(Qt::TabFocusReason);
577 } else {
578 //when the action has no QWidget, the QMenu itself should
579 // get the focus
580 // Since the menu is a pop-up, it uses the popup reason.
581 if (!q->hasFocus())
582 q->setFocus(Qt::PopupFocusReason);
583 }
584 }
585 } else { //action is a separator
586 if (popup != -1)
587 hideActiveMenu = 0; //will be done "later"
588 }
589#ifndef QT_NO_STATUSTIP
590 } else if (previousAction) {
591 QWidget *w = causedPopup.widget;
592 while (QMenu *m = qobject_cast<QMenu*>(w))
593 w = m->d_func()->causedPopup.widget;
594 if (w) {
595 QString empty;
596 QStatusTipEvent tip(empty);
597 QApplication::sendEvent(w, &tip);
598 }
599#endif
600 }
601 if (hideActiveMenu) {
602 activeMenu = 0;
603#ifndef QT_NO_EFFECTS
604 // kill any running effect
605 qFadeEffect(0);
606 qScrollEffect(0);
607#endif
608 hideMenu(hideActiveMenu);
609 }
610}
611
612QAction *QMenuPrivate::actionAt(QPoint p) const
613{
614 if (!q_func()->rect().contains(p)) //sanity check
615 return 0;
616
617 for(int i = 0; i < actionList.count(); i++) {
618 QAction *act = actionList[i];
619 if (actionRect(act).contains(p))
620 return act;
621 }
622 return 0;
623}
624
625void QMenuPrivate::setOverrideMenuAction(QAction *a)
626{
627 Q_Q(QMenu);
628 QObject::disconnect(menuAction, SIGNAL(destroyed()), q, SLOT(_q_overrideMenuActionDestroyed()));
629 if (a) {
630 menuAction = a;
631 QObject::connect(a, SIGNAL(destroyed()), q, SLOT(_q_overrideMenuActionDestroyed()));
632 } else { //we revert back to the default action created by the QMenu itself
633 menuAction = defaultMenuAction;
634 }
635}
636
637void QMenuPrivate::_q_overrideMenuActionDestroyed()
638{
639 menuAction=defaultMenuAction;
640}
641
642/*!
643 Returns the action associated with this menu.
644*/
645QAction *QMenu::menuAction() const
646{
647 return d_func()->menuAction;
648}
649
650/*!
651 \property QMenu::title
652 \brief The title of the menu
653
654 This is equivalent to the QAction::text property of the menuAction().
655
656 By default, this property contains an empty string.
657*/
658QString QMenu::title() const
659{
660 return d_func()->menuAction->text();
661}
662
663void QMenu::setTitle(const QString &text)
664{
665 d_func()->menuAction->setText(text);
666}
667
668/*!
669 \property QMenu::icon
670
671 \brief The icon of the menu
672
673 This is equivalent to the QAction::icon property of the menuAction().
674
675 By default, if no icon is explicitly set, this property contains a null icon.
676*/
677QIcon QMenu::icon() const
678{
679 return d_func()->menuAction->icon();
680}
681
682void QMenu::setIcon(const QIcon &icon)
683{
684 d_func()->menuAction->setIcon(icon);
685}
686
687
688//actually performs the scrolling
689void QMenuPrivate::scrollMenu(QAction *action, QMenuScroller::ScrollLocation location, bool active)
690{
691 Q_Q(QMenu);
692 if (!scroll || !scroll->scrollFlags)
693 return;
694 int newOffset = 0;
695 const int scrollHeight = q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q);
696 const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp) ? scrollHeight : 0;
697 const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollHeight : 0;
698 const int vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q);
699 const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q);
700
701 if (location == QMenuScroller::ScrollTop) {
702 for(int i = 0, saccum = 0; i < actionList.count(); i++) {
703 QAction *act = actionList.at(i);
704 if (act == action) {
705 newOffset = topScroll - saccum;
706 break;
707 }
708 saccum += actionRects.value(act).height();
709 }
710 } else {
711 for(int i = 0, saccum = 0; i < actionList.count(); i++) {
712 QAction *act = actionList.at(i);
713 saccum += actionRects.value(act).height();
714 if (act == action) {
715 if (location == QMenuScroller::ScrollCenter)
716 newOffset = ((q->height() / 2) - botScroll) - (saccum - topScroll);
717 else
718 newOffset = (q->height() - botScroll) - saccum;
719 break;
720 }
721 }
722 if(newOffset)
723 newOffset -= fw*2;
724 }
725
726 //figure out which scroll flags
727 uint newScrollFlags = QMenuScroller::ScrollNone;
728 if (newOffset < 0) //easy and cheap one
729 newScrollFlags |= QMenuScroller::ScrollUp;
730 int saccum = newOffset;
731 for(int i = 0; i < actionList.count(); i++) {
732 saccum += actionRects.value(actionList.at(i)).height();
733 if (saccum > q->height()) {
734 newScrollFlags |= QMenuScroller::ScrollDown;
735 break;
736 }
737 }
738
739 if (!(newScrollFlags & QMenuScroller::ScrollDown) && (scroll->scrollFlags & QMenuScroller::ScrollDown)) {
740 newOffset = q->height() - (saccum - newOffset) - fw*2 - vmargin; //last item at bottom
741 }
742
743 if (!(newScrollFlags & QMenuScroller::ScrollUp) && (scroll->scrollFlags & QMenuScroller::ScrollUp)) {
744 newOffset = 0; //first item at top
745 }
746
747 if (newScrollFlags & QMenuScroller::ScrollUp)
748 newOffset -= vmargin;
749
750 QRect screen = popupGeometry(QApplication::desktop()->screenNumber(q));
751 const int desktopFrame = q->style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, 0, q);
752 if (q->height() < screen.height()-(desktopFrame*2)-1) {
753 QRect geom = q->geometry();
754 if (newOffset > scroll->scrollOffset && (scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollUp)) { //scroll up
755 const int newHeight = geom.height()-(newOffset-scroll->scrollOffset);
756 if(newHeight > geom.height())
757 geom.setHeight(newHeight);
758 } else if(scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollDown) {
759 int newTop = geom.top() + (newOffset-scroll->scrollOffset);
760 if (newTop < desktopFrame+screen.top())
761 newTop = desktopFrame+screen.top();
762 if (newTop < geom.top()) {
763 geom.setTop(newTop);
764 newOffset = 0;
765 newScrollFlags &= ~QMenuScroller::ScrollUp;
766 }
767 }
768 if (geom.bottom() > screen.bottom() - desktopFrame)
769 geom.setBottom(screen.bottom() - desktopFrame);
770 if (geom.top() < desktopFrame+screen.top())
771 geom.setTop(desktopFrame+screen.top());
772 if (geom != q->geometry()) {
773#if 0
774 if (newScrollFlags & QMenuScroller::ScrollDown &&
775 q->geometry().top() - geom.top() >= -newOffset)
776 newScrollFlags &= ~QMenuScroller::ScrollDown;
777#endif
778 q->setGeometry(geom);
779 }
780 }
781
782 //actually update flags
783 scroll->scrollOffset = newOffset;
784 if (scroll->scrollOffset > 0)
785 scroll->scrollOffset = 0;
786 scroll->scrollFlags = newScrollFlags;
787 if (active)
788 setCurrentAction(action);
789
790 q->update(); //issue an update so we see all the new state..
791}
792
793void QMenuPrivate::scrollMenu(QMenuScroller::ScrollLocation location, bool active)
794{
795 Q_Q(QMenu);
796 if(location == QMenuScroller::ScrollBottom) {
797 for(int i = actionList.size()-1; i >= 0; --i) {
798 QAction *act = actionList.at(i);
799 if (!act->isSeparator() &&
800 (q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q)
801 || act->isEnabled())) {
802 if(scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown)
803 scrollMenu(act, QMenuPrivate::QMenuScroller::ScrollBottom, active);
804 else if(active)
805 setCurrentAction(act, /*popup*/-1, QMenuPrivate::SelectedFromKeyboard);
806 break;
807 }
808 }
809 } else if(location == QMenuScroller::ScrollTop) {
810 for(int i = 0; i < actionList.size(); ++i) {
811 QAction *act = actionList.at(i);
812 if (!act->isSeparator() &&
813 (q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q)
814 || act->isEnabled())) {
815 if(scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
816 scrollMenu(act, QMenuPrivate::QMenuScroller::ScrollTop, active);
817 else if(active)
818 setCurrentAction(act, /*popup*/-1, QMenuPrivate::SelectedFromKeyboard);
819 break;
820 }
821 }
822 }
823}
824
825//only directional
826void QMenuPrivate::scrollMenu(QMenuScroller::ScrollDirection direction, bool page, bool active)
827{
828 Q_Q(QMenu);
829 if (!scroll || !(scroll->scrollFlags & direction)) //not really possible...
830 return;
831 const int scrollHeight = q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q);
832 const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp) ? scrollHeight : 0;
833 const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollHeight : 0;
834 const int vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q);
835 const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q);
836 const int offset = topScroll ? topScroll-vmargin : 0;
837 if (direction == QMenuScroller::ScrollUp) {
838 for(int i = 0, saccum = 0; i < actionList.count(); i++) {
839 QAction *act = actionList.at(i);
840 const int iHeight = actionRects.value(act).height();
841 saccum -= iHeight;
842 if (saccum <= scroll->scrollOffset-offset) {
843 scrollMenu(act, page ? QMenuScroller::ScrollBottom : QMenuScroller::ScrollTop, active);
844 break;
845 }
846 }
847 } else if (direction == QMenuScroller::ScrollDown) {
848 bool scrolled = false;
849 for(int i = 0, saccum = 0; i < actionList.count(); i++) {
850 QAction *act = actionList.at(i);
851 const int iHeight = actionRects.value(act).height();
852 saccum -= iHeight;
853 if (saccum <= scroll->scrollOffset-offset) {
854 const int scrollerArea = q->height() - botScroll - fw*2;
855 int visible = (scroll->scrollOffset-offset) - saccum;
856 for(i++ ; i < actionList.count(); i++) {
857 act = actionList.at(i);
858 const int iHeight = actionRects.value(act).height();
859 visible += iHeight;
860 if (visible > scrollerArea - topScroll) {
861 scrolled = true;
862 scrollMenu(act, page ? QMenuScroller::ScrollTop : QMenuScroller::ScrollBottom, active);
863 break;
864 }
865 }
866 break;
867 }
868 }
869 if(!scrolled) {
870 scroll->scrollFlags &= ~QMenuScroller::ScrollDown;
871 q->update();
872 }
873 }
874}
875
876/* This is poor-mans eventfilters. This avoids the use of
877 eventFilter (which can be nasty for users of QMenuBar's). */
878bool QMenuPrivate::mouseEventTaken(QMouseEvent *e)
879{
880 Q_Q(QMenu);
881 QPoint pos = q->mapFromGlobal(e->globalPos());
882 if (scroll && !activeMenu) { //let the scroller "steal" the event
883 bool isScroll = false;
884 if (pos.x() >= 0 && pos.x() < q->width()) {
885 const int scrollerHeight = q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q);
886 for(int dir = QMenuScroller::ScrollUp; dir <= QMenuScroller::ScrollDown; dir = dir << 1) {
887 if (scroll->scrollFlags & dir) {
888 if (dir == QMenuScroller::ScrollUp)
889 isScroll = (pos.y() <= scrollerHeight);
890 else if (dir == QMenuScroller::ScrollDown)
891 isScroll = (pos.y() >= q->height()-scrollerHeight);
892 if (isScroll) {
893 scroll->scrollDirection = dir;
894 break;
895 }
896 }
897 }
898 }
899 if (isScroll) {
900 if (!scroll->scrollTimer)
901 scroll->scrollTimer = new QBasicTimer;
902 scroll->scrollTimer->start(50, q);
903 return true;
904 } else if (scroll->scrollTimer && scroll->scrollTimer->isActive()) {
905 scroll->scrollTimer->stop();
906 }
907 }
908
909 if (tearoff) { //let the tear off thingie "steal" the event..
910 QRect tearRect(0, 0, q->width(), q->style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, q));
911 if (scroll && scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
912 tearRect.translate(0, q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q));
913 q->update(tearRect);
914 if (tearRect.contains(pos) && hasMouseMoved(e->globalPos())) {
915 setCurrentAction(0);
916 tearoffHighlighted = 1;
917 if (e->type() == QEvent::MouseButtonRelease) {
918 if (!tornPopup)
919 tornPopup = new QTornOffMenu(q);
920 tornPopup->setGeometry(q->geometry());
921 tornPopup->show();
922 hideUpToMenuBar();
923 }
924 return true;
925 }
926 tearoffHighlighted = 0;
927 }
928
929 if (q->frameGeometry().contains(e->globalPos())) //otherwise if the event is in our rect we want it..
930 return false;
931
932 for(QWidget *caused = causedPopup.widget; caused;) {
933 bool passOnEvent = false;
934 QWidget *next_widget = 0;
935 QPoint cpos = caused->mapFromGlobal(e->globalPos());
936#ifndef QT_NO_MENUBAR
937 if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) {
938 passOnEvent = mb->rect().contains(cpos);
939 } else
940#endif
941 if (QMenu *m = qobject_cast<QMenu*>(caused)) {
942 passOnEvent = m->rect().contains(cpos);
943 next_widget = m->d_func()->causedPopup.widget;
944 }
945 if (passOnEvent) {
946 if(e->type() != QEvent::MouseButtonRelease || mouseDown == caused) {
947 QMouseEvent new_e(e->type(), cpos, e->button(), e->buttons(), e->modifiers());
948 QApplication::sendEvent(caused, &new_e);
949 return true;
950 }
951 }
952 if (!next_widget)
953 break;
954 caused = next_widget;
955 }
956 return false;
957}
958
959void QMenuPrivate::activateCausedStack(const QList<QPointer<QWidget> > &causedStack, QAction *action, QAction::ActionEvent action_e, bool self)
960{
961 Q_ASSERT(!activationRecursionGuard);
962 activationRecursionGuard = true;
963#ifdef QT3_SUPPORT
964 const int actionId = q_func()->findIdForAction(action);
965#endif
966 if(self)
967 action->activate(action_e);
968
969 for(int i = 0; i < causedStack.size(); ++i) {
970 QPointer<QWidget> widget = causedStack.at(i);
971 if (!widget)
972 continue;
973 //fire
974 if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) {
975 widget = qmenu->d_func()->causedPopup.widget;
976 if (action_e == QAction::Trigger) {
977 emit qmenu->triggered(action);
978 } else if (action_e == QAction::Hover) {
979 emit qmenu->hovered(action);
980#ifdef QT3_SUPPORT
981 if (emitHighlighted) {
982 emit qmenu->highlighted(actionId);
983 emitHighlighted = false;
984 }
985#endif
986 }
987#ifndef QT_NO_MENUBAR
988 } else if (QMenuBar *qmenubar = qobject_cast<QMenuBar*>(widget)) {
989 if (action_e == QAction::Trigger) {
990 emit qmenubar->triggered(action);
991#ifdef QT3_SUPPORT
992 emit qmenubar->activated(actionId);
993#endif
994 } else if (action_e == QAction::Hover) {
995 emit qmenubar->hovered(action);
996#ifdef QT3_SUPPORT
997 if (emitHighlighted) {
998 emit qmenubar->highlighted(actionId);
999 emitHighlighted = false;
1000 }
1001#endif
1002 }
1003 break; //nothing more..
1004#endif
1005 }
1006 }
1007 activationRecursionGuard = false;
1008}
1009
1010void QMenuPrivate::activateAction(QAction *action, QAction::ActionEvent action_e, bool self)
1011{
1012 Q_Q(QMenu);
1013#ifndef QT_NO_WHATSTHIS
1014 bool inWhatsThisMode = QWhatsThis::inWhatsThisMode();
1015#endif
1016 if (!action || !q->isEnabled()
1017 || (action_e == QAction::Trigger
1018#ifndef QT_NO_WHATSTHIS
1019 && !inWhatsThisMode
1020#endif
1021 && (action->isSeparator() ||!action->isEnabled())))
1022 return;
1023
1024 /* I have to save the caused stack here because it will be undone after popup execution (ie in the hide).
1025 Then I iterate over the list to actually send the events. --Sam
1026 */
1027 const QList<QPointer<QWidget> > causedStack = calcCausedStack();
1028 if (action_e == QAction::Trigger) {
1029#ifndef QT_NO_WHATSTHIS
1030 if (!inWhatsThisMode)
1031 actionAboutToTrigger = action;
1032#endif
1033
1034 if (q->testAttribute(Qt::WA_DontShowOnScreen)) {
1035 hideUpToMenuBar();
1036 } else {
1037 for(QWidget *widget = qApp->activePopupWidget(); widget; ) {
1038 if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) {
1039 if(qmenu == q)
1040 hideUpToMenuBar();
1041 widget = qmenu->d_func()->causedPopup.widget;
1042 } else {
1043 break;
1044 }
1045 }
1046 }
1047
1048#ifndef QT_NO_WHATSTHIS
1049 if (inWhatsThisMode) {
1050 QString s = action->whatsThis();
1051 if (s.isEmpty())
1052 s = whatsThis;
1053 QWhatsThis::showText(q->mapToGlobal(actionRect(action).center()), s, q);
1054 return;
1055 }
1056#endif
1057 }
1058
1059
1060 activateCausedStack(causedStack, action, action_e, self);
1061
1062
1063 if (action_e == QAction::Hover) {
1064#ifndef QT_NO_ACCESSIBILITY
1065 if (QAccessible::isActive()) {
1066 int actionIndex = indexOf(action) + 1;
1067 QAccessible::updateAccessibility(q, actionIndex, QAccessible::Focus);
1068 QAccessible::updateAccessibility(q, actionIndex, QAccessible::Selection);
1069 }
1070#endif
1071 QWidget *w = causedPopup.widget;
1072 while (QMenu *m = qobject_cast<QMenu*>(w))
1073 w = m->d_func()->causedPopup.widget;
1074 action->showStatusText(w);
1075 } else {
1076 actionAboutToTrigger = 0;
1077 }
1078}
1079
1080void QMenuPrivate::_q_actionTriggered()
1081{
1082 Q_Q(QMenu);
1083 if (QAction *action = qobject_cast<QAction *>(q->sender())) {
1084#ifdef QT3_SUPPORT
1085 //we store it here because the action might be deleted/changed by connected slots
1086 const int id = q->findIdForAction(action);
1087#endif
1088 emit q->triggered(action);
1089#ifdef QT3_SUPPORT
1090 emit q->activated(id);
1091#endif
1092
1093 if (!activationRecursionGuard) {
1094 //in case the action has not been activated by the mouse
1095 //we check the parent hierarchy
1096 QList< QPointer<QWidget> > list;
1097 for(QWidget *widget = q->parentWidget(); widget; ) {
1098 if (qobject_cast<QMenu*>(widget)
1099#ifndef QT_NO_MENUBAR
1100 || qobject_cast<QMenuBar*>(widget)
1101#endif
1102 ) {
1103 list.append(widget);
1104 widget = widget->parentWidget();
1105 } else {
1106 break;
1107 }
1108 }
1109 activateCausedStack(list, action, QAction::Trigger, false);
1110 }
1111 }
1112}
1113
1114void QMenuPrivate::_q_actionHovered()
1115{
1116 Q_Q(QMenu);
1117 if (QAction * action = qobject_cast<QAction *>(q->sender())) {
1118#ifdef QT3_SUPPORT
1119 //we store it here because the action might be deleted/changed by connected slots
1120 const int id = q->findIdForAction(action);
1121#endif
1122 emit q->hovered(action);
1123#ifdef QT3_SUPPORT
1124 if (emitHighlighted) {
1125 emit q->highlighted(id);
1126 emitHighlighted = false;
1127 }
1128#endif
1129 }
1130}
1131
1132bool QMenuPrivate::hasMouseMoved(const QPoint &globalPos)
1133{
1134 //determines if the mouse has moved (ie its intial position has
1135 //changed by more than QApplication::startDragDistance()
1136 //or if there were at least 6 mouse motions)
1137 return motions > 6 ||
1138 QApplication::startDragDistance() < (mousePopupPos - globalPos).manhattanLength();
1139}
1140
1141
1142/*!
1143 Initialize \a option with the values from this menu and information from \a action. This method
1144 is useful for subclasses when they need a QStyleOptionMenuItem, but don't want
1145 to fill in all the information themselves.
1146
1147 \sa QStyleOption::initFrom() QMenuBar::initStyleOption()
1148*/
1149void QMenu::initStyleOption(QStyleOptionMenuItem *option, const QAction *action) const
1150{
1151 if (!option || !action)
1152 return;
1153
1154 Q_D(const QMenu);
1155 option->initFrom(this);
1156 option->palette = palette();
1157 option->state = QStyle::State_None;
1158
1159 if (window()->isActiveWindow())
1160 option->state |= QStyle::State_Active;
1161 if (isEnabled() && action->isEnabled()
1162 && (!action->menu() || action->menu()->isEnabled()))
1163 option->state |= QStyle::State_Enabled;
1164 else
1165 option->palette.setCurrentColorGroup(QPalette::Disabled);
1166
1167 option->font = action->font();
1168
1169 if (d->currentAction && d->currentAction == action && !d->currentAction->isSeparator()) {
1170 option->state |= QStyle::State_Selected
1171 | (d->mouseDown ? QStyle::State_Sunken : QStyle::State_None);
1172 }
1173
1174 option->menuHasCheckableItems = d->hasCheckableItems;
1175 if (!action->isCheckable()) {
1176 option->checkType = QStyleOptionMenuItem::NotCheckable;
1177 } else {
1178 option->checkType = (action->actionGroup() && action->actionGroup()->isExclusive())
1179 ? QStyleOptionMenuItem::Exclusive : QStyleOptionMenuItem::NonExclusive;
1180 option->checked = action->isChecked();
1181 }
1182 if (action->menu())
1183 option->menuItemType = QStyleOptionMenuItem::SubMenu;
1184 else if (action->isSeparator())
1185 option->menuItemType = QStyleOptionMenuItem::Separator;
1186 else if (d->defaultAction == action)
1187 option->menuItemType = QStyleOptionMenuItem::DefaultItem;
1188 else
1189 option->menuItemType = QStyleOptionMenuItem::Normal;
1190 if (action->isIconVisibleInMenu())
1191 option->icon = action->icon();
1192 QString textAndAccel = action->text();
1193#ifndef QT_NO_SHORTCUT
1194 if (textAndAccel.indexOf(QLatin1Char('\t')) == -1) {
1195 QKeySequence seq = action->shortcut();
1196 if (!seq.isEmpty())
1197 textAndAccel += QLatin1Char('\t') + QString(seq);
1198 }
1199#endif
1200 option->text = textAndAccel;
1201 option->tabWidth = d->tabWidth;
1202 option->maxIconWidth = d->maxIconWidth;
1203 option->menuRect = rect();
1204}
1205
1206/*!
1207 \class QMenu
1208 \brief The QMenu class provides a menu widget for use in menu
1209 bars, context menus, and other popup menus.
1210
1211 \ingroup application
1212 \ingroup basicwidgets
1213 \mainclass
1214
1215 A menu widget is a selection menu. It can be either a pull-down
1216 menu in a menu bar or a standalone context menu. Pull-down menus
1217 are shown by the menu bar when the user clicks on the respective
1218 item or presses the specified shortcut key. Use
1219 QMenuBar::addMenu() to insert a menu into a menu bar. Context
1220 menus are usually invoked by some special keyboard key or by
1221 right-clicking. They can be executed either asynchronously with
1222 popup() or synchronously with exec(). Menus can also be invoked in
1223 response to button presses; these are just like context menus
1224 except for how they are invoked.
1225
1226 \raw HTML
1227 <table align="center" cellpadding="0">
1228 <tr>
1229 <td>
1230 \endraw
1231 \inlineimage plastique-menu.png
1232 \raw HTML
1233 </td>
1234 <td>
1235 \endraw
1236 \inlineimage windowsxp-menu.png
1237 \raw HTML
1238 </td>
1239 <td>
1240 \endraw
1241 \inlineimage macintosh-menu.png
1242 \raw HTML
1243 </td>
1244
1245 </tr>
1246 <tr>
1247 <td colspan="3">
1248 \endraw
1249 A menu shown in \l{Plastique Style Widget Gallery}{Plastique widget style},
1250 \l{Windows XP Style Widget Gallery}{Windows XP widget style},
1251 and \l{Macintosh Style Widget Gallery}{Macintosh widget style}.
1252 \raw HTML
1253 </td>
1254 </tr>
1255 </table>
1256 \endraw
1257
1258 \section1 Actions
1259
1260 A menu consists of a list of action items. Actions are added with
1261 the addAction(), addActions() and insertAction() functions. An action
1262 is represented vertically and rendered by QStyle. In addition, actions
1263 can have a text label, an optional icon drawn on the very left side,
1264 and shortcut key sequence such as "Ctrl+X".
1265
1266 The existing actions held by a menu can be found with actions().
1267
1268 There are four kinds of action items: separators, actions that
1269 show a submenu, widgets, and actions that perform an action.
1270 Separators are inserted with addSeparator(), submenus with addMenu(),
1271 and all other items are considered action items.
1272
1273 When inserting action items you usually specify a receiver and a
1274 slot. The receiver will be notifed whenever the item is
1275 \l{QAction::triggered()}{triggered()}. In addition, QMenu provides
1276 two signals, activated() and highlighted(), which signal the
1277 QAction that was triggered from the menu.
1278
1279 You clear a menu with clear() and remove individual action items
1280 with removeAction().
1281
1282 A QMenu can also provide a tear-off menu. A tear-off menu is a
1283 top-level window that contains a copy of the menu. This makes it
1284 possible for the user to "tear off" frequently used menus and
1285 position them in a convenient place on the screen. If you want
1286 this functionality for a particular menu, insert a tear-off handle
1287 with setTearOffEnabled(). When using tear-off menus, bear in mind
1288 that the concept isn't typically used on Microsoft Windows so
1289 some users may not be familiar with it. Consider using a QToolBar
1290 instead.
1291
1292 Widgets can be inserted into menus with the QWidgetAction class.
1293 Instances of this class are used to hold widgets, and are inserted
1294 into menus with the addAction() overload that takes a QAction.
1295
1296 Conversely, actions can be added to widgets with the addAction(),
1297 addActions() and insertAction() functions.
1298
1299 \section1 QMenu on Qt for Windows CE
1300
1301 If a menu is integrated into the native menubar on Windows Mobile we
1302 do not support the signals: aboutToHide (), aboutToShow () and hovered ().
1303 It is not possible to display an icon in a native menu on Windows Mobile.
1304
1305 See the \l{mainwindows/menus}{Menus} example for an example of how
1306 to use QMenuBar and QMenu in your application.
1307
1308 \bold{Important inherited functions:} addAction(), removeAction(), clear(),
1309 addSeparator(), and addMenu().
1310
1311 \sa QMenuBar, {fowler}{GUI Design Handbook: Menu, Drop-Down and Pop-Up},
1312 {Application Example}, {Menus Example}, {Recent Files Example}
1313*/
1314
1315
1316/*!
1317 Constructs a menu with parent \a parent.
1318
1319 Although a popup menu is always a top-level widget, if a parent is
1320 passed the popup menu will be deleted when that parent is
1321 destroyed (as with any other QObject).
1322*/
1323QMenu::QMenu(QWidget *parent)
1324 : QWidget(*new QMenuPrivate, parent, Qt::Popup)
1325{
1326 Q_D(QMenu);
1327 d->init();
1328}
1329
1330/*!
1331 Constructs a menu with a \a title and a \a parent.
1332
1333 Although a popup menu is always a top-level widget, if a parent is
1334 passed the popup menu will be deleted when that parent is
1335 destroyed (as with any other QObject).
1336
1337 \sa title
1338*/
1339QMenu::QMenu(const QString &title, QWidget *parent)
1340 : QWidget(*new QMenuPrivate, parent, Qt::Popup)
1341{
1342 Q_D(QMenu);
1343 d->init();
1344 d->menuAction->setText(title);
1345}
1346
1347/*! \internal
1348 */
1349QMenu::QMenu(QMenuPrivate &dd, QWidget *parent)
1350 : QWidget(dd, parent, Qt::Popup)
1351{
1352 Q_D(QMenu);
1353 d->init();
1354}
1355
1356/*!
1357 Destroys the menu.
1358*/
1359QMenu::~QMenu()
1360{
1361 Q_D(QMenu);
1362 for (QHash<QAction *, QWidget *>::ConstIterator item = d->widgetItems.constBegin(),
1363 end = d->widgetItems.constEnd(); item != end; ++item) {
1364 QWidgetAction *action = static_cast<QWidgetAction *>(item.key());
1365 QWidget *widget = item.value();
1366 if (action && widget)
1367 action->releaseWidget(widget);
1368 }
1369 d->widgetItems.clear();
1370
1371 if (d->eventLoop)
1372 d->eventLoop->exit();
1373 if (d->tornPopup)
1374 d->tornPopup->close();
1375}
1376
1377/*!
1378 \overload
1379
1380 This convenience function creates a new action with \a text.
1381 The function adds the newly created action to the menu's
1382 list of actions, and returns it.
1383
1384 \sa QWidget::addAction()
1385*/
1386QAction *QMenu::addAction(const QString &text)
1387{
1388 QAction *ret = new QAction(text, this);
1389 addAction(ret);
1390 return ret;
1391}
1392
1393/*!
1394 \overload
1395
1396 This convenience function creates a new action with an \a icon
1397 and some \a text. The function adds the newly created action to
1398 the menu's list of actions, and returns it.
1399
1400 \sa QWidget::addAction()
1401*/
1402QAction *QMenu::addAction(const QIcon &icon, const QString &text)
1403{
1404 QAction *ret = new QAction(icon, text, this);
1405 addAction(ret);
1406 return ret;
1407}
1408
1409/*!
1410 \overload
1411
1412 This convenience function creates a new action with the text \a
1413 text and an optional shortcut \a shortcut. The action's
1414 \l{QAction::triggered()}{triggered()} signal is connected to the
1415 \a receiver's \a member slot. The function adds the newly created
1416 action to the menu's list of actions and returns it.
1417
1418 \sa QWidget::addAction()
1419*/
1420QAction *QMenu::addAction(const QString &text, const QObject *receiver, const char* member, const QKeySequence &shortcut)
1421{
1422 QAction *action = new QAction(text, this);
1423#ifdef QT_NO_SHORTCUT
1424 Q_UNUSED(shortcut);
1425#else
1426 action->setShortcut(shortcut);
1427#endif
1428 QObject::connect(action, SIGNAL(triggered(bool)), receiver, member);
1429 addAction(action);
1430 return action;
1431}
1432
1433/*!
1434 \overload
1435
1436 This convenience function creates a new action with an \a icon and
1437 some \a text and an optional shortcut \a shortcut. The action's
1438 \l{QAction::triggered()}{triggered()} signal is connected to the
1439 \a member slot of the \a receiver object. The function adds the
1440 newly created action to the menu's list of actions, and returns it.
1441
1442 \sa QWidget::addAction()
1443*/
1444QAction *QMenu::addAction(const QIcon &icon, const QString &text, const QObject *receiver,
1445 const char* member, const QKeySequence &shortcut)
1446{
1447 QAction *action = new QAction(icon, text, this);
1448#ifdef QT_NO_SHORTCUT
1449 Q_UNUSED(shortcut);
1450#else
1451 action->setShortcut(shortcut);
1452#endif
1453 QObject::connect(action, SIGNAL(triggered(bool)), receiver, member);
1454 addAction(action);
1455 return action;
1456}
1457
1458/*!
1459 This convenience function adds \a menu as a submenu to this menu.
1460 It returns \a menu's menuAction(). This menu does not take
1461 ownership of \a menu.
1462
1463 \sa QWidget::addAction() QMenu::menuAction()
1464*/
1465QAction *QMenu::addMenu(QMenu *menu)
1466{
1467 QAction *action = menu->menuAction();
1468 addAction(action);
1469 return action;
1470}
1471
1472/*!
1473 Appends a new QMenu with \a title to the menu. The menu
1474 takes ownership of the menu. Returns the new menu.
1475
1476 \sa QWidget::addAction() QMenu::menuAction()
1477*/
1478QMenu *QMenu::addMenu(const QString &title)
1479{
1480 QMenu *menu = new QMenu(title, this);
1481 addAction(menu->menuAction());
1482 return menu;
1483}
1484
1485/*!
1486 Appends a new QMenu with \a icon and \a title to the menu. The menu
1487 takes ownership of the menu. Returns the new menu.
1488
1489 \sa QWidget::addAction() QMenu::menuAction()
1490*/
1491QMenu *QMenu::addMenu(const QIcon &icon, const QString &title)
1492{
1493 QMenu *menu = new QMenu(title, this);
1494 menu->setIcon(icon);
1495 addAction(menu->menuAction());
1496 return menu;
1497}
1498
1499/*!
1500 This convenience function creates a new separator action, i.e. an
1501 action with QAction::isSeparator() returning true, and adds the new
1502 action to this menu's list of actions. It returns the newly
1503 created action.
1504
1505 \sa QWidget::addAction()
1506*/
1507QAction *QMenu::addSeparator()
1508{
1509 QAction *action = new QAction(this);
1510 action->setSeparator(true);
1511 addAction(action);
1512 return action;
1513}
1514
1515/*!
1516 This convenience function inserts \a menu before action \a before
1517 and returns the menus menuAction().
1518
1519 \sa QWidget::insertAction(), addMenu()
1520*/
1521QAction *QMenu::insertMenu(QAction *before, QMenu *menu)
1522{
1523 QAction *action = menu->menuAction();
1524 insertAction(before, action);
1525 return action;
1526}
1527
1528/*!
1529 This convenience function creates a new separator action, i.e. an
1530 action with QAction::isSeparator() returning true. The function inserts
1531 the newly created action into this menu's list of actions before
1532 action \a before and returns it.
1533
1534 \sa QWidget::insertAction(), addSeparator()
1535*/
1536QAction *QMenu::insertSeparator(QAction *before)
1537{
1538 QAction *action = new QAction(this);
1539 action->setSeparator(true);
1540 insertAction(before, action);
1541 return action;
1542}
1543
1544/*!
1545 This will set the default action to \a act. The default action may
1546 have a visual queue depending on the current QStyle. A default
1547 action is usually meant to indicate what will defaultly happen on a
1548 drop, as shown in a context menu.
1549
1550 \sa defaultAction()
1551*/
1552void QMenu::setDefaultAction(QAction *act)
1553{
1554 d_func()->defaultAction = act;
1555}
1556
1557/*!
1558 Returns the current default action.
1559
1560 \sa setDefaultAction()
1561*/
1562QAction *QMenu::defaultAction() const
1563{
1564 return d_func()->defaultAction;
1565}
1566
1567/*!
1568 \property QMenu::tearOffEnabled
1569 \brief whether the menu supports being torn off
1570
1571 When true, the menu contains a special tear-off item (often shown as a dashed
1572 line at the top of the menu) that creates a copy of the menu when it is
1573 triggered.
1574
1575 This "torn-off" copy lives in a separate window. It contains the same menu
1576 items as the original menu, with the exception of the tear-off handle.
1577
1578 By default, this property is false.
1579*/
1580void QMenu::setTearOffEnabled(bool b)
1581{
1582 Q_D(QMenu);
1583 if (d->tearoff == b)
1584 return;
1585 if (!b && d->tornPopup)
1586 d->tornPopup->close();
1587 d->tearoff = b;
1588
1589 d->itemsDirty = true;
1590 if (isVisible())
1591 resize(sizeHint());
1592}
1593
1594bool QMenu::isTearOffEnabled() const
1595{
1596 return d_func()->tearoff;
1597}
1598
1599/*!
1600 When a menu is torn off a second menu is shown to display the menu
1601 contents in a new window. When the menu is in this mode and the menu
1602 is visible returns true; otherwise false.
1603
1604 \sa hideTearOffMenu() isTearOffEnabled()
1605*/
1606bool QMenu::isTearOffMenuVisible() const
1607{
1608 if (d_func()->tornPopup)
1609 return d_func()->tornPopup->isVisible();
1610 return false;
1611}
1612
1613/*!
1614 This function will forcibly hide the torn off menu making it
1615 disappear from the users desktop.
1616
1617 \sa isTearOffMenuVisible() isTearOffEnabled()
1618*/
1619void QMenu::hideTearOffMenu()
1620{
1621 if (d_func()->tornPopup)
1622 d_func()->tornPopup->close();
1623}
1624
1625
1626/*!
1627 Sets the currently highlighted action to \a act.
1628*/
1629void QMenu::setActiveAction(QAction *act)
1630{
1631 Q_D(QMenu);
1632 d->setCurrentAction(act, 0);
1633 if (d->scroll)
1634 d->scrollMenu(act, QMenuPrivate::QMenuScroller::ScrollCenter);
1635}
1636
1637
1638/*!
1639 Returns the currently highlighted action, or 0 if no
1640 action is currently highlighted.
1641*/
1642QAction *QMenu::activeAction() const
1643{
1644 return d_func()->currentAction;
1645}
1646
1647/*!
1648 \since 4.2
1649
1650 Returns true if there are no visible actions inserted into the menu, false
1651 otherwise.
1652
1653 \sa QWidget::actions()
1654*/
1655
1656bool QMenu::isEmpty() const
1657{
1658 bool ret = true;
1659 for(int i = 0; ret && i < actions().count(); ++i) {
1660 const QAction *action = actions().at(i);
1661 if (!action->isSeparator() && action->isVisible()) {
1662 ret = false;
1663 }
1664 }
1665 return ret;
1666}
1667
1668/*!
1669 Removes all the menu's actions. Actions owned by the menu and not
1670 shown in any other widget are deleted.
1671
1672 \sa removeAction()
1673*/
1674void QMenu::clear()
1675{
1676 QList<QAction*> acts = actions();
1677 for(int i = 0; i < acts.size(); i++) {
1678 removeAction(acts[i]);
1679 if (acts[i]->parent() == this && acts[i]->d_func()->widgets.isEmpty())
1680 delete acts[i];
1681 }
1682}
1683
1684/*!
1685 If a menu does not fit on the screen it lays itself out so that it
1686 does fit. It is style dependent what layout means (for example, on
1687 Windows it will use multiple columns).
1688
1689 This functions returns the number of columns necessary.
1690*/
1691int QMenu::columnCount() const
1692{
1693 return d_func()->ncols;
1694}
1695
1696/*!
1697 Returns the item at \a pt; returns 0 if there is no item there.
1698*/
1699QAction *QMenu::actionAt(const QPoint &pt) const
1700{
1701 if (QAction *ret = d_func()->actionAt(pt))
1702 return ret;
1703 return 0;
1704}
1705
1706/*!
1707 Returns the geometry of action \a act.
1708*/
1709QRect QMenu::actionGeometry(QAction *act) const
1710{
1711 return d_func()->actionRect(act);
1712}
1713
1714/*!
1715 \reimp
1716*/
1717QSize QMenu::sizeHint() const
1718{
1719 Q_D(const QMenu);
1720 ensurePolished();
1721 QMap<QAction*, QRect> actionRects;
1722 QList<QAction*> actionList;
1723 d->calcActionRects(actionRects, actionList);
1724
1725 QSize s;
1726 QStyleOption opt(0);
1727 opt.rect = rect();
1728 opt.palette = palette();
1729 opt.state = QStyle::State_None;
1730 for (QMap<QAction*, QRect>::const_iterator i = actionRects.constBegin();
1731 i != actionRects.constEnd(); ++i) {
1732 if (i.value().bottom() > s.height())
1733 s.setHeight(i.value().y()+i.value().height());
1734 if (i.value().right() > s.width())
1735 s.setWidth(i.value().right());
1736 }
1737 if (d->tearoff)
1738 s.rheight() += style()->pixelMetric(QStyle::PM_MenuTearoffHeight, &opt, this);
1739 if (const int fw = style()->pixelMetric(QStyle::PM_MenuPanelWidth, &opt, this)) {
1740 s.rwidth() += fw*2;
1741 s.rheight() += fw*2;
1742 }
1743 // Note that the action rects calculated above already include
1744 // the top and left margins, so we only need to add margins for
1745 // the bottom and right.
1746 s.rwidth() += style()->pixelMetric(QStyle::PM_MenuHMargin, &opt, this);
1747 s.rheight() += style()->pixelMetric(QStyle::PM_MenuVMargin, &opt, this);
1748
1749 s += QSize(d->leftmargin + d->rightmargin, d->topmargin + d->bottommargin);
1750
1751 return style()->sizeFromContents(QStyle::CT_Menu, &opt,
1752 s.expandedTo(QApplication::globalStrut()), this);
1753}
1754
1755/*!
1756 Displays the menu so that the action \a atAction will be at the
1757 specified \e global position \a p. To translate a widget's local
1758 coordinates into global coordinates, use QWidget::mapToGlobal().
1759
1760 When positioning a menu with exec() or popup(), bear in mind that
1761 you cannot rely on the menu's current size(). For performance
1762 reasons, the menu adapts its size only when necessary, so in many
1763 cases, the size before and after the show is different. Instead,
1764 use sizeHint() which calculates the proper size depending on the
1765 menu's current contents.
1766
1767 \sa QWidget::mapToGlobal(), exec()
1768*/
1769void QMenu::popup(const QPoint &p, QAction *atAction)
1770{
1771 Q_D(QMenu);
1772 if (d->scroll) { //reset scroll state from last popup
1773 d->scroll->scrollOffset = 0;
1774 d->scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone;
1775 }
1776 d->tearoffHighlighted = 0;
1777 d->motions = 0;
1778 d->doChildEffects = true;
1779
1780#ifndef QT_NO_MENUBAR
1781 // if this menu is part of a chain attached to a QMenuBar, set the
1782 // _NET_WM_WINDOW_TYPE_DROPDOWN_MENU X11 window type
1783 QWidget* top = this;
1784 while (QMenu* m = qobject_cast<QMenu *>(top))
1785 top = m->d_func()->causedPopup.widget;
1786 setAttribute(Qt::WA_X11NetWmWindowTypeDropDownMenu, qobject_cast<QMenuBar *>(top) != 0);
1787#endif
1788
1789 ensurePolished(); // Get the right font
1790 emit aboutToShow();
1791 d->updateActions();
1792 QPoint pos = p;
1793 QSize size = sizeHint();
1794 QRect screen = d->popupGeometry(QApplication::desktop()->screenNumber(p));
1795 const int desktopFrame = style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, 0, this);
1796 bool adjustToDesktop = !window()->testAttribute(Qt::WA_DontShowOnScreen);
1797 if (d->ncols > 1) {
1798 pos.setY(screen.top()+desktopFrame);
1799 } else if (atAction) {
1800 for(int i=0, above_height=0; i<(int)d->actionList.count(); i++) {
1801 QAction *action = d->actionList.at(i);
1802 if (action == atAction) {
1803 int newY = pos.y()-above_height;
1804 if (d->scroll && newY < desktopFrame) {
1805 d->scroll->scrollFlags = d->scroll->scrollFlags
1806 | QMenuPrivate::QMenuScroller::ScrollUp;
1807 d->scroll->scrollOffset = newY;
1808 newY = desktopFrame;
1809 }
1810 pos.setY(newY);
1811
1812 if (d->scroll && d->scroll->scrollFlags != QMenuPrivate::QMenuScroller::ScrollNone
1813 && !style()->styleHint(QStyle::SH_Menu_FillScreenWithScroll, 0, this)) {
1814 int below_height = above_height + d->scroll->scrollOffset;
1815 for(int i2 = i; i2 < (int)d->actionList.count(); i2++)
1816 below_height += d->actionRects.value(d->actionList.at(i2)).height();
1817 size.setHeight(below_height);
1818 }
1819 break;
1820 } else {
1821 above_height += d->actionRects.value(action).height();
1822 }
1823 }
1824 }
1825
1826 QPoint mouse = QCursor::pos();
1827 d->mousePopupPos = mouse;
1828 const bool snapToMouse = (QRect(p.x()-3, p.y()-3, 6, 6).contains(mouse));
1829
1830 if (adjustToDesktop) {
1831 //handle popup falling "off screen"
1832 if (qApp->layoutDirection() == Qt::RightToLeft) {
1833 if(snapToMouse) //position flowing left from the mouse
1834 pos.setX(mouse.x()-size.width());
1835
1836 if (pos.x() < screen.left()+desktopFrame)
1837 pos.setX(qMax(p.x(), screen.left()+desktopFrame));
1838 if (pos.x()+size.width()-1 > screen.right()-desktopFrame)
1839 pos.setX(qMax(p.x()-size.width(), screen.right()-desktopFrame-size.width()+1));
1840 } else {
1841 if (pos.x()+size.width()-1 > screen.right()-desktopFrame)
1842 pos.setX(qMin(p.x()+size.width(), screen.right()-desktopFrame-size.width()+1));
1843 if (pos.x() < screen.left()+desktopFrame)
1844 pos.setX(qMax(p.x(), screen.left() + desktopFrame));
1845 }
1846 if (pos.y() + size.height() - 1 > screen.bottom() - desktopFrame) {
1847 if(snapToMouse)
1848 pos.setY(qMin(mouse.y() - (size.height() + desktopFrame), screen.bottom()-desktopFrame-size.height()+1));
1849 else
1850 pos.setY(qMax(p.y() - (size.height() + desktopFrame), screen.bottom()-desktopFrame-size.height()+1));
1851 } else if (pos.y() < screen.top() + desktopFrame) {
1852 pos.setY(screen.top() + desktopFrame);
1853 }
1854
1855 if (pos.y() < screen.top() + desktopFrame)
1856 pos.setY(screen.top() + desktopFrame);
1857 if (pos.y()+size.height()-1 > screen.bottom() - desktopFrame) {
1858 if (d->scroll) {
1859 d->scroll->scrollFlags |= uint(QMenuPrivate::QMenuScroller::ScrollDown);
1860 int y = qMax(screen.y(),pos.y());
1861 size.setHeight(screen.bottom()-(desktopFrame*2)-y);
1862 } else {
1863 // Too big for screen, bias to see bottom of menu (for some reason)
1864 pos.setY(screen.bottom()-size.height()+1);
1865 }
1866 }
1867 }
1868 setGeometry(QRect(pos, size));
1869#ifndef QT_NO_EFFECTS
1870 int hGuess = qApp->layoutDirection() == Qt::RightToLeft ? QEffects::LeftScroll : QEffects::RightScroll;
1871 int vGuess = QEffects::DownScroll;
1872 if (qApp->layoutDirection() == Qt::RightToLeft) {
1873 if ((snapToMouse && (pos.x() + size.width()/2 > mouse.x())) ||
1874 (qobject_cast<QMenu*>(d->causedPopup.widget) && pos.x() + size.width()/2 > d->causedPopup.widget->x()))
1875 hGuess = QEffects::RightScroll;
1876 } else {
1877 if ((snapToMouse && (pos.x() + size.width()/2 < mouse.x())) ||
1878 (qobject_cast<QMenu*>(d->causedPopup.widget) && pos.x() + size.width()/2 < d->causedPopup.widget->x()))
1879 hGuess = QEffects::LeftScroll;
1880 }
1881
1882#ifndef QT_NO_MENUBAR
1883 if ((snapToMouse && (pos.y() + size.height()/2 < mouse.y())) ||
1884 (qobject_cast<QMenuBar*>(d->causedPopup.widget) &&
1885 pos.y() + size.width()/2 < d->causedPopup.widget->mapToGlobal(d->causedPopup.widget->pos()).y()))
1886 vGuess = QEffects::UpScroll;
1887#endif
1888 if (QApplication::isEffectEnabled(Qt::UI_AnimateMenu)) {
1889 bool doChildEffects = true;
1890#ifndef QT_NO_MENUBAR
1891 if (QMenuBar *mb = qobject_cast<QMenuBar*>(d->causedPopup.widget)) {
1892 doChildEffects = mb->d_func()->doChildEffects;
1893 mb->d_func()->doChildEffects = false;
1894 } else
1895#endif
1896 if (QMenu *m = qobject_cast<QMenu*>(d->causedPopup.widget)) {
1897 doChildEffects = m->d_func()->doChildEffects;
1898 m->d_func()->doChildEffects = false;
1899 }
1900
1901 if (doChildEffects) {
1902 if (QApplication::isEffectEnabled(Qt::UI_FadeMenu))
1903 qFadeEffect(this);
1904 else if (d->causedPopup.widget)
1905 qScrollEffect(this, qobject_cast<QMenu*>(d->causedPopup.widget) ? hGuess : vGuess);
1906 else
1907 qScrollEffect(this, hGuess | vGuess);
1908 } else {
1909 // kill any running effect
1910 qFadeEffect(0);
1911 qScrollEffect(0);
1912
1913 show();
1914 }
1915 } else
1916#endif
1917 {
1918 show();
1919 }
1920
1921#ifndef QT_NO_ACCESSIBILITY
1922 QAccessible::updateAccessibility(this, 0, QAccessible::PopupMenuStart);
1923#endif
1924}
1925
1926/*!
1927 Executes this menu synchronously.
1928
1929 This is equivalent to \c{exec(pos())}.
1930
1931 This returns the triggered QAction in either the popup menu or one
1932 of its submenus, or 0 if no item was triggered (normally because
1933 the user pressed Esc).
1934
1935 In most situations you'll want to specify the position yourself,
1936 for example, the current mouse position:
1937 \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 0
1938 or aligned to a widget:
1939 \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 1
1940 or in reaction to a QMouseEvent *e:
1941 \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 2
1942*/
1943QAction *QMenu::exec()
1944{
1945 return exec(pos());
1946}
1947
1948
1949/*!
1950 \overload
1951
1952 Executes this menu synchronously.
1953
1954 Pops up the menu so that the action \a action will be at the
1955 specified \e global position \a p. To translate a widget's local
1956 coordinates into global coordinates, use QWidget::mapToGlobal().
1957
1958 This returns the triggered QAction in either the popup menu or one
1959 of its submenus, or 0 if no item was triggered (normally because
1960 the user pressed Esc).
1961
1962 Note that all signals are emitted as usual. If you connect a
1963 QAction to a slot and call the menu's exec(), you get the result
1964 both via the signal-slot connection and in the return value of
1965 exec().
1966
1967 Common usage is to position the menu at the current mouse
1968 position:
1969 \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 3
1970 or aligned to a widget:
1971 \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 4
1972 or in reaction to a QMouseEvent *e:
1973 \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 5
1974
1975 When positioning a menu with exec() or popup(), bear in mind that
1976 you cannot rely on the menu's current size(). For performance
1977 reasons, the menu adapts its size only when necessary. So in many
1978 cases, the size before and after the show is different. Instead,
1979 use sizeHint() which calculates the proper size depending on the
1980 menu's current contents.
1981
1982 \sa popup(), QWidget::mapToGlobal()
1983*/
1984QAction *QMenu::exec(const QPoint &p, QAction *action)
1985{
1986 Q_D(QMenu);
1987 createWinId();
1988 QEventLoop eventLoop;
1989 d->eventLoop = &eventLoop;
1990 popup(p, action);
1991
1992 QPointer<QObject> guard = this;
1993 (void) eventLoop.exec();
1994 if (guard.isNull())
1995 return 0;
1996
1997 action = d->syncAction;
1998 d->syncAction = 0;
1999 d->eventLoop = 0;
2000 return action;
2001}
2002
2003/*!
2004 \overload
2005
2006 Executes a menu synchronously.
2007
2008 The menu's actions are specified by the list of \a actions. The menu will