1
/****************************************************************************
3
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4
** Contact: http://www.qt-project.org/legal
6
** This file is part of the QtGui module of the Qt Toolkit.
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 Digia. For licensing terms and
14
** conditions see http://qt.digia.com/licensing. For further information
15
** use the contact form at http://qt.digia.com/contact-us.
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 2.1 as published by the Free Software
20
** Foundation and appearing in the file LICENSE.LGPL included in the
21
** packaging of this file. Please review the following information to
22
** ensure the GNU Lesser General Public License version 2.1 requirements
23
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25
** In addition, as a special exception, Digia gives you certain additional
26
** rights. These rights are described in the Digia Qt LGPL Exception
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29
** GNU General Public License Usage
30
** Alternatively, this file may be used under the terms of the GNU
31
** General Public License version 3.0 as published by the Free Software
32
** Foundation and appearing in the file LICENSE.GPL included in the
33
** packaging of this file. Please review the following information to
34
** ensure the GNU General Public License version 3.0 requirements will be
35
** met: http://www.gnu.org/copyleft/gpl.html.
40
****************************************************************************/
52
#include <qpa/qplatformtheme.h>
53
#include "qapplication.h"
54
#include "qdesktopwidget.h"
55
#ifndef QT_NO_ACCESSIBILITY
56
# include "qaccessible.h"
59
# include <private/qeffects_p.h>
61
#ifndef QT_NO_WHATSTHIS
62
# include <qwhatsthis.h>
66
#include "qmenubar_p.h"
67
#include "qwidgetaction.h"
68
#include "qtoolbutton.h"
69
#include "qpushbutton.h"
70
#include <private/qpushbutton_p.h>
71
#include <private/qaction_p.h>
72
#include <private/qguiapplication_p.h>
76
QMenu *QMenuPrivate::mouseDown = 0;
77
int QMenuPrivate::sloppyDelayTimer = 0;
80
// internal class used for the torn off popup
81
class QTornOffMenu : public QMenu
84
class QTornOffMenuPrivate : public QMenuPrivate
86
Q_DECLARE_PUBLIC(QMenu)
88
QTornOffMenuPrivate(QMenu *p) : causedMenu(p) {
90
causedPopup.widget = 0;
91
causedPopup.action = ((QTornOffMenu*)p)->d_func()->causedPopup.action;
92
causedStack = ((QTornOffMenu*)p)->d_func()->calcCausedStack();
94
QList<QPointer<QWidget> > calcCausedStack() const { return causedStack; }
95
QPointer<QMenu> causedMenu;
96
QList<QPointer<QWidget> > causedStack;
99
QTornOffMenu(QMenu *p) : QMenu(*(new QTornOffMenuPrivate(p)))
102
// make the torn-off menu a sibling of p (instead of a child)
103
QWidget *parentWidget = d->causedStack.isEmpty() ? p : d->causedStack.last();
104
if (parentWidget->parentWidget())
105
parentWidget = parentWidget->parentWidget();
106
setParent(parentWidget, Qt::Window | Qt::Tool);
107
setAttribute(Qt::WA_DeleteOnClose, true);
108
setAttribute(Qt::WA_X11NetWmWindowTypeMenu, true);
109
setWindowTitle(p->windowTitle());
110
setEnabled(p->isEnabled());
111
//QObject::connect(this, SIGNAL(triggered(QAction*)), this, SLOT(onTrigger(QAction*)));
112
//QObject::connect(this, SIGNAL(hovered(QAction*)), this, SLOT(onHovered(QAction*)));
113
QList<QAction*> items = p->actions();
114
for(int i = 0; i < items.count(); i++)
115
addAction(items.at(i));
117
void syncWithMenu(QMenu *menu, QActionEvent *act)
120
if(menu != d->causedMenu)
122
if (act->type() == QEvent::ActionAdded) {
123
insertAction(act->before(), act->action());
124
} else if (act->type() == QEvent::ActionRemoved)
125
removeAction(act->action());
127
void actionEvent(QActionEvent *e)
129
QMenu::actionEvent(e);
130
setFixedSize(sizeHint());
133
void onTrigger(QAction *action) { d_func()->activateAction(action, QAction::Trigger, false); }
134
void onHovered(QAction *action) { d_func()->activateAction(action, QAction::Hover, false); }
136
Q_DECLARE_PRIVATE(QTornOffMenu)
137
friend class QMenuPrivate;
140
void QMenuPrivate::init()
143
#ifndef QT_NO_WHATSTHIS
144
q->setAttribute(Qt::WA_CustomWhatsThis);
146
q->setAttribute(Qt::WA_X11NetWmWindowTypePopupMenu);
147
defaultMenuAction = menuAction = new QAction(q);
148
menuAction->d_func()->menu = q;
149
q->setMouseTracking(q->style()->styleHint(QStyle::SH_Menu_MouseTracking, 0, q));
150
if (q->style()->styleHint(QStyle::SH_Menu_Scrollable, 0, q)) {
151
scroll = new QMenuPrivate::QMenuScroller;
152
scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone;
155
platformMenu = QGuiApplicationPrivate::platformTheme()->createPlatformMenu();
157
QObject::connect(platformMenu, SIGNAL(aboutToShow()), q, SIGNAL(aboutToShow()));
158
QObject::connect(platformMenu, SIGNAL(aboutToHide()), q, SIGNAL(aboutToHide()));
162
int QMenuPrivate::scrollerHeight() const
165
return qMax(QApplication::globalStrut().height(), q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q));
168
//Windows and KDE allows menus to cover the taskbar, while GNOME and Mac don't
169
QRect QMenuPrivate::popupGeometry(const QWidget *widget) const
171
if (QGuiApplicationPrivate::platformTheme() &&
172
QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::UseFullScreenForPopupMenu).toBool()) {
173
return QApplication::desktop()->screenGeometry(widget);
175
return QApplication::desktop()->availableGeometry(widget);
179
//Windows and KDE allows menus to cover the taskbar, while GNOME and Mac don't
180
QRect QMenuPrivate::popupGeometry(int screen) const
182
if (QGuiApplicationPrivate::platformTheme() &&
183
QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::UseFullScreenForPopupMenu).toBool()) {
184
return QApplication::desktop()->screenGeometry(screen);
186
return QApplication::desktop()->availableGeometry(screen);
190
QList<QPointer<QWidget> > QMenuPrivate::calcCausedStack() const
192
QList<QPointer<QWidget> > ret;
193
for(QWidget *widget = causedPopup.widget; widget; ) {
195
if (QTornOffMenu *qtmenu = qobject_cast<QTornOffMenu*>(widget))
196
ret += qtmenu->d_func()->causedStack;
197
if (QMenu *qmenu = qobject_cast<QMenu*>(widget))
198
widget = qmenu->d_func()->causedPopup.widget;
205
void QMenuPrivate::updateActionRects() const
208
updateActionRects(popupGeometry(q));
211
void QMenuPrivate::updateActionRects(const QRect &screen) const
219
//let's reinitialize the buffer
220
actionRects.resize(actions.count());
221
actionRects.fill(QRect());
223
int lastVisibleAction = getLastVisibleAction();
225
int max_column_width = 0,
226
dh = screen.height(),
228
QStyle *style = q->style();
231
const int hmargin = style->pixelMetric(QStyle::PM_MenuHMargin, &opt, q),
232
vmargin = style->pixelMetric(QStyle::PM_MenuVMargin, &opt, q),
233
icone = style->pixelMetric(QStyle::PM_SmallIconSize, &opt, q);
234
const int fw = style->pixelMetric(QStyle::PM_MenuPanelWidth, &opt, q);
235
const int deskFw = style->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, &opt, q);
236
const int tearoffHeight = tearoff ? style->pixelMetric(QStyle::PM_MenuTearoffHeight, &opt, q) : 0;
238
//for compatibility now - will have to refactor this away
241
hasCheckableItems = false;
245
for (int i = 0; i < actions.count(); ++i) {
246
QAction *action = actions.at(i);
247
if (action->isSeparator() || !action->isVisible() || widgetItems.contains(action))
250
hasCheckableItems |= action->isCheckable();
251
QIcon is = action->icon();
253
maxIconWidth = qMax<uint>(maxIconWidth, icone + 4);
258
QFontMetrics qfm = q->fontMetrics();
259
bool previousWasSeparator = true; // this is true to allow removing the leading separators
260
for(int i = 0; i <= lastVisibleAction; i++) {
261
QAction *action = actions.at(i);
263
if (!action->isVisible() ||
264
(collapsibleSeparators && previousWasSeparator && action->isSeparator()))
265
continue; // we continue, this action will get an empty QRect
267
previousWasSeparator = action->isSeparator();
269
//let the style modify the above size..
270
QStyleOptionMenuItem opt;
271
q->initStyleOption(&opt, action);
272
const QFontMetrics &fm = opt.fontMetrics;
275
if (QWidget *w = widgetItems.value(action)) {
276
sz = w->sizeHint().expandedTo(w->minimumSize()).expandedTo(w->minimumSizeHint()).boundedTo(w->maximumSize());
278
//calc what I think the size is..
279
if (action->isSeparator()) {
282
QString s = action->text();
283
int t = s.indexOf(QLatin1Char('\t'));
285
tabWidth = qMax(int(tabWidth), qfm.width(s.mid(t+1)));
287
#ifndef QT_NO_SHORTCUT
289
QKeySequence seq = action->shortcut();
291
tabWidth = qMax(int(tabWidth), qfm.width(seq.toString(QKeySequence::NativeText)));
294
sz.setWidth(fm.boundingRect(QRect(), Qt::TextSingleLine | Qt::TextShowMnemonic, s).width());
295
sz.setHeight(qMax(fm.height(), qfm.height()));
297
QIcon is = action->icon();
299
QSize is_sz = QSize(icone, icone);
300
if (is_sz.height() > sz.height())
301
sz.setHeight(is_sz.height());
304
sz = style->sizeFromContents(QStyle::CT_MenuItem, &opt, sz, q);
309
max_column_width = qMax(max_column_width, sz.width());
312
y+sz.height()+vmargin > dh - (deskFw * 2)) {
318
actionRects[i] = QRect(0, 0, sz.width(), sz.height());
322
max_column_width += tabWidth; //finally add in the tab width
323
const int sfcMargin = style->sizeFromContents(QStyle::CT_Menu, &opt, QApplication::globalStrut(), q).width() - QApplication::globalStrut().width();
324
const int min_column_width = q->minimumWidth() - (sfcMargin + leftmargin + rightmargin + 2 * (fw + hmargin));
325
max_column_width = qMax(min_column_width, max_column_width);
328
const int base_y = vmargin + fw + topmargin +
329
(scroll ? scroll->scrollOffset : 0) +
331
int x = hmargin + fw + leftmargin;
334
for(int i = 0; i < actions.count(); i++) {
335
QRect &rect = actionRects[i];
339
y+rect.height() > dh - deskFw * 2) {
340
x += max_column_width + hmargin;
343
rect.translate(x, y); //move
344
rect.setWidth(max_column_width); //uniform width
346
//we need to update the widgets geometry
347
if (QWidget *widget = widgetItems.value(actions.at(i))) {
348
widget->setGeometry(rect);
349
widget->setVisible(actions.at(i)->isVisible());
357
QSize QMenuPrivate::adjustMenuSizeForScreen(const QRect &screen)
360
QSize ret = screen.size();
362
updateActionRects(screen);
363
const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q);
364
ret.setWidth(actionRects.at(getLastVisibleAction()).right() + fw);
368
int QMenuPrivate::getLastVisibleAction() const
370
//let's try to get the last visible action
371
int lastVisibleAction = actions.count() - 1;
372
for (;lastVisibleAction >= 0; --lastVisibleAction) {
373
const QAction *action = actions.at(lastVisibleAction);
374
if (action->isVisible()) {
375
//removing trailing separators
376
if (action->isSeparator() && collapsibleSeparators)
381
return lastVisibleAction;
385
QRect QMenuPrivate::actionRect(QAction *act) const
387
int index = actions.indexOf(act);
393
//we found the action
394
return actionRects.at(index);
397
#if defined(Q_OS_MAC)
398
static const qreal MenuFadeTimeInSec = 0.150;
401
void QMenuPrivate::hideUpToMenuBar()
404
bool fadeMenus = q->style()->styleHint(QStyle::SH_Menu_FadeOutOnHide);
406
QWidget *caused = causedPopup.widget;
407
hideMenu(q); //hide after getting causedPopup
409
#ifndef QT_NO_MENUBAR
410
if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) {
411
mb->d_func()->setCurrentAction(0);
412
mb->d_func()->setKeyboardMode(false);
416
if (QMenu *m = qobject_cast<QMenu*>(caused)) {
417
caused = m->d_func()->causedPopup.widget;
418
if (!m->d_func()->tornoff)
419
hideMenu(m, fadeMenus);
420
if (!fadeMenus) // Mac doesn't clear the action until after hidden.
421
m->d_func()->setCurrentAction(0);
425
#if defined(Q_WS_MAC)
427
QEventLoop eventLoop;
428
QTimer::singleShot(int(MenuFadeTimeInSec * 1000), &eventLoop, SLOT(quit()));
429
QMacWindowFader::currentFader()->performFade();
437
void QMenuPrivate::hideMenu(QMenu *menu, bool justRegister)
441
#if !defined(QT_NO_EFFECTS)
442
menu->blockSignals(true);
444
// Flash item which is about to trigger (if any).
445
if (menu->style()->styleHint(QStyle::SH_Menu_FlashTriggeredItem)
446
&& currentAction && currentAction == actionAboutToTrigger
447
&& menu->actions().contains(currentAction)) {
448
QEventLoop eventLoop;
449
QAction *activeAction = currentAction;
451
menu->setActiveAction(0);
452
QTimer::singleShot(60, &eventLoop, SLOT(quit()));
455
// Select and wait 20 ms.
456
menu->setActiveAction(activeAction);
457
QTimer::singleShot(20, &eventLoop, SLOT(quit()));
462
if (menu->style()->styleHint(QStyle::SH_Menu_FadeOutOnHide)) {
464
// Should be something like: q->transitionWindow(Qt::FadeOutTransition, MenuFadeTimeInSec);
465
// Hopefully we'll integrate qt/research/windowtransitions into main before 4.4.
466
// Talk to Richard, Trenton or Bjoern.
467
#if defined(Q_WS_MAC)
469
QMacWindowFader::currentFader()->setFadeDuration(MenuFadeTimeInSec);
470
QMacWindowFader::currentFader()->registerWindowToFade(menu);
472
macWindowFade(qt_mac_window_for(menu), MenuFadeTimeInSec);
478
menu->blockSignals(false);
479
#endif // QT_NO_EFFECTS
484
void QMenuPrivate::popupAction(QAction *action, int delay, bool activateFirst)
487
if (action && action->isEnabled()) {
489
q->internalDelayedPopup();
490
else if (!menuDelayTimer.isActive() && (!action->menu() || !action->menu()->isVisible()))
491
menuDelayTimer.start(delay, q);
492
if (activateFirst && action->menu())
493
action->menu()->d_func()->setFirstActionActive();
494
} else if (QMenu *menu = activeMenu) { //hide the current item
500
void QMenuPrivate::setSyncAction()
503
QAction *current = currentAction;
504
if(current && (!current->isEnabled() || current->menu() || current->isSeparator()))
506
for(QWidget *caused = q; caused;) {
507
if (QMenu *m = qobject_cast<QMenu*>(caused)) {
508
caused = m->d_func()->causedPopup.widget;
509
if (m->d_func()->eventLoop)
510
m->d_func()->syncAction = current; // synchronous operation
518
void QMenuPrivate::setFirstActionActive()
522
for(int i = 0, saccum = 0; i < actions.count(); i++) {
523
const QRect &rect = actionRects.at(i);
526
if (scroll && scroll->scrollFlags & QMenuScroller::ScrollUp) {
527
saccum -= rect.height();
528
if (saccum > scroll->scrollOffset - scrollerHeight())
531
QAction *act = actions.at(i);
532
if (!act->isSeparator() &&
533
(q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q)
534
|| act->isEnabled())) {
535
setCurrentAction(act);
541
// popup == -1 means do not popup, 0 means immediately, others mean use a timer
542
void QMenuPrivate::setCurrentAction(QAction *action, int popup, SelectionReason reason, bool activateFirst)
545
tearoffHighlighted = 0;
546
// Reselect the currently active action in case mouse moved over other menu items when
547
// moving from sub menu action to sub menu (QTBUG-20094).
548
if (reason != SelectedFromKeyboard && action == currentAction && !(action && action->menu() && action->menu() != activeMenu)) {
549
if (QMenu *menu = qobject_cast<QMenu*>(causedPopup.widget)) {
550
if (causedPopup.action && menu->d_func()->activeMenu == q)
551
menu->d_func()->setCurrentAction(causedPopup.action, 0, reason, false);
557
q->update(actionRect(currentAction));
560
if (!sloppyRegion.isEmpty())
561
sloppyRegion = QRegion();
562
QMenu *hideActiveMenu = activeMenu;
563
#ifndef QT_NO_STATUSTIP
564
QAction *previousAction = currentAction;
567
currentAction = action;
569
if (!action->isSeparator()) {
570
activateAction(action, QAction::Hover);
572
hideActiveMenu = 0; //will be done "later"
573
// if the menu is visible then activate the required action,
574
// otherwise we just mark the action as currentAction
575
// and activate it when the menu will be popuped.
577
popupAction(currentAction, popup, activateFirst);
579
q->update(actionRect(action));
581
if (reason == SelectedFromKeyboard) {
582
QWidget *widget = widgetItems.value(action);
584
if (widget->focusPolicy() != Qt::NoFocus)
585
widget->setFocus(Qt::TabFocusReason);
587
//when the action has no QWidget, the QMenu itself should
589
// Since the menu is a pop-up, it uses the popup reason.
590
if (!q->hasFocus()) {
591
q->setFocus(Qt::PopupFocusReason);
595
} else { //action is a separator
597
hideActiveMenu = 0; //will be done "later"
599
#ifndef QT_NO_STATUSTIP
600
} else if (previousAction) {
601
previousAction->d_func()->showStatusText(topCausedWidget(), QString());
604
if (hideActiveMenu) {
606
#ifndef QT_NO_EFFECTS
607
// kill any running effect
611
hideMenu(hideActiveMenu);
615
//return the top causedPopup.widget that is not a QMenu
616
QWidget *QMenuPrivate::topCausedWidget() const
618
QWidget* top = causedPopup.widget;
619
while (QMenu* m = qobject_cast<QMenu *>(top))
620
top = m->d_func()->causedPopup.widget;
624
QAction *QMenuPrivate::actionAt(QPoint p) const
626
if (!q_func()->rect().contains(p)) //sanity check
629
for(int i = 0; i < actionRects.count(); i++) {
630
if (actionRects.at(i).contains(p))
631
return actions.at(i);
636
void QMenuPrivate::setOverrideMenuAction(QAction *a)
639
QObject::disconnect(menuAction, SIGNAL(destroyed()), q, SLOT(_q_overrideMenuActionDestroyed()));
642
QObject::connect(a, SIGNAL(destroyed()), q, SLOT(_q_overrideMenuActionDestroyed()));
643
} else { //we revert back to the default action created by the QMenu itself
644
menuAction = defaultMenuAction;
648
void QMenuPrivate::_q_overrideMenuActionDestroyed()
650
menuAction=defaultMenuAction;
654
void QMenuPrivate::updateLayoutDirection()
657
//we need to mimic the cause of the popup's layout direction
658
//to allow setting it on a mainwindow for example
659
//we call setLayoutDirection_helper to not overwrite a user-defined value
660
if (!q->testAttribute(Qt::WA_SetLayoutDirection)) {
661
if (QWidget *w = causedPopup.widget)
662
setLayoutDirection_helper(w->layoutDirection());
663
else if (QWidget *w = q->parentWidget())
664
setLayoutDirection_helper(w->layoutDirection());
666
setLayoutDirection_helper(QApplication::layoutDirection());
672
Returns the action associated with this menu.
674
QAction *QMenu::menuAction() const
676
return d_func()->menuAction;
680
\property QMenu::title
681
\brief The title of the menu
683
This is equivalent to the QAction::text property of the menuAction().
685
By default, this property contains an empty string.
687
QString QMenu::title() const
689
return d_func()->menuAction->text();
692
void QMenu::setTitle(const QString &text)
694
d_func()->menuAction->setText(text);
698
\property QMenu::icon
700
\brief The icon of the menu
702
This is equivalent to the QAction::icon property of the menuAction().
704
By default, if no icon is explicitly set, this property contains a null icon.
706
QIcon QMenu::icon() const
708
return d_func()->menuAction->icon();
711
void QMenu::setIcon(const QIcon &icon)
713
d_func()->menuAction->setIcon(icon);
717
//actually performs the scrolling
718
void QMenuPrivate::scrollMenu(QAction *action, QMenuScroller::ScrollLocation location, bool active)
721
if (!scroll || !scroll->scrollFlags)
725
const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp) ? scrollerHeight() : 0;
726
const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollerHeight() : 0;
727
const int vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q);
728
const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q);
730
if (location == QMenuScroller::ScrollTop) {
731
for(int i = 0, saccum = 0; i < actions.count(); i++) {
732
if (actions.at(i) == action) {
733
newOffset = topScroll - saccum;
736
saccum += actionRects.at(i).height();
739
for(int i = 0, saccum = 0; i < actions.count(); i++) {
740
saccum += actionRects.at(i).height();
741
if (actions.at(i) == action) {
742
if (location == QMenuScroller::ScrollCenter)
743
newOffset = ((q->height() / 2) - botScroll) - (saccum - topScroll);
745
newOffset = (q->height() - botScroll) - saccum;
753
//figure out which scroll flags
754
uint newScrollFlags = QMenuScroller::ScrollNone;
755
if (newOffset < 0) //easy and cheap one
756
newScrollFlags |= QMenuScroller::ScrollUp;
757
int saccum = newOffset;
758
for(int i = 0; i < actionRects.count(); i++) {
759
saccum += actionRects.at(i).height();
760
if (saccum > q->height()) {
761
newScrollFlags |= QMenuScroller::ScrollDown;
766
if (!(newScrollFlags & QMenuScroller::ScrollDown) && (scroll->scrollFlags & QMenuScroller::ScrollDown)) {
767
newOffset = q->height() - (saccum - newOffset) - fw*2 - vmargin; //last item at bottom
770
if (!(newScrollFlags & QMenuScroller::ScrollUp) && (scroll->scrollFlags & QMenuScroller::ScrollUp)) {
771
newOffset = 0; //first item at top
774
if (newScrollFlags & QMenuScroller::ScrollUp)
775
newOffset -= vmargin;
777
QRect screen = popupGeometry(q);
778
const int desktopFrame = q->style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, 0, q);
779
if (q->height() < screen.height()-(desktopFrame*2)-1) {
780
QRect geom = q->geometry();
781
if (newOffset > scroll->scrollOffset && (scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollUp)) { //scroll up
782
const int newHeight = geom.height()-(newOffset-scroll->scrollOffset);
783
if(newHeight > geom.height())
784
geom.setHeight(newHeight);
785
} else if(scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollDown) {
786
int newTop = geom.top() + (newOffset-scroll->scrollOffset);
787
if (newTop < desktopFrame+screen.top())
788
newTop = desktopFrame+screen.top();
789
if (newTop < geom.top()) {
792
newScrollFlags &= ~QMenuScroller::ScrollUp;
795
if (geom.bottom() > screen.bottom() - desktopFrame)
796
geom.setBottom(screen.bottom() - desktopFrame);
797
if (geom.top() < desktopFrame+screen.top())
798
geom.setTop(desktopFrame+screen.top());
799
if (geom != q->geometry()) {
801
if (newScrollFlags & QMenuScroller::ScrollDown &&
802
q->geometry().top() - geom.top() >= -newOffset)
803
newScrollFlags &= ~QMenuScroller::ScrollDown;
805
q->setGeometry(geom);
809
//actually update flags
810
const int delta = qMin(0, newOffset) - scroll->scrollOffset; //make sure the new offset is always negative
811
if (!itemsDirty && delta) {
812
//we've scrolled so we need to update the action rects
813
for (int i = 0; i < actionRects.count(); ++i) {
814
QRect ¤t = actionRects[i];
815
current.moveTop(current.top() + delta);
817
//we need to update the widgets geometry
818
if (QWidget *w = widgetItems.value(actions.at(i)))
819
w->setGeometry(current);
822
scroll->scrollOffset += delta;
823
scroll->scrollFlags = newScrollFlags;
825
setCurrentAction(action);
827
q->update(); //issue an update so we see all the new state..
830
void QMenuPrivate::scrollMenu(QMenuScroller::ScrollLocation location, bool active)
834
if(location == QMenuScroller::ScrollBottom) {
835
for(int i = actions.size()-1; i >= 0; --i) {
836
QAction *act = actions.at(i);
837
if (actionRects.at(i).isNull())
839
if (!act->isSeparator() &&
840
(q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q)
841
|| act->isEnabled())) {
842
if(scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown)
843
scrollMenu(act, QMenuPrivate::QMenuScroller::ScrollBottom, active);
845
setCurrentAction(act, /*popup*/-1, QMenuPrivate::SelectedFromKeyboard);
849
} else if(location == QMenuScroller::ScrollTop) {
850
for(int i = 0; i < actions.size(); ++i) {
851
QAction *act = actions.at(i);
852
if (actionRects.at(i).isNull())
854
if (!act->isSeparator() &&
855
(q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q)
856
|| act->isEnabled())) {
857
if(scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
858
scrollMenu(act, QMenuPrivate::QMenuScroller::ScrollTop, active);
860
setCurrentAction(act, /*popup*/-1, QMenuPrivate::SelectedFromKeyboard);
868
void QMenuPrivate::scrollMenu(QMenuScroller::ScrollDirection direction, bool page, bool active)
871
if (!scroll || !(scroll->scrollFlags & direction)) //not really possible...
874
const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp) ? scrollerHeight() : 0;
875
const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollerHeight() : 0;
876
const int vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q);
877
const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q);
878
const int offset = topScroll ? topScroll-vmargin : 0;
879
if (direction == QMenuScroller::ScrollUp) {
880
for(int i = 0, saccum = 0; i < actions.count(); i++) {
881
saccum -= actionRects.at(i).height();
882
if (saccum <= scroll->scrollOffset-offset) {
883
scrollMenu(actions.at(i), page ? QMenuScroller::ScrollBottom : QMenuScroller::ScrollTop, active);
887
} else if (direction == QMenuScroller::ScrollDown) {
888
bool scrolled = false;
889
for(int i = 0, saccum = 0; i < actions.count(); i++) {
890
const int iHeight = actionRects.at(i).height();
892
if (saccum <= scroll->scrollOffset-offset) {
893
const int scrollerArea = q->height() - botScroll - fw*2;
894
int visible = (scroll->scrollOffset-offset) - saccum;
895
for(i++ ; i < actions.count(); i++) {
896
visible += actionRects.at(i).height();
897
if (visible > scrollerArea - topScroll) {
899
scrollMenu(actions.at(i), page ? QMenuScroller::ScrollTop : QMenuScroller::ScrollBottom, active);
907
scroll->scrollFlags &= ~QMenuScroller::ScrollDown;
913
/* This is poor-mans eventfilters. This avoids the use of
914
eventFilter (which can be nasty for users of QMenuBar's). */
915
bool QMenuPrivate::mouseEventTaken(QMouseEvent *e)
918
QPoint pos = q->mapFromGlobal(e->globalPos());
919
if (scroll && !activeMenu) { //let the scroller "steal" the event
920
bool isScroll = false;
921
if (pos.x() >= 0 && pos.x() < q->width()) {
922
for(int dir = QMenuScroller::ScrollUp; dir <= QMenuScroller::ScrollDown; dir = dir << 1) {
923
if (scroll->scrollFlags & dir) {
924
if (dir == QMenuScroller::ScrollUp)
925
isScroll = (pos.y() <= scrollerHeight());
926
else if (dir == QMenuScroller::ScrollDown)
927
isScroll = (pos.y() >= q->height() - scrollerHeight());
929
scroll->scrollDirection = dir;
936
scroll->scrollTimer.start(50, q);
939
scroll->scrollTimer.stop();
943
if (tearoff) { //let the tear off thingie "steal" the event..
944
QRect tearRect(0, 0, q->width(), q->style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, q));
945
if (scroll && scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
946
tearRect.translate(0, scrollerHeight());
948
if (tearRect.contains(pos) && hasMouseMoved(e->globalPos())) {
950
tearoffHighlighted = 1;
951
if (e->type() == QEvent::MouseButtonRelease) {
953
tornPopup = new QTornOffMenu(q);
954
tornPopup->setGeometry(q->geometry());
960
tearoffHighlighted = 0;
963
if (q->frameGeometry().contains(e->globalPos())) //otherwise if the event is in our rect we want it..
966
for(QWidget *caused = causedPopup.widget; caused;) {
967
bool passOnEvent = false;
968
QWidget *next_widget = 0;
969
QPoint cpos = caused->mapFromGlobal(e->globalPos());
970
#ifndef QT_NO_MENUBAR
971
if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) {
972
passOnEvent = mb->rect().contains(cpos);
975
if (QMenu *m = qobject_cast<QMenu*>(caused)) {
976
passOnEvent = m->rect().contains(cpos);
977
next_widget = m->d_func()->causedPopup.widget;
980
if(e->type() != QEvent::MouseButtonRelease || mouseDown == caused) {
981
QMouseEvent new_e(e->type(), cpos, caused->mapTo(caused->topLevelWidget(), cpos), e->screenPos(),
982
e->button(), e->buttons(), e->modifiers());
983
QApplication::sendEvent(caused, &new_e);
989
caused = next_widget;
994
void QMenuPrivate::activateCausedStack(const QList<QPointer<QWidget> > &causedStack, QAction *action, QAction::ActionEvent action_e, bool self)
996
QBoolBlocker guard(activationRecursionGuard);
998
action->activate(action_e);
1000
for(int i = 0; i < causedStack.size(); ++i) {
1001
QPointer<QWidget> widget = causedStack.at(i);
1005
if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) {
1006
widget = qmenu->d_func()->causedPopup.widget;
1007
if (action_e == QAction::Trigger) {
1008
emit qmenu->triggered(action);
1009
} else if (action_e == QAction::Hover) {
1010
emit qmenu->hovered(action);
1012
#ifndef QT_NO_MENUBAR
1013
} else if (QMenuBar *qmenubar = qobject_cast<QMenuBar*>(widget)) {
1014
if (action_e == QAction::Trigger) {
1015
emit qmenubar->triggered(action);
1016
} else if (action_e == QAction::Hover) {
1017
emit qmenubar->hovered(action);
1019
break; //nothing more..
1025
void QMenuPrivate::activateAction(QAction *action, QAction::ActionEvent action_e, bool self)
1028
#ifndef QT_NO_WHATSTHIS
1029
bool inWhatsThisMode = QWhatsThis::inWhatsThisMode();
1031
if (!action || !q->isEnabled()
1032
|| (action_e == QAction::Trigger
1033
#ifndef QT_NO_WHATSTHIS
1036
&& (action->isSeparator() ||!action->isEnabled())))
1039
/* I have to save the caused stack here because it will be undone after popup execution (ie in the hide).
1040
Then I iterate over the list to actually send the events. --Sam
1042
const QList<QPointer<QWidget> > causedStack = calcCausedStack();
1043
if (action_e == QAction::Trigger) {
1044
#ifndef QT_NO_WHATSTHIS
1045
if (!inWhatsThisMode)
1046
actionAboutToTrigger = action;
1049
if (q->testAttribute(Qt::WA_DontShowOnScreen)) {
1052
for(QWidget *widget = QApplication::activePopupWidget(); widget; ) {
1053
if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) {
1056
widget = qmenu->d_func()->causedPopup.widget;
1063
#ifndef QT_NO_WHATSTHIS
1064
if (inWhatsThisMode) {
1065
QString s = action->whatsThis();
1068
QWhatsThis::showText(q->mapToGlobal(actionRect(action).center()), s, q);
1075
activateCausedStack(causedStack, action, action_e, self);
1078
if (action_e == QAction::Hover) {
1079
#ifndef QT_NO_ACCESSIBILITY
1080
if (QAccessible::isActive()) {
1081
int actionIndex = indexOf(action);
1082
QAccessibleEvent focusEvent(q, QAccessible::Focus);
1083
focusEvent.setChild(actionIndex);
1084
QAccessible::updateAccessibility(&focusEvent);
1085
QAccessibleEvent selectionEvent(q, QAccessible::Selection);
1086
focusEvent.setChild(actionIndex);
1087
QAccessible::updateAccessibility(&selectionEvent);
1090
action->showStatusText(topCausedWidget());
1092
actionAboutToTrigger = 0;
1096
void QMenuPrivate::_q_actionTriggered()
1099
if (QAction *action = qobject_cast<QAction *>(q->sender())) {
1100
QPointer<QAction> actionGuard = action;
1101
emit q->triggered(action);
1102
if (!activationRecursionGuard && actionGuard) {
1103
//in case the action has not been activated by the mouse
1104
//we check the parent hierarchy
1105
QList< QPointer<QWidget> > list;
1106
for(QWidget *widget = q->parentWidget(); widget; ) {
1107
if (qobject_cast<QMenu*>(widget)
1108
#ifndef QT_NO_MENUBAR
1109
|| qobject_cast<QMenuBar*>(widget)
1112
list.append(widget);
1113
widget = widget->parentWidget();
1118
activateCausedStack(list, action, QAction::Trigger, false);
1123
void QMenuPrivate::_q_actionHovered()
1126
if (QAction * action = qobject_cast<QAction *>(q->sender())) {
1127
emit q->hovered(action);
1131
bool QMenuPrivate::hasMouseMoved(const QPoint &globalPos)
1133
//determines if the mouse has moved (ie its initial position has
1134
//changed by more than QApplication::startDragDistance()
1135
//or if there were at least 6 mouse motions)
1136
return motions > 6 ||
1137
QApplication::startDragDistance() < (mousePopupPos - globalPos).manhattanLength();
1142
Initialize \a option with the values from this menu and information from \a action. This method
1143
is useful for subclasses when they need a QStyleOptionMenuItem, but don't want
1144
to fill in all the information themselves.
1146
\sa QStyleOption::initFrom(), QMenuBar::initStyleOption()
1148
void QMenu::initStyleOption(QStyleOptionMenuItem *option, const QAction *action) const
1150
if (!option || !action)
1154
option->initFrom(this);
1155
option->palette = palette();
1156
option->state = QStyle::State_None;
1158
if (window()->isActiveWindow())
1159
option->state |= QStyle::State_Active;
1160
if (isEnabled() && action->isEnabled()
1161
&& (!action->menu() || action->menu()->isEnabled()))
1162
option->state |= QStyle::State_Enabled;
1164
option->palette.setCurrentColorGroup(QPalette::Disabled);
1166
option->font = action->font().resolve(font());
1167
option->fontMetrics = QFontMetrics(option->font);
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);
1174
option->menuHasCheckableItems = d->hasCheckableItems;
1175
if (!action->isCheckable()) {
1176
option->checkType = QStyleOptionMenuItem::NotCheckable;
1178
option->checkType = (action->actionGroup() && action->actionGroup()->isExclusive())
1179
? QStyleOptionMenuItem::Exclusive : QStyleOptionMenuItem::NonExclusive;
1180
option->checked = action->isChecked();
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;
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();
1197
textAndAccel += QLatin1Char('\t') + seq.toString(QKeySequence::NativeText);
1200
option->text = textAndAccel;
1201
option->tabWidth = d->tabWidth;
1202
option->maxIconWidth = d->maxIconWidth;
1203
option->menuRect = rect();
1208
\brief The QMenu class provides a menu widget for use in menu
1209
bars, context menus, and other popup menus.
1211
\ingroup mainwindow-classes
1212
\ingroup basicwidgets
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.
1228
\li \inlineimage fusion-menu.png
1229
\li \inlineimage windowsxp-menu.png
1230
\li \inlineimage macintosh-menu.png
1232
\caption Fig. A menu shown in \l{Fusion Style Widget Gallery}{Fusion widget style},
1233
\l{Windows XP Style Widget Gallery}{Windows XP widget style},
1234
and \l{Macintosh Style Widget Gallery}{Macintosh widget style}.
1238
A menu consists of a list of action items. Actions are added with
1239
the addAction(), addActions() and insertAction() functions. An action
1240
is represented vertically and rendered by QStyle. In addition, actions
1241
can have a text label, an optional icon drawn on the very left side,
1242
and shortcut key sequence such as "Ctrl+X".
1244
The existing actions held by a menu can be found with actions().
1246
There are four kinds of action items: separators, actions that
1247
show a submenu, widgets, and actions that perform an action.
1248
Separators are inserted with addSeparator(), submenus with addMenu(),
1249
and all other items are considered action items.
1251
When inserting action items you usually specify a receiver and a
1252
slot. The receiver will be notifed whenever the item is
1253
\l{QAction::triggered()}{triggered()}. In addition, QMenu provides
1254
two signals, activated() and highlighted(), which signal the
1255
QAction that was triggered from the menu.
1257
You clear a menu with clear() and remove individual action items
1258
with removeAction().
1260
A QMenu can also provide a tear-off menu. A tear-off menu is a
1261
top-level window that contains a copy of the menu. This makes it
1262
possible for the user to "tear off" frequently used menus and
1263
position them in a convenient place on the screen. If you want
1264
this functionality for a particular menu, insert a tear-off handle
1265
with setTearOffEnabled(). When using tear-off menus, bear in mind
1266
that the concept isn't typically used on Microsoft Windows so
1267
some users may not be familiar with it. Consider using a QToolBar
1270
Widgets can be inserted into menus with the QWidgetAction class.
1271
Instances of this class are used to hold widgets, and are inserted
1272
into menus with the addAction() overload that takes a QAction.
1274
Conversely, actions can be added to widgets with the addAction(),
1275
addActions() and insertAction() functions.
1277
\warning To make QMenu visible on the screen, exec() or popup() should be
1278
used instead of show().
1280
\section1 QMenu on Qt for Windows CE
1282
If a menu is integrated into the native menubar on Windows Mobile we
1283
do not support the signals: aboutToHide (), aboutToShow () and hovered ().
1284
It is not possible to display an icon in a native menu on Windows Mobile.
1286
\section1 QMenu on Mac OS X with Qt build against Cocoa
1288
QMenu can be inserted only once in a menu/menubar. Subsequent insertions will
1289
have no effect or will result in a disabled menu item.
1291
See the \l{mainwindows/menus}{Menus} example for an example of how
1292
to use QMenuBar and QMenu in your application.
1294
\b{Important inherited functions:} addAction(), removeAction(), clear(),
1295
addSeparator(), and addMenu().
1297
\sa QMenuBar, {fowler}{GUI Design Handbook: Menu, Drop-Down and Pop-Up},
1298
{Application Example}, {Menus Example}, {Recent Files Example}
1303
Constructs a menu with parent \a parent.
1305
Although a popup menu is always a top-level widget, if a parent is
1306
passed the popup menu will be deleted when that parent is
1307
destroyed (as with any other QObject).
1309
QMenu::QMenu(QWidget *parent)
1310
: QWidget(*new QMenuPrivate, parent, Qt::Popup)
1317
Constructs a menu with a \a title and a \a parent.
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).
1325
QMenu::QMenu(const QString &title, QWidget *parent)
1326
: QWidget(*new QMenuPrivate, parent, Qt::Popup)
1330
d->menuAction->setText(title);
1335
QMenu::QMenu(QMenuPrivate &dd, QWidget *parent)
1336
: QWidget(dd, parent, Qt::Popup)
1348
if (!d->widgetItems.isEmpty()) { // avoid detach on shared null hash
1349
QHash<QAction *, QWidget *>::iterator it = d->widgetItems.begin();
1350
for (; it != d->widgetItems.end(); ++it) {
1351
if (QWidget *widget = it.value()) {
1352
QWidgetAction *action = static_cast<QWidgetAction *>(it.key());
1353
action->releaseWidget(widget);
1360
d->eventLoop->exit();
1367
This convenience function creates a new action with \a text.
1368
The function adds the newly created action to the menu's
1369
list of actions, and returns it.
1371
QMenu takes ownership of the returned QAction.
1373
\sa QWidget::addAction()
1375
QAction *QMenu::addAction(const QString &text)
1377
QAction *ret = new QAction(text, this);
1385
This convenience function creates a new action with an \a icon
1386
and some \a text. The function adds the newly created action to
1387
the menu's list of actions, and returns it.
1389
QMenu takes ownership of the returned QAction.
1391
\sa QWidget::addAction()
1393
QAction *QMenu::addAction(const QIcon &icon, const QString &text)
1395
QAction *ret = new QAction(icon, text, this);
1403
This convenience function creates a new action with the text \a
1404
text and an optional shortcut \a shortcut. The action's
1405
\l{QAction::triggered()}{triggered()} signal is connected to the
1406
\a receiver's \a member slot. The function adds the newly created
1407
action to the menu's list of actions and returns it.
1409
QMenu takes ownership of the returned QAction.
1411
\sa QWidget::addAction()
1413
QAction *QMenu::addAction(const QString &text, const QObject *receiver, const char* member, const QKeySequence &shortcut)
1415
QAction *action = new QAction(text, this);
1416
#ifdef QT_NO_SHORTCUT
1419
action->setShortcut(shortcut);
1421
QObject::connect(action, SIGNAL(triggered(bool)), receiver, member);
1429
This convenience function creates a new action with an \a icon and
1430
some \a text and an optional shortcut \a shortcut. The action's
1431
\l{QAction::triggered()}{triggered()} signal is connected to the
1432
\a member slot of the \a receiver object. The function adds the
1433
newly created action to the menu's list of actions, and returns it.
1435
QMenu takes ownership of the returned QAction.
1437
\sa QWidget::addAction()
1439
QAction *QMenu::addAction(const QIcon &icon, const QString &text, const QObject *receiver,
1440
const char* member, const QKeySequence &shortcut)
1442
QAction *action = new QAction(icon, text, this);
1443
#ifdef QT_NO_SHORTCUT
1446
action->setShortcut(shortcut);
1448
QObject::connect(action, SIGNAL(triggered(bool)), receiver, member);
1454
This convenience function adds \a menu as a submenu to this menu.
1455
It returns \a menu's menuAction(). This menu does not take
1456
ownership of \a menu.
1458
\sa QWidget::addAction(), QMenu::menuAction()
1460
QAction *QMenu::addMenu(QMenu *menu)
1462
QAction *action = menu->menuAction();
1468
Appends a new QMenu with \a title to the menu. The menu
1469
takes ownership of the menu. Returns the new menu.
1471
\sa QWidget::addAction(), QMenu::menuAction()
1473
QMenu *QMenu::addMenu(const QString &title)
1475
QMenu *menu = new QMenu(title, this);
1476
addAction(menu->menuAction());
1481
Appends a new QMenu with \a icon and \a title to the menu. The menu
1482
takes ownership of the menu. Returns the new menu.
1484
\sa QWidget::addAction(), QMenu::menuAction()
1486
QMenu *QMenu::addMenu(const QIcon &icon, const QString &title)
1488
QMenu *menu = new QMenu(title, this);
1489
menu->setIcon(icon);
1490
addAction(menu->menuAction());
1495
This convenience function creates a new separator action, i.e. an
1496
action with QAction::isSeparator() returning true, and adds the new
1497
action to this menu's list of actions. It returns the newly
1500
QMenu takes ownership of the returned QAction.
1502
\sa QWidget::addAction()
1504
QAction *QMenu::addSeparator()
1506
QAction *action = new QAction(this);
1507
action->setSeparator(true);
1513
This convenience function inserts \a menu before action \a before
1514
and returns the menus menuAction().
1516
\sa QWidget::insertAction(), addMenu()
1518
QAction *QMenu::insertMenu(QAction *before, QMenu *menu)
1520
QAction *action = menu->menuAction();
1521
insertAction(before, action);
1526
This convenience function creates a new separator action, i.e. an
1527
action with QAction::isSeparator() returning true. The function inserts
1528
the newly created action into this menu's list of actions before
1529
action \a before and returns it.
1531
QMenu takes ownership of the returned QAction.
1533
\sa QWidget::insertAction(), addSeparator()
1535
QAction *QMenu::insertSeparator(QAction *before)
1537
QAction *action = new QAction(this);
1538
action->setSeparator(true);
1539
insertAction(before, action);
1544
This sets the default action to \a act. The default action may have
1545
a visual cue, depending on the current QStyle. A default action
1546
usually indicates what will happen by default when a drop occurs.
1550
void QMenu::setDefaultAction(QAction *act)
1552
d_func()->defaultAction = act;
1556
Returns the current default action.
1558
\sa setDefaultAction()
1560
QAction *QMenu::defaultAction() const
1562
return d_func()->defaultAction;
1566
\property QMenu::tearOffEnabled
1567
\brief whether the menu supports being torn off
1569
When true, the menu contains a special tear-off item (often shown as a dashed
1570
line at the top of the menu) that creates a copy of the menu when it is
1573
This "torn-off" copy lives in a separate window. It contains the same menu
1574
items as the original menu, with the exception of the tear-off handle.
1576
By default, this property is false.
1578
void QMenu::setTearOffEnabled(bool b)
1581
if (d->tearoff == b)
1587
d->itemsDirty = true;
1592
bool QMenu::isTearOffEnabled() const
1594
return d_func()->tearoff;
1598
When a menu is torn off a second menu is shown to display the menu
1599
contents in a new window. When the menu is in this mode and the menu
1600
is visible returns true; otherwise false.
1602
\sa hideTearOffMenu(), isTearOffEnabled()
1604
bool QMenu::isTearOffMenuVisible() const
1606
if (d_func()->tornPopup)
1607
return d_func()->tornPopup->isVisible();
1612
This function will forcibly hide the torn off menu making it
1613
disappear from the users desktop.
1615
\sa isTearOffMenuVisible(), isTearOffEnabled()
1617
void QMenu::hideTearOffMenu()
1619
if (QWidget *w = d_func()->tornPopup)
1625
Sets the currently highlighted action to \a act.
1627
void QMenu::setActiveAction(QAction *act)
1630
d->setCurrentAction(act, 0);
1632
d->scrollMenu(act, QMenuPrivate::QMenuScroller::ScrollCenter);
1637
Returns the currently highlighted action, or 0 if no
1638
action is currently highlighted.
1640
QAction *QMenu::activeAction() const
1642
return d_func()->currentAction;
1648
Returns true if there are no visible actions inserted into the menu, false
1651
\sa QWidget::actions()
1654
bool QMenu::isEmpty() const
1657
for(int i = 0; ret && i < actions().count(); ++i) {
1658
const QAction *action = actions().at(i);
1659
if (!action->isSeparator() && action->isVisible()) {
1667
Removes all the menu's actions. Actions owned by the menu and not
1668
shown in any other widget are deleted.
1674
QList<QAction*> acts = actions();
1676
for(int i = 0; i < acts.size(); i++) {
1677
removeAction(acts[i]);
1678
if (acts[i]->parent() == this && acts[i]->d_func()->widgets.isEmpty())
1684
If a menu does not fit on the screen it lays itself out so that it
1685
does fit. It is style dependent what layout means (for example, on
1686
Windows it will use multiple columns).
1688
This functions returns the number of columns necessary.
1690
int QMenu::columnCount() const
1692
return d_func()->ncols;
1696
Returns the item at \a pt; returns 0 if there is no item there.
1698
QAction *QMenu::actionAt(const QPoint &pt) const
1700
if (QAction *ret = d_func()->actionAt(pt))
1706
Returns the geometry of action \a act.
1708
QRect QMenu::actionGeometry(QAction *act) const
1710
return d_func()->actionRect(act);
1716
QSize QMenu::sizeHint() const
1719
d->updateActionRects();
1722
for (int i = 0; i < d->actionRects.count(); ++i) {
1723
const QRect &rect = d->actionRects.at(i);
1726
if (rect.bottom() >= s.height())
1727
s.setHeight(rect.y() + rect.height());
1728
if (rect.right() >= s.width())
1729
s.setWidth(rect.x() + rect.width());
1731
// Note that the action rects calculated above already include
1732
// the top and left margins, so we only need to add margins for
1733
// the bottom and right.
1734
QStyleOption opt(0);
1736
const int fw = style()->pixelMetric(QStyle::PM_MenuPanelWidth, &opt, this);
1737
s.rwidth() += style()->pixelMetric(QStyle::PM_MenuHMargin, &opt, this) + fw + d->rightmargin;
1738
s.rheight() += style()->pixelMetric(QStyle::PM_MenuVMargin, &opt, this) + fw + d->bottommargin;
1740
return style()->sizeFromContents(QStyle::CT_Menu, &opt,
1741
s.expandedTo(QApplication::globalStrut()), this);
1745
Displays the menu so that the action \a atAction will be at the
1746
specified \e global position \a p. To translate a widget's local
1747
coordinates into global coordinates, use QWidget::mapToGlobal().
1749
When positioning a menu with exec() or popup(), bear in mind that
1750
you cannot rely on the menu's current size(). For performance
1751
reasons, the menu adapts its size only when necessary, so in many
1752
cases, the size before and after the show is different. Instead,
1753
use sizeHint() which calculates the proper size depending on the
1754
menu's current contents.
1756
\sa QWidget::mapToGlobal(), exec()
1758
void QMenu::popup(const QPoint &p, QAction *atAction)
1761
if (d->scroll) { // reset scroll state from last popup
1762
if (d->scroll->scrollOffset)
1763
d->itemsDirty = 1; // sizeHint will be incorrect if there is previous scroll
1764
d->scroll->scrollOffset = 0;
1765
d->scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone;
1767
d->tearoffHighlighted = 0;
1769
d->doChildEffects = true;
1770
d->updateLayoutDirection();
1772
#ifndef QT_NO_MENUBAR
1773
// if this menu is part of a chain attached to a QMenuBar, set the
1774
// _NET_WM_WINDOW_TYPE_DROPDOWN_MENU X11 window type
1775
setAttribute(Qt::WA_X11NetWmWindowTypeDropDownMenu, qobject_cast<QMenuBar *>(d->topCausedWidget()) != 0);
1778
ensurePolished(); // Get the right font
1780
const bool actionListChanged = d->itemsDirty;
1781
d->updateActionRects();
1783
QPushButton *causedButton = qobject_cast<QPushButton*>(d->causedPopup.widget);
1784
if (actionListChanged && causedButton)
1785
pos = QPushButtonPrivate::get(causedButton)->adjustedMenuPosition();
1789
QSize size = sizeHint();
1791
#ifndef QT_NO_GRAPHICSVIEW
1792
bool isEmbedded = !bypassGraphicsProxyWidget(this) && d->nearestGraphicsProxyWidget(this);
1794
screen = d->popupGeometry(this);
1797
screen = d->popupGeometry(QApplication::desktop()->screenNumber(p));
1798
const int desktopFrame = style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, 0, this);
1799
bool adjustToDesktop = !window()->testAttribute(Qt::WA_DontShowOnScreen);
1801
// if the screens have very different geometries and the menu is too big, we have to recalculate
1802
if (size.height() > screen.height() || size.width() > screen.width()) {
1803
size = d->adjustMenuSizeForScreen(screen);
1804
adjustToDesktop = true;
1806
// Layout is not right, we might be able to save horizontal space
1807
if (d->ncols >1 && size.height() < screen.height()) {
1808
size = d->adjustMenuSizeForScreen(screen);
1809
adjustToDesktop = true;
1812
#ifdef QT_KEYPAD_NAVIGATION
1813
if (!atAction && QApplication::keypadNavigationEnabled()) {
1814
// Try to have one item activated
1815
if (d->defaultAction && d->defaultAction->isEnabled()) {
1816
atAction = d->defaultAction;
1817
// TODO: This works for first level menus, not yet sub menus
1819
foreach (QAction *action, d->actions)
1820
if (action->isEnabled()) {
1825
d->currentAction = atAction;
1829
pos.setY(screen.top() + desktopFrame);
1830
} else if (atAction) {
1831
for (int i = 0, above_height = 0; i < d->actions.count(); i++) {
1832
QAction *action = d->actions.at(i);
1833
if (action == atAction) {
1834
int newY = pos.y() - above_height;
1835
if (d->scroll && newY < desktopFrame) {
1836
d->scroll->scrollFlags = d->scroll->scrollFlags
1837
| QMenuPrivate::QMenuScroller::ScrollUp;
1838
d->scroll->scrollOffset = newY;
1839
newY = desktopFrame;
1843
if (d->scroll && d->scroll->scrollFlags != QMenuPrivate::QMenuScroller::ScrollNone
1844
&& !style()->styleHint(QStyle::SH_Menu_FillScreenWithScroll, 0, this)) {
1845
int below_height = above_height + d->scroll->scrollOffset;
1846
for (int i2 = i; i2 < d->actionRects.count(); i2++)
1847
below_height += d->actionRects.at(i2).height();
1848
size.setHeight(below_height);
1852
above_height += d->actionRects.at(i).height();
1857
QPoint mouse = QCursor::pos();
1858
d->mousePopupPos = mouse;
1859
const bool snapToMouse = !d->causedPopup.widget && (QRect(p.x() - 3, p.y() - 3, 6, 6).contains(mouse));
1861
const QSize menuSize(sizeHint());
1862
if (adjustToDesktop) {
1863
// handle popup falling "off screen"
1864
if (isRightToLeft()) {
1865
if (snapToMouse) // position flowing left from the mouse
1866
pos.setX(mouse.x() - size.width());
1868
#ifndef QT_NO_MENUBAR
1869
// if in a menubar, it should be right-aligned
1870
if (qobject_cast<QMenuBar*>(d->causedPopup.widget))
1871
pos.rx() -= size.width();
1872
#endif //QT_NO_MENUBAR
1874
if (pos.x() < screen.left() + desktopFrame)
1875
pos.setX(qMax(p.x(), screen.left() + desktopFrame));
1876
if (pos.x() + size.width() - 1 > screen.right() - desktopFrame)
1877
pos.setX(qMax(p.x() - size.width(), screen.right() - desktopFrame - size.width() + 1));
1879
if (pos.x() + size.width() - 1 > screen.right() - desktopFrame)
1880
pos.setX(screen.right() - desktopFrame - size.width() + 1);
1881
if (pos.x() < screen.left() + desktopFrame)
1882
pos.setX(screen.left() + desktopFrame);
1884
if (pos.y() + size.height() - 1 > screen.bottom() - desktopFrame) {
1886
pos.setY(qMin(mouse.y() - (size.height() + desktopFrame), screen.bottom()-desktopFrame-size.height()+1));
1888
pos.setY(qMax(p.y() - (size.height() + desktopFrame), screen.bottom()-desktopFrame-size.height()+1));
1889
} else if (pos.y() < screen.top() + desktopFrame) {
1890
pos.setY(screen.top() + desktopFrame);
1893
if (pos.y() < screen.top() + desktopFrame)
1894
pos.setY(screen.top() + desktopFrame);
1895
if (pos.y() + menuSize.height() - 1 > screen.bottom() - desktopFrame) {
1897
d->scroll->scrollFlags |= uint(QMenuPrivate::QMenuScroller::ScrollDown);
1898
int y = qMax(screen.y(),pos.y());
1899
size.setHeight(screen.bottom() - (desktopFrame * 2) - y);
1901
// Too big for screen, bias to see bottom of menu (for some reason)
1902
pos.setY(screen.bottom() - size.height() + 1);
1906
const int subMenuOffset = style()->pixelMetric(QStyle::PM_SubMenuOverlap, 0, this);
1907
QMenu *caused = qobject_cast<QMenu*>(d_func()->causedPopup.widget);
1908
if (caused && caused->geometry().width() + menuSize.width() + subMenuOffset < screen.width()) {
1909
QRect parentActionRect(caused->d_func()->actionRect(caused->d_func()->currentAction));
1910
const QPoint actionTopLeft = caused->mapToGlobal(parentActionRect.topLeft());
1911
parentActionRect.moveTopLeft(actionTopLeft);
1912
if (isRightToLeft()) {
1913
if ((pos.x() + menuSize.width() > parentActionRect.left() - subMenuOffset)
1914
&& (pos.x() < parentActionRect.right()))
1916
pos.rx() = parentActionRect.left() - menuSize.width();
1917
if (pos.x() < screen.x())
1918
pos.rx() = parentActionRect.right();
1919
if (pos.x() + menuSize.width() > screen.x() + screen.width())
1920
pos.rx() = screen.x();
1923
if ((pos.x() < parentActionRect.right() + subMenuOffset)
1924
&& (pos.x() + menuSize.width() > parentActionRect.left()))
1926
pos.rx() = parentActionRect.right();
1927
if (pos.x() + menuSize.width() > screen.x() + screen.width())
1928
pos.rx() = parentActionRect.left() - menuSize.width();
1929
if (pos.x() < screen.x())
1930
pos.rx() = screen.x() + screen.width() - menuSize.width();
1934
setGeometry(QRect(pos, size));
1935
#ifndef QT_NO_EFFECTS
1936
int hGuess = isRightToLeft() ? QEffects::LeftScroll : QEffects::RightScroll;
1937
int vGuess = QEffects::DownScroll;
1938
if (isRightToLeft()) {
1939
if ((snapToMouse && (pos.x() + size.width() / 2 > mouse.x())) ||
1940
(qobject_cast<QMenu*>(d->causedPopup.widget) && pos.x() + size.width() / 2 > d->causedPopup.widget->x()))
1941
hGuess = QEffects::RightScroll;
1943
if ((snapToMouse && (pos.x() + size.width() / 2 < mouse.x())) ||
1944
(qobject_cast<QMenu*>(d->causedPopup.widget) && pos.x() + size.width() / 2 < d->causedPopup.widget->x()))
1945
hGuess = QEffects::LeftScroll;
1948
#ifndef QT_NO_MENUBAR
1949
if ((snapToMouse && (pos.y() + size.height() / 2 < mouse.y())) ||
1950
(qobject_cast<QMenuBar*>(d->causedPopup.widget) &&
1951
pos.y() + size.width() / 2 < d->causedPopup.widget->mapToGlobal(d->causedPopup.widget->pos()).y()))
1952
vGuess = QEffects::UpScroll;
1954
if (QApplication::isEffectEnabled(Qt::UI_AnimateMenu)) {
1955
bool doChildEffects = true;
1956
#ifndef QT_NO_MENUBAR
1957
if (QMenuBar *mb = qobject_cast<QMenuBar*>(d->causedPopup.widget)) {
1958
doChildEffects = mb->d_func()->doChildEffects;
1959
mb->d_func()->doChildEffects = false;
1962
if (QMenu *m = qobject_cast<QMenu*>(d->causedPopup.widget)) {
1963
doChildEffects = m->d_func()->doChildEffects;
1964
m->d_func()->doChildEffects = false;
1967
if (doChildEffects) {
1968
if (QApplication::isEffectEnabled(Qt::UI_FadeMenu))
1970
else if (d->causedPopup.widget)
1971
qScrollEffect(this, qobject_cast<QMenu*>(d->causedPopup.widget) ? hGuess : vGuess);
1973
qScrollEffect(this, hGuess | vGuess);
1975
// kill any running effect
1987
#ifndef QT_NO_ACCESSIBILITY
1988
QAccessibleEvent event(this, QAccessible::PopupMenuStart);
1989
QAccessible::updateAccessibility(&event);
1994
Executes this menu synchronously.
1996
This is equivalent to \c{exec(pos())}.
1998
This returns the triggered QAction in either the popup menu or one
1999
of its submenus, or 0 if no item was triggered (normally because
2000
the user pressed Esc).
2002
In most situations you'll want to specify the position yourself,
2003
for example, the current mouse position:
2004
\snippet code/src_gui_widgets_qmenu.cpp 0
2005
or aligned to a widget:
2006
\snippet code/src_gui_widgets_qmenu.cpp 1
2007
or in reaction to a QMouseEvent *e:
2008
\snippet code/src_gui_widgets_qmenu.cpp 2
2010
QAction *QMenu::exec()
2019
Executes this menu synchronously.
2021
Pops up the menu so that the action \a action will be at the
2022
specified \e global position \a p. To translate a widget's local
2023
coordinates into global coordinates, use QWidget::mapToGlobal().
2025
This returns the triggered QAction in either the popup menu or one
2026
of its submenus, or 0 if no item was triggered (normally because
2027
the user pressed Esc).
2029
Note that all signals are emitted as usual. If you connect a
2030
QAction to a slot and call the menu's exec(), you get the result
2031
both via the signal-slot connection and in the return value of
2034
Common usage is to position the menu at the current mouse
2036
\snippet code/src_gui_widgets_qmenu.cpp 3
2037
or aligned to a widget:
2038
\snippet code/src_gui_widgets_qmenu.cpp 4
2039
or in reaction to a QMouseEvent *e:
2040
\snippet code/src_gui_widgets_qmenu.cpp 5
2042
When positioning a menu with exec() or popup(), bear in mind that
2043
you cannot rely on the menu's current size(). For performance
2044
reasons, the menu adapts its size only when necessary. So in many
2045
cases, the size before and after the show is different. Instead,
2046
use sizeHint() which calculates the proper size depending on the
2047
menu's current contents.
2049
\sa popup(), QWidget::mapToGlobal()
2051
QAction *QMenu::exec(const QPoint &p, QAction *action)
2055
QEventLoop eventLoop;
2056
d->eventLoop = &eventLoop;
2059
QPointer<QObject> guard = this;
2060
(void) eventLoop.exec();
2064
action = d->syncAction;
2073
Executes a menu synchronously.
2075
The menu's actions are specified by the list of \a actions. The menu will
2076
pop up so that the specified action, \a at, appears at global position \a
2077
pos. If \a at is not specified then the menu appears at position \a
2078
pos. \a parent is the menu's parent widget; specifying the parent will
2079
provide context when \a pos alone is not enough to decide where the menu
2080
should go (e.g., with multiple desktops or when the parent is embedded in
2083
The function returns the triggered QAction in either the popup
2084
menu or one of its submenus, or 0 if no item was triggered
2085
(normally because the user pressed Esc).
2087
This is equivalent to:
2088
\snippet code/src_gui_widgets_qmenu.cpp 6
2090
\sa popup(), QWidget::mapToGlobal()
2092
QAction *QMenu::exec(QList<QAction*> actions, const QPoint &pos, QAction *at, QWidget *parent)
2095
menu.addActions(actions);
2096
return menu.exec(pos, at);
2102
void QMenu::hideEvent(QHideEvent *)
2107
d->eventLoop->exit();
2108
d->setCurrentAction(0);
2109
#ifndef QT_NO_ACCESSIBILITY
2110
QAccessibleEvent event(this, QAccessible::PopupMenuEnd);
2111
QAccessible::updateAccessibility(&event);
2113
#ifndef QT_NO_MENUBAR
2114
if (QMenuBar *mb = qobject_cast<QMenuBar*>(d->causedPopup.widget))
2115
mb->d_func()->setCurrentAction(0);
2118
d->hasHadMouse = false;
2119
d->causedPopup.widget = 0;
2120
d->causedPopup.action = 0;
2122
d->scroll->scrollTimer.stop(); //make sure the timer stops
2128
void QMenu::paintEvent(QPaintEvent *e)
2131
d->updateActionRects();
2133
QRegion emptyArea = QRegion(rect());
2135
QStyleOptionMenuItem menuOpt;
2136
menuOpt.initFrom(this);
2137
menuOpt.state = QStyle::State_None;
2138
menuOpt.checkType = QStyleOptionMenuItem::NotCheckable;
2139
menuOpt.maxIconWidth = 0;
2140
menuOpt.tabWidth = 0;
2141
style()->drawPrimitive(QStyle::PE_PanelMenu, &menuOpt, &p, this);
2143
//draw the items that need updating..
2144
for (int i = 0; i < d->actions.count(); ++i) {
2145
QAction *action = d->actions.at(i);
2146
QRect adjustedActionRect = d->actionRects.at(i);
2147
if (!e->rect().intersects(adjustedActionRect)
2148
|| d->widgetItems.value(action))
2150
//set the clip region to be extra safe (and adjust for the scrollers)
2151
QRegion adjustedActionReg(adjustedActionRect);
2152
emptyArea -= adjustedActionReg;
2153
p.setClipRegion(adjustedActionReg);
2155
QStyleOptionMenuItem opt;
2156
initStyleOption(&opt, action);
2157
opt.rect = adjustedActionRect;
2158
style()->drawControl(QStyle::CE_MenuItem, &opt, &p, this);
2161
const int fw = style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, this);
2162
//draw the scroller regions..
2164
menuOpt.menuItemType = QStyleOptionMenuItem::Scroller;
2165
menuOpt.state |= QStyle::State_Enabled;
2166
if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp) {
2167
menuOpt.rect.setRect(fw, fw, width() - (fw * 2), d->scrollerHeight());
2168
emptyArea -= QRegion(menuOpt.rect);
2169
p.setClipRect(menuOpt.rect);
2170
style()->drawControl(QStyle::CE_MenuScroller, &menuOpt, &p, this);
2172
if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown) {
2173
menuOpt.rect.setRect(fw, height() - d->scrollerHeight() - fw, width() - (fw * 2),
2174
d->scrollerHeight());
2175
emptyArea -= QRegion(menuOpt.rect);
2176
menuOpt.state |= QStyle::State_DownArrow;
2177
p.setClipRect(menuOpt.rect);
2178
style()->drawControl(QStyle::CE_MenuScroller, &menuOpt, &p, this);
2181
//paint the tear off..
2183
menuOpt.menuItemType = QStyleOptionMenuItem::TearOff;
2184
menuOpt.rect.setRect(fw, fw, width() - (fw * 2),
2185
style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, this));
2186
if (d->scroll && d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
2187
menuOpt.rect.translate(0, d->scrollerHeight());
2188
emptyArea -= QRegion(menuOpt.rect);
2189
p.setClipRect(menuOpt.rect);
2190
menuOpt.state = QStyle::State_None;
2191
if (d->tearoffHighlighted)
2192
menuOpt.state |= QStyle::State_Selected;
2193
style()->drawControl(QStyle::CE_MenuTearoff, &menuOpt, &p, this);
2198
borderReg += QRect(0, 0, fw, height()); //left
2199
borderReg += QRect(width()-fw, 0, fw, height()); //right
2200
borderReg += QRect(0, 0, width(), fw); //top
2201
borderReg += QRect(0, height()-fw, width(), fw); //bottom
2202
p.setClipRegion(borderReg);
2203
emptyArea -= borderReg;
2204
QStyleOptionFrame frame;
2205
frame.rect = rect();
2206
frame.palette = palette();
2207
frame.state = QStyle::State_None;
2208
frame.lineWidth = style()->pixelMetric(QStyle::PM_MenuPanelWidth);
2209
frame.midLineWidth = 0;
2210
style()->drawPrimitive(QStyle::PE_FrameMenu, &frame, &p, this);
2213
//finally the rest of the space
2214
p.setClipRegion(emptyArea);
2215
menuOpt.state = QStyle::State_None;
2216
menuOpt.menuItemType = QStyleOptionMenuItem::EmptyArea;
2217
menuOpt.checkType = QStyleOptionMenuItem::NotCheckable;
2218
menuOpt.rect = rect();
2219
menuOpt.menuRect = rect();
2220
style()->drawControl(QStyle::CE_MenuEmptyArea, &menuOpt, &p, this);
2223
#ifndef QT_NO_WHEELEVENT
2227
void QMenu::wheelEvent(QWheelEvent *e)
2230
if (d->scroll && rect().contains(e->pos()))
2231
d->scrollMenu(e->delta() > 0 ?
2232
QMenuPrivate::QMenuScroller::ScrollUp : QMenuPrivate::QMenuScroller::ScrollDown);
2239
void QMenu::mousePressEvent(QMouseEvent *e)
2242
if (d->aboutToHide || d->mouseEventTaken(e))
2244
if (!rect().contains(e->pos())) {
2246
&& QRect(d->noReplayFor->mapToGlobal(QPoint()), d->noReplayFor->size()).contains(e->globalPos()))
2247
setAttribute(Qt::WA_NoMouseReplay);
2248
if (d->eventLoop) // synchronous operation
2250
d->hideUpToMenuBar();
2253
d->mouseDown = this;
2255
QAction *action = d->actionAt(e->pos());
2256
d->setCurrentAction(action, 20);
2263
void QMenu::mouseReleaseEvent(QMouseEvent *e)
2266
if (d->aboutToHide || d->mouseEventTaken(e))
2268
if(d->mouseDown != this) {
2275
QAction *action = d->actionAt(e->pos());
2277
if (action && action == d->currentAction) {
2278
if (!action->menu()){
2279
#if defined(Q_OS_WIN)
2280
//On Windows only context menus can be activated with the right button
2281
if (e->button() == Qt::LeftButton || d->topCausedWidget() == 0)
2283
d->activateAction(action, QAction::Trigger);
2285
} else if (d->hasMouseMoved(e->globalPos())) {
2286
d->hideUpToMenuBar();
2293
void QMenu::changeEvent(QEvent *e)
2296
if (e->type() == QEvent::StyleChange || e->type() == QEvent::FontChange ||
2297
e->type() == QEvent::LayoutDirectionChange) {
2299
setMouseTracking(style()->styleHint(QStyle::SH_Menu_MouseTracking, 0, this));
2302
if (!style()->styleHint(QStyle::SH_Menu_Scrollable, 0, this)) {
2305
} else if (!d->scroll) {
2306
d->scroll = new QMenuPrivate::QMenuScroller;
2307
d->scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone;
2309
} else if (e->type() == QEvent::EnabledChange) {
2310
if (d->tornPopup) // torn-off menu
2311
d->tornPopup->setEnabled(isEnabled());
2312
d->menuAction->setEnabled(isEnabled());
2313
if (d->platformMenu)
2314
d->platformMenu->setEnabled(isEnabled());
2316
QWidget::changeEvent(e);
2324
QMenu::event(QEvent *e)
2327
switch (e->type()) {
2328
case QEvent::Polish:
2329
d->updateLayoutDirection();
2331
case QEvent::ShortcutOverride: {
2332
QKeyEvent *kev = static_cast<QKeyEvent*>(e);
2333
if (kev->key() == Qt::Key_Up || kev->key() == Qt::Key_Down
2334
|| kev->key() == Qt::Key_Left || kev->key() == Qt::Key_Right
2335
|| kev->key() == Qt::Key_Enter || kev->key() == Qt::Key_Return
2336
|| kev->key() == Qt::Key_Escape) {
2342
case QEvent::KeyPress: {
2343
QKeyEvent *ke = (QKeyEvent*)e;
2344
if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
2349
case QEvent::ContextMenu:
2350
if(d->menuDelayTimer.isActive()) {
2351
d->menuDelayTimer.stop();
2352
internalDelayedPopup();
2355
case QEvent::Resize: {
2356
QStyleHintReturnMask menuMask;
2357
QStyleOption option;
2358
option.initFrom(this);
2359
if (style()->styleHint(QStyle::SH_Menu_Mask, &option, this, &menuMask)) {
2360
setMask(menuMask.region);
2363
d->updateActionRects();
2367
d->updateActionRects();
2368
if (d->currentAction)
2369
d->popupAction(d->currentAction, 0, false);
2371
#ifndef QT_NO_WHATSTHIS
2372
case QEvent::QueryWhatsThis:
2373
e->setAccepted(d->whatsThis.size());
2374
if (QAction *action = d->actionAt(static_cast<QHelpEvent*>(e)->pos())) {
2375
if (action->whatsThis().size() || action->menu())
2383
return QWidget::event(e);
2389
bool QMenu::focusNextPrevChild(bool next)
2392
QKeyEvent ev(QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier);
2400
void QMenu::keyPressEvent(QKeyEvent *e)
2403
d->updateActionRects();
2405
if (isRightToLeft()) { // in reverse mode open/close key for submenues are reversed
2406
if (key == Qt::Key_Left)
2407
key = Qt::Key_Right;
2408
else if (key == Qt::Key_Right)
2412
if (key == Qt::Key_Tab) //means down
2414
if (key == Qt::Key_Backtab) //means up
2418
bool key_consumed = false;
2421
key_consumed = true;
2423
d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollTop, true);
2426
key_consumed = true;
2428
d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollBottom, true);
2430
case Qt::Key_PageUp:
2431
key_consumed = true;
2432
if (d->currentAction && d->scroll) {
2433
if(d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
2434
d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollUp, true, true);
2436
d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollTop, true);
2439
case Qt::Key_PageDown:
2440
key_consumed = true;
2441
if (d->currentAction && d->scroll) {
2442
if(d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown)
2443
d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollDown, true, true);
2445
d->scrollMenu(QMenuPrivate::QMenuScroller::ScrollBottom, true);
2449
case Qt::Key_Down: {
2450
key_consumed = true;
2451
QAction *nextAction = 0;
2452
QMenuPrivate::QMenuScroller::ScrollLocation scroll_loc = QMenuPrivate::QMenuScroller::ScrollStay;
2453
if (!d->currentAction) {
2454
if(key == Qt::Key_Down) {
2455
for(int i = 0; i < d->actions.count(); ++i) {
2456
QAction *act = d->actions.at(i);
2457
if (d->actionRects.at(i).isNull())
2459
if (!act->isSeparator() &&
2460
(style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this)
2461
|| act->isEnabled())) {
2467
for(int i = d->actions.count()-1; i >= 0; --i) {
2468
QAction *act = d->actions.at(i);
2469
if (d->actionRects.at(i).isNull())
2471
if (!act->isSeparator() &&
2472
(style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this)
2473
|| act->isEnabled())) {
2480
for(int i = 0, y = 0; !nextAction && i < d->actions.count(); i++) {
2481
QAction *act = d->actions.at(i);
2482
if (act == d->currentAction) {
2483
if (key == Qt::Key_Up) {
2484
for(int next_i = i-1; true; next_i--) {
2486
if(!style()->styleHint(QStyle::SH_Menu_SelectionWrap, 0, this))
2489
scroll_loc = QMenuPrivate::QMenuScroller::ScrollBottom;
2490
next_i = d->actionRects.count()-1;
2492
QAction *next = d->actions.at(next_i);
2493
if (next == d->currentAction)
2495
if (d->actionRects.at(next_i).isNull())
2497
if (next->isSeparator() ||
2498
(!next->isEnabled() &&
2499
!style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this)))
2502
if (d->scroll && (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)) {
2503
int topVisible = d->scrollerHeight();
2505
topVisible += style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, this);
2506
if (((y + d->scroll->scrollOffset) - topVisible) <= d->actionRects.at(next_i).height())
2507
scroll_loc = QMenuPrivate::QMenuScroller::ScrollTop;
2511
if (!nextAction && d->tearoff)
2512
d->tearoffHighlighted = 1;
2514
y += d->actionRects.at(i).height();
2515
for(int next_i = i+1; true; next_i++) {
2516
if (next_i == d->actionRects.count()) {
2517
if(!style()->styleHint(QStyle::SH_Menu_SelectionWrap, 0, this))
2520
scroll_loc = QMenuPrivate::QMenuScroller::ScrollTop;
2523
QAction *next = d->actions.at(next_i);
2524
if (next == d->currentAction)
2526
if (d->actionRects.at(next_i).isNull())
2528
if (next->isSeparator() ||
2529
(!next->isEnabled() &&
2530
!style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this)))
2533
if (d->scroll && (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollDown)) {
2534
int bottomVisible = height() - d->scrollerHeight();
2535
if (d->scroll->scrollFlags & QMenuPrivate::QMenuScroller::ScrollUp)
2536
bottomVisible -= d->scrollerHeight();
2538
bottomVisible -= style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, this);
2539
if ((y + d->scroll->scrollOffset + d->actionRects.at(next_i).height()) > bottomVisible)
2540
scroll_loc = QMenuPrivate::QMenuScroller::ScrollBottom;
2547
y += d->actionRects.at(i).height();
2551
if (d->scroll && scroll_loc != QMenuPrivate::QMenuScroller::ScrollStay) {
2552
d->scroll->scrollTimer.stop();
2553
d->scrollMenu(nextAction, scroll_loc);
2555
d->setCurrentAction(nextAction, /*popup*/-1, QMenuPrivate::SelectedFromKeyboard);
2560
if (d->currentAction && d->currentAction->isEnabled() && d->currentAction->menu()) {
2561
d->popupAction(d->currentAction, 0, true);
2562
key_consumed = true;
2566
case Qt::Key_Left: {
2567
if (d->currentAction && !d->scroll) {
2568
QAction *nextAction = 0;
2569
if (key == Qt::Key_Left) {
2570
QRect actionR = d->actionRect(d->currentAction);
2571
for(int x = actionR.left()-1; !nextAction && x >= 0; x--)
2572
nextAction = d->actionAt(QPoint(x, actionR.center().y()));
2574
QRect actionR = d->actionRect(d->currentAction);
2575
for(int x = actionR.right()+1; !nextAction && x < width(); x++)
2576
nextAction = d->actionAt(QPoint(x, actionR.center().y()));
2579
d->setCurrentAction(nextAction, /*popup*/-1, QMenuPrivate::SelectedFromKeyboard);
2580
key_consumed = true;
2583
if (!key_consumed && key == Qt::Key_Left && qobject_cast<QMenu*>(d->causedPopup.widget)) {
2584
QPointer<QWidget> caused = d->causedPopup.widget;
2588
key_consumed = true;
2596
key_consumed = true;
2597
if (style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, this))
2600
#ifndef QT_NO_MENUBAR
2601
if (QMenuBar *mb = qobject_cast<QMenuBar*>(QApplication::focusWidget())) {
2602
mb->d_func()->setKeyboardMode(false);
2608
case Qt::Key_Escape:
2609
#ifdef QT_KEYPAD_NAVIGATION
2612
key_consumed = true;
2618
QPointer<QWidget> caused = d->causedPopup.widget;
2619
d->hideMenu(this); // hide after getting causedPopup
2620
#ifndef QT_NO_MENUBAR
2621
if (QMenuBar *mb = qobject_cast<QMenuBar*>(caused)) {
2622
mb->d_func()->setCurrentAction(d->menuAction);
2623
mb->d_func()->setKeyboardMode(true);
2630
if (!style()->styleHint(QStyle::SH_Menu_SpaceActivatesItem, 0, this))
2632
// for motif, fall through
2633
#ifdef QT_KEYPAD_NAVIGATION
2634
case Qt::Key_Select:
2636
case Qt::Key_Return:
2637
case Qt::Key_Enter: {
2638
if (!d->currentAction) {
2639
d->setFirstActionActive();
2640
key_consumed = true;
2646
if (d->currentAction->menu())
2647
d->popupAction(d->currentAction, 0, true);
2649
d->activateAction(d->currentAction, QAction::Trigger);
2650
key_consumed = true;
2653
#ifndef QT_NO_WHATSTHIS
2655
if (!d->currentAction || d->currentAction->whatsThis().isNull())
2657
QWhatsThis::enterWhatsThisMode();
2658
d->activateAction(d->currentAction, QAction::Trigger);
2662
key_consumed = false;
2665
if (!key_consumed) { // send to menu bar
2666
if ((!e->modifiers() || e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ShiftModifier) &&
2667
e->text().length()==1) {
2668
bool activateAction = false;
2669
QAction *nextAction = 0;
2670
if (style()->styleHint(QStyle::SH_Menu_KeyboardSearch, 0, this) && !e->modifiers()) {
2671
int best_match_count = 0;
2672
d->searchBufferTimer.start(2000, this);
2673
d->searchBuffer += e->text();
2674
for(int i = 0; i < d->actions.size(); ++i) {
2675
int match_count = 0;
2676
if (d->actionRects.at(i).isNull())
2678
QAction *act = d->actions.at(i);
2679
const QString act_text = act->text();
2680
for(int c = 0; c < d->searchBuffer.size(); ++c) {
2681
if(act_text.indexOf(d->searchBuffer.at(c), 0, Qt::CaseInsensitive) != -1)
2684
if(match_count > best_match_count) {
2685
best_match_count = match_count;
2690
#ifndef QT_NO_SHORTCUT
2693
QAction *first = 0, *currentSelected = 0, *firstAfterCurrent = 0;
2694
QChar c = e->text().at(0).toUpper();
2695
for(int i = 0; i < d->actions.size(); ++i) {
2696
if (d->actionRects.at(i).isNull())
2698
QAction *act = d->actions.at(i);
2699
QKeySequence sequence = QKeySequence::mnemonic(act->text());
2700
int key = sequence[0] & 0xffff;
2701
if (key == c.unicode()) {
2705
if (act == d->currentAction)
2706
currentSelected = act;
2707
else if (!firstAfterCurrent && currentSelected)
2708
firstAfterCurrent = act;
2711
if (clashCount == 1)
2712
activateAction = true;
2713
if (clashCount >= 1) {
2714
if (clashCount == 1 || !currentSelected || !firstAfterCurrent)
2717
nextAction = firstAfterCurrent;
2722
key_consumed = true;
2724
d->scrollMenu(nextAction, QMenuPrivate::QMenuScroller::ScrollCenter, false);
2725
d->setCurrentAction(nextAction, 20, QMenuPrivate::SelectedFromElsewhere, true);
2726
if (!nextAction->menu() && activateAction) {
2728
d->activateAction(nextAction, QAction::Trigger);
2732
if (!key_consumed) {
2733
#ifndef QT_NO_MENUBAR
2734
if (QMenuBar *mb = qobject_cast<QMenuBar*>(d->topCausedWidget())) {
2735
QAction *oldAct = mb->d_func()->currentAction;
2736
QApplication::sendEvent(mb, e);
2737
if (mb->d_func()->currentAction != oldAct)
2738
key_consumed = true;
2744
if (key_consumed && (e->key() == Qt::Key_Control || e->key() == Qt::Key_Shift || e->key() == Qt::Key_Meta))
2745
QApplication::beep();
2746
#endif // Q_OS_WIN32
2757
void QMenu::mouseMoveEvent(QMouseEvent *e)
2760
if (!isVisible() || d->aboutToHide || d->mouseEventTaken(e))
2763
if (d->motions == 0) // ignore first mouse move event (see enterEvent())
2765
d->hasHadMouse = d->hasHadMouse || rect().contains(e->pos());
2767
QAction *action = d->actionAt(e->pos());
2770
&& (!d->currentAction
2771
|| !(d->currentAction->menu() && d->currentAction->menu()->isVisible())))
2772
d->setCurrentAction(0);
2774
} else if(e->buttons()) {
2775
d->mouseDown = this;
2777
if (d->sloppyRegion.contains(e->pos())) {
2778
d->sloppyAction = action;
2779
QMenuPrivate::sloppyDelayTimer = startTimer(style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, 0, this)*6);
2780
} else if (action != d->currentAction) {
2781
d->setCurrentAction(action, style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, 0, this));
2788
void QMenu::enterEvent(QEvent *)
2790
d_func()->motions = -1; // force us to ignore the generate mouse move in mouseMoveEvent()
2796
void QMenu::leaveEvent(QEvent *)
2799
d->sloppyAction = 0;
2800
if (!d->sloppyRegion.isEmpty())
2801
d->sloppyRegion = QRegion();
2802
if (!d->activeMenu && d->currentAction)
2810
QMenu::timerEvent(QTimerEvent *e)
2813
if (d->scroll && d->scroll->scrollTimer.timerId() == e->timerId()) {
2814
d->scrollMenu((QMenuPrivate::QMenuScroller::ScrollDirection)d->scroll->scrollDirection);
2815
if (d->scroll->scrollFlags == QMenuPrivate::QMenuScroller::ScrollNone)
2816
d->scroll->scrollTimer.stop();
2817
} else if(d->menuDelayTimer.timerId() == e->timerId()) {
2818
d->menuDelayTimer.stop();
2819
internalDelayedPopup();
2820
} else if(QMenuPrivate::sloppyDelayTimer == e->timerId()) {
2821
killTimer(QMenuPrivate::sloppyDelayTimer);
2822
QMenuPrivate::sloppyDelayTimer = 0;
2823
internalSetSloppyAction();
2824
} else if(d->searchBufferTimer.timerId() == e->timerId()) {
2825
d->searchBuffer.clear();
2829
void copyActionToPlatformItem(const QAction *action, QPlatformMenuItem* item)
2831
item->setText(action->text());
2832
item->setIsSeparator(action->isSeparator());
2833
if (action->isIconVisibleInMenu())
2834
item->setIcon(action->icon());
2835
item->setVisible(action->isVisible());
2836
item->setShortcut(action->shortcut());
2837
item->setChecked(action->isChecked());
2838
item->setFont(action->font());
2839
item->setRole((QPlatformMenuItem::MenuRole) action->menuRole());
2840
item->setEnabled(action->isEnabled());
2842
if (action->menu()) {
2843
item->setMenu(action->menu()->platformMenu());
2852
void QMenu::actionEvent(QActionEvent *e)
2856
setAttribute(Qt::WA_Resized, false);
2858
d->tornPopup->syncWithMenu(this, e);
2859
if (e->type() == QEvent::ActionAdded) {
2861
connect(e->action(), SIGNAL(triggered()), this, SLOT(_q_actionTriggered()));
2862
connect(e->action(), SIGNAL(hovered()), this, SLOT(_q_actionHovered()));
2864
if (QWidgetAction *wa = qobject_cast<QWidgetAction *>(e->action())) {
2865
QWidget *widget = wa->requestWidget(this);
2867
d->widgetItems.insert(wa, widget);
2869
} else if (e->type() == QEvent::ActionRemoved) {
2870
e->action()->disconnect(this);
2871
if (e->action() == d->currentAction)
2872
d->currentAction = 0;
2873
if (QWidgetAction *wa = qobject_cast<QWidgetAction *>(e->action())) {
2874
if (QWidget *widget = d->widgetItems.value(wa))
2875
wa->releaseWidget(widget);
2877
d->widgetItems.remove(e->action());
2880
if (d->platformMenu) {
2881
if (e->type() == QEvent::ActionAdded) {
2882
QPlatformMenuItem *menuItem =
2883
QGuiApplicationPrivate::platformTheme()->createPlatformMenuItem();
2884
menuItem->setTag(reinterpret_cast<quintptr>(e->action()));
2885
QObject::connect(menuItem, SIGNAL(activated()), e->action(), SLOT(trigger()));
2886
copyActionToPlatformItem(e->action(), menuItem);
2887
QPlatformMenuItem* beforeItem = d->platformMenu->menuItemForTag(reinterpret_cast<quintptr>(e->before()));
2888
d->platformMenu->insertMenuItem(menuItem, beforeItem);
2889
} else if (e->type() == QEvent::ActionRemoved) {
2890
QPlatformMenuItem *menuItem = d->platformMenu->menuItemForTag(reinterpret_cast<quintptr>(e->action()));
2891
d->platformMenu->removeMenuItem(menuItem);
2892
} else if (e->type() == QEvent::ActionChanged) {
2893
QPlatformMenuItem *menuItem = d->platformMenu->menuItemForTag(reinterpret_cast<quintptr>(e->action()));
2894
copyActionToPlatformItem(e->action(), menuItem);
2895
d->platformMenu->syncMenuItem(menuItem);
2898
d->platformMenu->syncSeparatorsCollapsible(d->collapsibleSeparators);
2901
#if defined(Q_OS_WINCE) && !defined(QT_NO_MENUBAR)
2903
d->wce_menu = new QMenuPrivate::QWceMenuPrivate;
2904
if (e->type() == QEvent::ActionAdded)
2905
d->wce_menu->addAction(e->action(), d->wce_menu->findAction(e->before()));
2906
else if (e->type() == QEvent::ActionRemoved)
2907
d->wce_menu->removeAction(e->action());
2908
else if (e->type() == QEvent::ActionChanged)
2909
d->wce_menu->syncAction(e->action());
2913
d->updateActionRects();
2922
void QMenu::internalSetSloppyAction()
2924
if (d_func()->sloppyAction)
2925
d_func()->setCurrentAction(d_func()->sloppyAction, 0);
2931
void QMenu::internalDelayedPopup()
2935
//hide the current item
2936
if (QMenu *menu = d->activeMenu) {
2941
if (!d->currentAction || !d->currentAction->isEnabled() || !d->currentAction->menu() ||
2942
!d->currentAction->menu()->isEnabled() || d->currentAction->menu()->isVisible())
2946
d->activeMenu = d->currentAction->menu();
2947
d->activeMenu->d_func()->causedPopup.widget = this;
2948
d->activeMenu->d_func()->causedPopup.action = d->currentAction;
2950
int subMenuOffset = style()->pixelMetric(QStyle::PM_SubMenuOverlap, 0, this);
2951
const QRect actionRect(d->actionRect(d->currentAction));
2952
const QSize menuSize(d->activeMenu->sizeHint());
2953
const QPoint rightPos(mapToGlobal(QPoint(actionRect.right() + subMenuOffset + 1, actionRect.top())));
2955
QPoint pos(rightPos);
2957
//calc sloppy focus buffer
2958
if (style()->styleHint(QStyle::SH_Menu_SloppySubMenus, 0, this)) {
2959
QPoint cur = QCursor::pos();
2960
if (actionRect.contains(mapFromGlobal(cur))) {
2962
pts[0] = QPoint(cur.x(), cur.y() - 2);
2963
pts[3] = QPoint(cur.x(), cur.y() + 2);
2964
if (pos.x() >= cur.x()) {
2965
pts[1] = QPoint(geometry().right(), pos.y());
2966
pts[2] = QPoint(geometry().right(), pos.y() + menuSize.height());
2968
pts[1] = QPoint(pos.x() + menuSize.width(), pos.y());
2969
pts[2] = QPoint(pos.x() + menuSize.width(), pos.y() + menuSize.height());
2972
for(int i = 0; i < 4; i++)
2973
points.setPoint(i, mapFromGlobal(pts[i]));
2974
d->sloppyRegion = QRegion(points);
2979
d->activeMenu->popup(pos);
2983
\fn void QMenu::addAction(QAction *action)
2986
Appends the action \a action to the menu's list of actions.
2988
\sa QMenuBar::addAction(), QWidget::addAction()
2992
\fn void QMenu::aboutToHide()
2995
This signal is emitted just before the menu is hidden from the user.
2997
\sa aboutToShow(), hide()
3001
\fn void QMenu::aboutToShow()
3003
This signal is emitted just before the menu is shown to the user.
3005
\sa aboutToHide(), show()
3009
\fn void QMenu::triggered(QAction *action)
3011
This signal is emitted when an action in this menu is triggered.
3013
\a action is the action that caused the signal to be emitted.
3015
Normally, you connect each menu action's \l{QAction::}{triggered()} signal
3016
to its own custom slot, but sometimes you will want to connect several
3017
actions to a single slot, for example, when you have a group of closely
3018
related actions, such as "left justify", "center", "right justify".
3020
\note This signal is emitted for the main parent menu in a hierarchy.
3021
Hence, only the parent menu needs to be connected to a slot; sub-menus need
3024
\sa hovered(), QAction::triggered()
3028
\fn void QMenu::hovered(QAction *action)
3030
This signal is emitted when a menu action is highlighted; \a action
3031
is the action that caused the signal to be emitted.
3033
Often this is used to update status information.
3035
\sa triggered(), QAction::hovered()
3041
void QMenu::setNoReplayFor(QWidget *noReplayFor)
3044
d_func()->noReplayFor = noReplayFor;
3046
Q_UNUSED(noReplayFor);
3052
QPlatformMenu *QMenu::platformMenu()
3055
return d_func()->platformMenu;
3059
\property QMenu::separatorsCollapsible
3062
\brief whether consecutive separators should be collapsed
3064
This property specifies whether consecutive separators in the menu
3065
should be visually collapsed to a single one. Separators at the
3066
beginning or the end of the menu are also hidden.
3068
By default, this property is true.
3070
bool QMenu::separatorsCollapsible() const
3073
return d->collapsibleSeparators;
3076
void QMenu::setSeparatorsCollapsible(bool collapse)
3079
if (d->collapsibleSeparators == collapse)
3082
d->collapsibleSeparators = collapse;
3085
d->updateActionRects();
3088
if (d->platformMenu)
3089
d->platformMenu->syncSeparatorsCollapsible(collapse);
3094
// for private slots
3095
#include "moc_qmenu.cpp"
3096
#include "qmenu.moc"
3098
#endif // QT_NO_MENU