1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include <qmenubar.h>
41
42#include <qstyle.h>
43#include <qlayout.h>
44#include <qapplication.h>
45#include <qdesktopwidget.h>
46#ifndef QT_NO_ACCESSIBILITY
47# include <qaccessible.h>
48#endif
49#include <qpainter.h>
50#include <qstylepainter.h>
51#include <qevent.h>
52#if QT_CONFIG(mainwindow)
53#include <qmainwindow.h>
54#endif
55#if QT_CONFIG(toolbar)
56#include <qtoolbar.h>
57#endif
58#if QT_CONFIG(toolbutton)
59#include <qtoolbutton.h>
60#endif
61#if QT_CONFIG(whatsthis)
62#include <qwhatsthis.h>
63#endif
64#include <qpa/qplatformtheme.h>
65#include "private/qguiapplication_p.h"
66#include "qpa/qplatformintegration.h"
67#include <private/qdesktopwidget_p.h>
68
69#include "qmenu_p.h"
70#include "qmenubar_p.h"
71#include <private/qscreen_p.h>
72#include "qdebug.h"
73
74QT_BEGIN_NAMESPACE
75
76class QMenuBarExtension : public QToolButton
77{
78public:
79 explicit QMenuBarExtension(QWidget *parent);
80
81 QSize sizeHint() const override;
82 void paintEvent(QPaintEvent *) override;
83};
84
85QMenuBarExtension::QMenuBarExtension(QWidget *parent)
86 : QToolButton(parent)
87{
88 setObjectName(QLatin1String("qt_menubar_ext_button"));
89 setAutoRaise(true);
90#if QT_CONFIG(menu)
91 setPopupMode(QToolButton::InstantPopup);
92#endif
93 setIcon(style()->standardIcon(standardIcon: QStyle::SP_ToolBarHorizontalExtensionButton, option: nullptr, widget: parentWidget()));
94}
95
96void QMenuBarExtension::paintEvent(QPaintEvent *)
97{
98 QStylePainter p(this);
99 QStyleOptionToolButton opt;
100 initStyleOption(option: &opt);
101 // We do not need to draw both extension arrows
102 opt.features &= ~QStyleOptionToolButton::HasMenu;
103 p.drawComplexControl(cc: QStyle::CC_ToolButton, opt);
104}
105
106
107QSize QMenuBarExtension::sizeHint() const
108{
109 int ext = style()->pixelMetric(metric: QStyle::PM_ToolBarExtensionExtent, option: nullptr, widget: parentWidget());
110 return QSize(ext, ext);
111}
112
113
114/*!
115 \internal
116*/
117QAction *QMenuBarPrivate::actionAt(QPoint p) const
118{
119 for(int i = 0; i < actions.size(); ++i) {
120 if(actionRect(actions.at(i)).contains(p))
121 return actions.at(i);
122 }
123 return nullptr;
124}
125
126QRect QMenuBarPrivate::menuRect(bool extVisible) const
127{
128 Q_Q(const QMenuBar);
129
130 int hmargin = q->style()->pixelMetric(metric: QStyle::PM_MenuBarPanelWidth, option: nullptr, widget: q);
131 QRect result = q->rect();
132 result.adjust(dx1: hmargin, dy1: 0, dx2: -hmargin, dy2: 0);
133
134 if (extVisible) {
135 if (q->isRightToLeft())
136 result.setLeft(result.left() + extension->sizeHint().width());
137 else
138 result.setWidth(result.width() - extension->sizeHint().width());
139 }
140
141 if (leftWidget && leftWidget->isVisible()) {
142 QSize sz = leftWidget->sizeHint();
143 if (q->isRightToLeft())
144 result.setRight(result.right() - sz.width());
145 else
146 result.setLeft(result.left() + sz.width());
147 }
148
149 if (rightWidget && rightWidget->isVisible()) {
150 QSize sz = rightWidget->sizeHint();
151 if (q->isRightToLeft())
152 result.setLeft(result.left() + sz.width());
153 else
154 result.setRight(result.right() - sz.width());
155 }
156
157 return result;
158}
159
160bool QMenuBarPrivate::isVisible(QAction *action)
161{
162 return !hiddenActions.contains(t: action);
163}
164
165void QMenuBarPrivate::updateGeometries()
166{
167 Q_Q(QMenuBar);
168 if(!itemsDirty)
169 return;
170 int q_width = q->width()-(q->style()->pixelMetric(metric: QStyle::PM_MenuBarPanelWidth, option: nullptr, widget: q)*2);
171 int q_start = -1;
172 if(leftWidget || rightWidget) {
173 int vmargin = q->style()->pixelMetric(metric: QStyle::PM_MenuBarVMargin, option: nullptr, widget: q)
174 + q->style()->pixelMetric(metric: QStyle::PM_MenuBarPanelWidth, option: nullptr, widget: q);
175 int hmargin = q->style()->pixelMetric(metric: QStyle::PM_MenuBarHMargin, option: nullptr, widget: q)
176 + q->style()->pixelMetric(metric: QStyle::PM_MenuBarPanelWidth, option: nullptr, widget: q);
177 if (leftWidget && leftWidget->isVisible()) {
178 QSize sz = leftWidget->sizeHint();
179 q_width -= sz.width();
180 q_start = sz.width();
181 QPoint pos(hmargin, (q->height() - leftWidget->height()) / 2);
182 QRect vRect = QStyle::visualRect(direction: q->layoutDirection(), boundingRect: q->rect(), logicalRect: QRect(pos, sz));
183 leftWidget->setGeometry(vRect);
184 }
185 if (rightWidget && rightWidget->isVisible()) {
186 QSize sz = rightWidget->sizeHint();
187 q_width -= sz.width();
188 QPoint pos(q->width() - sz.width() - hmargin, vmargin);
189 QRect vRect = QStyle::visualRect(direction: q->layoutDirection(), boundingRect: q->rect(), logicalRect: QRect(pos, sz));
190 rightWidget->setGeometry(vRect);
191 }
192 }
193
194#ifdef Q_OS_MAC
195 if(q->isNativeMenuBar()) {//nothing to see here folks, move along..
196 itemsDirty = false;
197 return;
198 }
199#endif
200 calcActionRects(max_width: q_width, start: q_start);
201 currentAction = nullptr;
202#ifndef QT_NO_SHORTCUT
203 if(itemsDirty) {
204 for(int j = 0; j < shortcutIndexMap.size(); ++j)
205 q->releaseShortcut(id: shortcutIndexMap.value(i: j));
206 shortcutIndexMap.clear();
207 const int actionsCount = actions.count();
208 shortcutIndexMap.reserve(asize: actionsCount);
209 for (int i = 0; i < actionsCount; i++)
210 shortcutIndexMap.append(t: q->grabShortcut(key: QKeySequence::mnemonic(text: actions.at(i)->text())));
211 }
212#endif
213 itemsDirty = false;
214
215 hiddenActions.clear();
216 //this is the menu rectangle without any extension
217 QRect menuRect = this->menuRect(extVisible: false);
218
219 //we try to see if the actions will fit there
220 bool hasHiddenActions = false;
221 for (int i = 0; i < actions.count(); ++i) {
222 const QRect &rect = actionRects.at(i);
223 if (rect.isValid() && !menuRect.contains(r: rect)) {
224 hasHiddenActions = true;
225 break;
226 }
227 }
228
229 //...and if not, determine the ones that fit on the menu with the extension visible
230 if (hasHiddenActions) {
231 menuRect = this->menuRect(extVisible: true);
232 for (int i = 0; i < actions.count(); ++i) {
233 const QRect &rect = actionRects.at(i);
234 if (rect.isValid() && !menuRect.contains(r: rect)) {
235 hiddenActions.append(t: actions.at(i));
236 }
237 }
238 }
239
240 if (hiddenActions.count() > 0) {
241 QMenu *pop = extension->menu();
242 if (!pop) {
243 pop = new QMenu(q);
244 extension->setMenu(pop);
245 }
246 pop->clear();
247 pop->addActions(actions: hiddenActions);
248
249 int vmargin = q->style()->pixelMetric(metric: QStyle::PM_MenuBarVMargin, option: nullptr, widget: q);
250 int x = q->isRightToLeft()
251 ? menuRect.left() - extension->sizeHint().width() + 1
252 : menuRect.right();
253 extension->setGeometry(ax: x, ay: vmargin, aw: extension->sizeHint().width(), ah: menuRect.height() - vmargin*2);
254 extension->show();
255 } else {
256 extension->hide();
257 }
258 q->updateGeometry();
259}
260
261QRect QMenuBarPrivate::actionRect(QAction *act) const
262{
263 const int index = actions.indexOf(t: act);
264
265 //makes sure the geometries are up-to-date
266 const_cast<QMenuBarPrivate*>(this)->updateGeometries();
267
268 if (index < 0 || index >= actionRects.count())
269 return QRect(); // that can happen in case of native menubar
270
271 return actionRects.at(i: index);
272}
273
274void QMenuBarPrivate::focusFirstAction()
275{
276 if(!currentAction) {
277 updateGeometries();
278 int index = 0;
279 while (index < actions.count() && actionRects.at(i: index).isNull()) ++index;
280 if (index < actions.count())
281 setCurrentAction(actions.at(i: index));
282 }
283}
284
285void QMenuBarPrivate::setKeyboardMode(bool b)
286{
287 Q_Q(QMenuBar);
288 if (b && !q->style()->styleHint(stylehint: QStyle::SH_MenuBar_AltKeyNavigation, opt: nullptr, widget: q)) {
289 setCurrentAction(nullptr);
290 return;
291 }
292 keyboardState = b;
293 if(b) {
294 QWidget *fw = QApplication::focusWidget();
295 if (fw && fw != q && fw->window() != QApplication::activePopupWidget())
296 keyboardFocusWidget = fw;
297 focusFirstAction();
298 q->setFocus(Qt::MenuBarFocusReason);
299 } else {
300 if(!popupState)
301 setCurrentAction(nullptr);
302 if(keyboardFocusWidget) {
303 if (QApplication::focusWidget() == q)
304 keyboardFocusWidget->setFocus(Qt::MenuBarFocusReason);
305 keyboardFocusWidget = nullptr;
306 }
307 }
308 q->update();
309}
310
311void QMenuBarPrivate::popupAction(QAction *action, bool activateFirst)
312{
313 Q_Q(QMenuBar);
314 if(!action || !action->menu() || closePopupMode)
315 return;
316 popupState = true;
317 if (action->isEnabled() && action->menu()->isEnabled()) {
318 closePopupMode = 0;
319 activeMenu = action->menu();
320 activeMenu->d_func()->causedPopup.widget = q;
321 activeMenu->d_func()->causedPopup.action = action;
322
323 QRect adjustedActionRect = actionRect(act: action);
324 QPoint pos(q->mapToGlobal(QPoint(adjustedActionRect.left(), adjustedActionRect.bottom() + 1)));
325 QSize popup_size = activeMenu->sizeHint();
326 //we put the popup menu on the screen containing the bottom-center of the action rect
327 QScreen *menubarScreen = q->window()->windowHandle()->screen();
328 QScreen *popupScreen = menubarScreen->virtualSiblingAt(point: pos + QPoint(adjustedActionRect.width() / 2, 0));
329 if (!popupScreen)
330 popupScreen = menubarScreen;
331 QRect screenRect = popupScreen->geometry();
332 pos = QPoint(qMax(a: pos.x(), b: screenRect.x()), qMax(a: pos.y(), b: screenRect.y()));
333 const bool fitUp = (pos.y() - popup_size.height() >= screenRect.top());
334 const bool fitDown = (pos.y() + popup_size.height() <= screenRect.bottom());
335 const bool rtl = q->isRightToLeft();
336 const int actionWidth = adjustedActionRect.width();
337
338 if (!fitUp && !fitDown) { //we should shift the menu
339 bool shouldShiftToRight = !rtl;
340 if (rtl && popup_size.width() > pos.x())
341 shouldShiftToRight = true;
342 else if (actionWidth + popup_size.width() + pos.x() > screenRect.right())
343 shouldShiftToRight = false;
344
345 if (shouldShiftToRight) {
346 pos.rx() += actionWidth + (rtl ? popup_size.width() : 0);
347 } else {
348 //shift to left
349 if (!rtl)
350 pos.rx() -= popup_size.width();
351 }
352 } else if (rtl) {
353 pos.rx() += actionWidth;
354 }
355
356 if(!defaultPopDown || (fitUp && !fitDown))
357 pos.setY(qMax(a: screenRect.y(), b: q->mapToGlobal(QPoint(0, adjustedActionRect.top()-popup_size.height())).y()));
358 QMenuPrivate::get(m: activeMenu)->topData()->initialScreenIndex = QGuiApplication::screens().indexOf(t: popupScreen);
359 activeMenu->popup(pos);
360 if(activateFirst)
361 activeMenu->d_func()->setFirstActionActive();
362 }
363 q->update(actionRect(act: action));
364}
365
366void QMenuBarPrivate::setCurrentAction(QAction *action, bool popup, bool activateFirst)
367{
368 if(currentAction == action && popup == popupState)
369 return;
370
371 autoReleaseTimer.stop();
372
373 doChildEffects = (popup && !activeMenu);
374 Q_Q(QMenuBar);
375 QWidget *fw = nullptr;
376 if(QMenu *menu = activeMenu) {
377 activeMenu = nullptr;
378 if (popup) {
379 fw = q->window()->focusWidget();
380 q->setFocus(Qt::NoFocusReason);
381 }
382 menu->hide();
383 }
384
385 if(currentAction)
386 q->update(actionRect(act: currentAction));
387
388 popupState = popup;
389#if QT_CONFIG(statustip)
390 QAction *previousAction = currentAction;
391#endif
392 currentAction = action;
393 if (action && action->isEnabled()) {
394 activateAction(action, QAction::Hover);
395 if(popup)
396 popupAction(action, activateFirst);
397 q->update(actionRect(act: action));
398#if QT_CONFIG(statustip)
399 } else if (previousAction) {
400 QString empty;
401 QStatusTipEvent tip(empty);
402 QCoreApplication::sendEvent(receiver: q, event: &tip);
403#endif
404 }
405 if (fw)
406 fw->setFocus(Qt::NoFocusReason);
407}
408
409void QMenuBarPrivate::calcActionRects(int max_width, int start) const
410{
411 Q_Q(const QMenuBar);
412
413 if(!itemsDirty)
414 return;
415
416 //let's reinitialize the buffer
417 actionRects.resize(asize: actions.count());
418 actionRects.fill(from: QRect());
419
420 const QStyle *style = q->style();
421
422 const int itemSpacing = style->pixelMetric(metric: QStyle::PM_MenuBarItemSpacing, option: nullptr, widget: q);
423 int max_item_height = 0, separator = -1, separator_start = 0, separator_len = 0;
424
425 //calculate size
426 const QFontMetrics fm = q->fontMetrics();
427 const int hmargin = style->pixelMetric(metric: QStyle::PM_MenuBarHMargin, option: nullptr, widget: q),
428 vmargin = style->pixelMetric(metric: QStyle::PM_MenuBarVMargin, option: nullptr, widget: q),
429 icone = style->pixelMetric(metric: QStyle::PM_SmallIconSize, option: nullptr, widget: q);
430 for(int i = 0; i < actions.count(); i++) {
431 QAction *action = actions.at(i);
432 if(!action->isVisible())
433 continue;
434
435 QSize sz;
436
437 //calc what I think the size is..
438 if(action->isSeparator()) {
439 if (style->styleHint(stylehint: QStyle::SH_DrawMenuBarSeparator, opt: nullptr, widget: q))
440 separator = i;
441 continue; //we don't really position these!
442 } else {
443 const QString s = action->text();
444 QIcon is = action->icon();
445 // If an icon is set, only the icon is visible
446 if (!is.isNull())
447 sz = sz.expandedTo(otherSize: QSize(icone, icone));
448 else if (!s.isEmpty())
449 sz = fm.size(flags: Qt::TextShowMnemonic, str: s);
450 }
451
452 //let the style modify the above size..
453 QStyleOptionMenuItem opt;
454 q->initStyleOption(option: &opt, action);
455 sz = q->style()->sizeFromContents(ct: QStyle::CT_MenuBarItem, opt: &opt, contentsSize: sz, w: q);
456
457 if(!sz.isEmpty()) {
458 { //update the separator state
459 int iWidth = sz.width() + itemSpacing;
460 if(separator == -1)
461 separator_start += iWidth;
462 else
463 separator_len += iWidth;
464 }
465 //maximum height
466 max_item_height = qMax(a: max_item_height, b: sz.height());
467 //append
468 actionRects[i] = QRect(0, 0, sz.width(), sz.height());
469 }
470 }
471
472 //calculate position
473 const int fw = q->style()->pixelMetric(metric: QStyle::PM_MenuBarPanelWidth, option: nullptr, widget: q);
474 int x = fw + ((start == -1) ? hmargin : start) + itemSpacing;
475 int y = fw + vmargin;
476 for(int i = 0; i < actions.count(); i++) {
477 QRect &rect = actionRects[i];
478 if (rect.isNull())
479 continue;
480
481 //resize
482 rect.setHeight(max_item_height);
483
484 //move
485 if(separator != -1 && i >= separator) { //after the separator
486 int left = (max_width - separator_len - hmargin - itemSpacing) + (x - separator_start - hmargin);
487 if(left < separator_start) { //wrap
488 separator_start = x = hmargin;
489 y += max_item_height;
490 }
491 rect.moveLeft(pos: left);
492 } else {
493 rect.moveLeft(pos: x);
494 }
495 rect.moveTop(pos: y);
496
497 //keep moving along..
498 x += rect.width() + itemSpacing;
499
500 //make sure we follow the layout direction
501 rect = QStyle::visualRect(direction: q->layoutDirection(), boundingRect: q->rect(), logicalRect: rect);
502 }
503}
504
505void QMenuBarPrivate::activateAction(QAction *action, QAction::ActionEvent action_e)
506{
507 Q_Q(QMenuBar);
508 if (!action || !action->isEnabled())
509 return;
510 action->activate(event: action_e);
511 if (action_e == QAction::Hover)
512 action->showStatusText(widget: q);
513
514// if(action_e == QAction::Trigger)
515// emit q->activated(action);
516// else if(action_e == QAction::Hover)
517// emit q->highlighted(action);
518}
519
520
521void QMenuBarPrivate::_q_actionTriggered()
522{
523 Q_Q(QMenuBar);
524 if (QAction *action = qobject_cast<QAction *>(object: q->sender())) {
525 emit q->triggered(action);
526 }
527}
528
529void QMenuBarPrivate::_q_actionHovered()
530{
531 Q_Q(QMenuBar);
532 if (QAction *action = qobject_cast<QAction *>(object: q->sender())) {
533 emit q->hovered(action);
534#ifndef QT_NO_ACCESSIBILITY
535 if (QAccessible::isActive()) {
536 int actionIndex = actions.indexOf(t: action);
537 QAccessibleEvent focusEvent(q, QAccessible::Focus);
538 focusEvent.setChild(actionIndex);
539 QAccessible::updateAccessibility(event: &focusEvent);
540 }
541#endif //QT_NO_ACCESSIBILITY
542 }
543}
544
545/*!
546 Initialize \a option with the values from the menu bar and information from \a action. This method
547 is useful for subclasses when they need a QStyleOptionMenuItem, but don't want
548 to fill in all the information themselves.
549
550 \sa QStyleOption::initFrom(), QMenu::initStyleOption()
551*/
552void QMenuBar::initStyleOption(QStyleOptionMenuItem *option, const QAction *action) const
553{
554 if (!option || !action)
555 return;
556 Q_D(const QMenuBar);
557 option->palette = palette();
558 option->state = QStyle::State_None;
559 if (isEnabled() && action->isEnabled())
560 option->state |= QStyle::State_Enabled;
561 else
562 option->palette.setCurrentColorGroup(QPalette::Disabled);
563 option->fontMetrics = fontMetrics();
564 if (d->currentAction && d->currentAction == action) {
565 option->state |= QStyle::State_Selected;
566 if (d->popupState && !d->closePopupMode)
567 option->state |= QStyle::State_Sunken;
568 }
569 if (hasFocus() || d->currentAction)
570 option->state |= QStyle::State_HasFocus;
571 option->menuRect = rect();
572 option->menuItemType = QStyleOptionMenuItem::Normal;
573 option->checkType = QStyleOptionMenuItem::NotCheckable;
574 option->text = action->text();
575 option->icon = action->icon();
576}
577
578/*!
579 \class QMenuBar
580 \brief The QMenuBar class provides a horizontal menu bar.
581
582 \ingroup mainwindow-classes
583 \inmodule QtWidgets
584
585 A menu bar consists of a list of pull-down menu items. You add
586 menu items with addMenu(). For example, asuming that \c menubar
587 is a pointer to a QMenuBar and \c fileMenu is a pointer to a
588 QMenu, the following statement inserts the menu into the menu bar:
589 \snippet code/src_gui_widgets_qmenubar.cpp 0
590
591 The ampersand in the menu item's text sets Alt+F as a shortcut for
592 this menu. (You can use "\&\&" to get a real ampersand in the menu
593 bar.)
594
595 There is no need to lay out a menu bar. It automatically sets its
596 own geometry to the top of the parent widget and changes it
597 appropriately whenever the parent is resized.
598
599 \section1 Usage
600
601 In most main window style applications you would use the
602 \l{QMainWindow::}{menuBar()} function provided in QMainWindow,
603 adding \l{QMenu}s to the menu bar and adding \l{QAction}s to the
604 pop-up menus.
605
606 Example (from the \l{mainwindows/menus}{Menus} example):
607
608 \snippet mainwindows/menus/mainwindow.cpp 9
609
610 Menu items may be removed with removeAction().
611
612 Widgets can be added to menus by using instances of the QWidgetAction
613 class to hold them. These actions can then be inserted into menus
614 in the usual way; see the QMenu documentation for more details.
615
616 \section1 Platform Dependent Look and Feel
617
618 Different platforms have different requirements for the appearance
619 of menu bars and their behavior when the user interacts with them.
620 For example, Windows systems are often configured so that the
621 underlined character mnemonics that indicate keyboard shortcuts
622 for items in the menu bar are only shown when the \uicontrol{Alt} key is
623 pressed.
624
625 \section1 QMenuBar as a Global Menu Bar
626
627 On \macos and on certain Linux desktop environments such as
628 Ubuntu Unity, QMenuBar is a wrapper for using the system-wide menu bar.
629 If you have multiple menu bars in one dialog the outermost menu bar
630 (normally inside a widget with widget flag Qt::Window) will
631 be used for the system-wide menu bar.
632
633 Qt for \macos also provides a menu bar merging feature to make
634 QMenuBar conform more closely to accepted \macos menu bar layout.
635 The merging functionality is based on string matching the title of
636 a QMenu entry. These strings are translated (using QObject::tr())
637 in the "QMenuBar" context. If an entry is moved its slots will still
638 fire as if it was in the original place. The table below outlines
639 the strings looked for and where the entry is placed if matched:
640
641 \table
642 \header \li String matches \li Placement \li Notes
643 \row \li about.*
644 \li Application Menu | About <application name>
645 \li The application name is fetched from the \c {Info.plist} file
646 (see note below). If this entry is not found no About item
647 will appear in the Application Menu.
648 \row \li config, options, setup, settings or preferences
649 \li Application Menu | Preferences
650 \li If this entry is not found the Settings item will be disabled
651 \row \li quit or exit
652 \li Application Menu | Quit <application name>
653 \li If this entry is not found a default Quit item will be
654 created to call QCoreApplication::quit()
655 \endtable
656
657 You can override this behavior by using the QAction::menuRole()
658 property.
659
660 If you want all windows in a Mac application to share one menu
661 bar, you must create a menu bar that does not have a parent.
662 Create a parent-less menu bar this way:
663
664 \snippet code/src_gui_widgets_qmenubar.cpp 1
665
666 \b{Note:} Do \e{not} call QMainWindow::menuBar() to create the
667 shared menu bar, because that menu bar will have the QMainWindow
668 as its parent. That menu bar would only be displayed for the
669 parent QMainWindow.
670
671 \b{Note:} The text used for the application name in the \macos menu
672 bar is obtained from the value set in the \c{Info.plist} file in
673 the application's bundle. See \l{Qt for macOS - Deployment}
674 for more information.
675
676 \b{Note:} On Linux, if the com.canonical.AppMenu.Registrar
677 service is available on the D-Bus session bus, then Qt will
678 communicate with it to install the application's menus into the
679 global menu bar, as described.
680
681 \section1 Examples
682
683 The \l{mainwindows/menus}{Menus} example shows how to use QMenuBar
684 and QMenu. The other \l{Main Window Examples}{main window
685 application examples} also provide menus using these classes.
686
687 \sa QMenu, QShortcut, QAction,
688 {http://developer.apple.com/documentation/UserExperience/Conceptual/AppleHIGuidelines/XHIGIntro/XHIGIntro.html}{Introduction to Apple Human Interface Guidelines},
689 {fowler}{GUI Design Handbook: Menu Bar}, {Menus Example}
690*/
691
692
693void QMenuBarPrivate::init()
694{
695 Q_Q(QMenuBar);
696 q->setSizePolicy(hor: QSizePolicy::MinimumExpanding, ver: QSizePolicy::Minimum);
697 q->setAttribute(Qt::WA_CustomWhatsThis);
698
699 if (!QCoreApplication::testAttribute(attribute: Qt::AA_DontUseNativeMenuBar))
700 platformMenuBar = QGuiApplicationPrivate::platformTheme()->createPlatformMenuBar();
701
702 if (platformMenuBar)
703 q->hide();
704 q->setBackgroundRole(QPalette::Button);
705 handleReparent();
706 q->setMouseTracking(q->style()->styleHint(stylehint: QStyle::SH_MenuBar_MouseTracking, opt: nullptr, widget: q));
707
708 extension = new QMenuBarExtension(q);
709 extension->setFocusPolicy(Qt::NoFocus);
710 extension->hide();
711}
712
713//Gets the next action for keyboard navigation
714QAction *QMenuBarPrivate::getNextAction(const int _start, const int increment) const
715{
716 Q_Q(const QMenuBar);
717 const_cast<QMenuBarPrivate*>(this)->updateGeometries();
718 bool allowActiveAndDisabled = q->style()->styleHint(stylehint: QStyle::SH_Menu_AllowActiveAndDisabled, opt: nullptr, widget: q);
719 const int start = (_start == -1 && increment == -1) ? actions.count() : _start;
720 const int end = increment == -1 ? 0 : actions.count() - 1;
721
722 for (int i = start; i != end;) {
723 i += increment;
724 QAction *current = actions.at(i);
725 if (!actionRects.at(i).isNull() && (allowActiveAndDisabled || current->isEnabled()))
726 return current;
727 }
728
729 if (_start != -1) //let's try from the beginning or the end
730 return getNextAction(start: -1, increment);
731
732 return nullptr;
733}
734
735/*!
736 Constructs a menu bar with parent \a parent.
737*/
738QMenuBar::QMenuBar(QWidget *parent) : QWidget(*new QMenuBarPrivate, parent, { })
739{
740 Q_D(QMenuBar);
741 d->init();
742}
743
744
745/*!
746 Destroys the menu bar.
747*/
748QMenuBar::~QMenuBar()
749{
750 Q_D(QMenuBar);
751 delete d->platformMenuBar;
752 d->platformMenuBar = nullptr;
753}
754
755/*!
756 This convenience function creates a new action with \a text.
757 The function adds the newly created action to the menu's
758 list of actions, and returns it.
759
760 \sa QWidget::addAction(), QWidget::actions()
761*/
762QAction *QMenuBar::addAction(const QString &text)
763{
764 QAction *ret = new QAction(text, this);
765 addAction(action: ret);
766 return ret;
767}
768
769/*!
770 \overload
771
772 This convenience function creates a new action with the given \a
773 text. The action's triggered() signal is connected to the \a
774 receiver's \a member slot. The function adds the newly created
775 action to the menu's list of actions and returns it.
776
777 \sa QWidget::addAction(), QWidget::actions()
778*/
779QAction *QMenuBar::addAction(const QString &text, const QObject *receiver, const char* member)
780{
781 QAction *ret = new QAction(text, this);
782 QObject::connect(sender: ret, SIGNAL(triggered(bool)), receiver, member);
783 addAction(action: ret);
784 return ret;
785}
786
787/*!
788 \fn template<typename Obj, typename PointerToMemberFunctionOrFunctor> QAction *QMenuBar::addAction(const QString &text, const Obj *receiver, PointerToMemberFunctionOrFunctor method)
789
790 \since 5.11
791
792 \overload
793
794 This convenience function creates a new action with the given \a
795 text. The action's triggered() signal is connected to the
796 \a method of the \a receiver. The function adds the newly created
797 action to the menu's list of actions and returns it.
798
799 QMenuBar takes ownership of the returned QAction.
800
801 \sa QWidget::addAction(), QWidget::actions()
802*/
803
804/*!
805 \fn template<typename Functor> QAction *QMenuBar::addAction(const QString &text, Functor functor)
806
807 \since 5.11
808
809 \overload
810
811 This convenience function creates a new action with the given \a
812 text. The action's triggered() signal is connected to the
813 \a functor. The function adds the newly created
814 action to the menu's list of actions and returns it.
815
816 QMenuBar takes ownership of the returned QAction.
817
818 \sa QWidget::addAction(), QWidget::actions()
819*/
820
821/*!
822 Appends a new QMenu with \a title to the menu bar. The menu bar
823 takes ownership of the menu. Returns the new menu.
824
825 \sa QWidget::addAction(), QMenu::menuAction()
826*/
827QMenu *QMenuBar::addMenu(const QString &title)
828{
829 QMenu *menu = new QMenu(title, this);
830 addAction(action: menu->menuAction());
831 return menu;
832}
833
834/*!
835 Appends a new QMenu with \a icon and \a title to the menu bar. The menu bar
836 takes ownership of the menu. Returns the new menu.
837
838 \sa QWidget::addAction(), QMenu::menuAction()
839*/
840QMenu *QMenuBar::addMenu(const QIcon &icon, const QString &title)
841{
842 QMenu *menu = new QMenu(title, this);
843 menu->setIcon(icon);
844 addAction(action: menu->menuAction());
845 return menu;
846}
847
848/*!
849 Appends \a menu to the menu bar. Returns the menu's menuAction(). The menu bar
850 does not take ownership of the menu.
851
852 \note The returned QAction object can be used to hide the corresponding
853 menu.
854
855 \sa QWidget::addAction(), QMenu::menuAction()
856*/
857QAction *QMenuBar::addMenu(QMenu *menu)
858{
859 QAction *action = menu->menuAction();
860 addAction(action);
861 return action;
862}
863
864/*!
865 Appends a separator to the menu.
866*/
867QAction *QMenuBar::addSeparator()
868{
869 QAction *ret = new QAction(this);
870 ret->setSeparator(true);
871 addAction(action: ret);
872 return ret;
873}
874
875/*!
876 This convenience function creates a new separator action, i.e. an
877 action with QAction::isSeparator() returning true. The function inserts
878 the newly created action into this menu bar's list of actions before
879 action \a before and returns it.
880
881 \sa QWidget::insertAction(), addSeparator()
882*/
883QAction *QMenuBar::insertSeparator(QAction *before)
884{
885 QAction *action = new QAction(this);
886 action->setSeparator(true);
887 insertAction(before, action);
888 return action;
889}
890
891/*!
892 This convenience function inserts \a menu before action \a before
893 and returns the menus menuAction().
894
895 \sa QWidget::insertAction(), addMenu()
896*/
897QAction *QMenuBar::insertMenu(QAction *before, QMenu *menu)
898{
899 QAction *action = menu->menuAction();
900 insertAction(before, action);
901 return action;
902}
903
904/*!
905 Returns the QAction that is currently highlighted, if any,
906 else \nullptr.
907*/
908QAction *QMenuBar::activeAction() const
909{
910 Q_D(const QMenuBar);
911 return d->currentAction;
912}
913
914/*!
915 \since 4.1
916
917 Sets the currently highlighted action to \a act.
918*/
919void QMenuBar::setActiveAction(QAction *act)
920{
921 Q_D(QMenuBar);
922 d->setCurrentAction(action: act, popup: true, activateFirst: false);
923}
924
925
926/*!
927 Removes all the actions from the menu bar.
928
929 \note On \macos, menu items that have been merged to the system
930 menu bar are not removed by this function. One way to handle this
931 would be to remove the extra actions yourself. You can set the
932 \l{QAction::MenuRole}{menu role} on the different menus, so that
933 you know ahead of time which menu items get merged and which do
934 not. Then decide what to recreate or remove yourself.
935
936 \sa removeAction()
937*/
938void QMenuBar::clear()
939{
940 QList<QAction*> acts = actions();
941 for(int i = 0; i < acts.size(); i++)
942 removeAction(action: acts[i]);
943}
944
945/*!
946 \property QMenuBar::defaultUp
947 \brief the popup orientation
948
949 The default popup orientation. By default, menus pop "down" the
950 screen. By setting the property to true, the menu will pop "up".
951 You might call this for menus that are \e below the document to
952 which they refer.
953
954 If the menu would not fit on the screen, the other direction is
955 used automatically.
956*/
957void QMenuBar::setDefaultUp(bool b)
958{
959 Q_D(QMenuBar);
960 d->defaultPopDown = !b;
961}
962
963bool QMenuBar::isDefaultUp() const
964{
965 Q_D(const QMenuBar);
966 return !d->defaultPopDown;
967}
968
969/*!
970 \reimp
971*/
972void QMenuBar::resizeEvent(QResizeEvent *)
973{
974 Q_D(QMenuBar);
975 d->itemsDirty = true;
976 d->updateGeometries();
977}
978
979/*!
980 \reimp
981*/
982void QMenuBar::paintEvent(QPaintEvent *e)
983{
984 Q_D(QMenuBar);
985 QPainter p(this);
986 QRegion emptyArea(rect());
987
988 //draw the items
989 for (int i = 0; i < d->actions.count(); ++i) {
990 QAction *action = d->actions.at(i);
991 QRect adjustedActionRect = d->actionRect(act: action);
992 if (adjustedActionRect.isEmpty() || !d->isVisible(action))
993 continue;
994 if(!e->rect().intersects(r: adjustedActionRect))
995 continue;
996
997 emptyArea -= adjustedActionRect;
998 QStyleOptionMenuItem opt;
999 initStyleOption(option: &opt, action);
1000 opt.rect = adjustedActionRect;
1001 p.setClipRect(adjustedActionRect);
1002 style()->drawControl(element: QStyle::CE_MenuBarItem, opt: &opt, p: &p, w: this);
1003 }
1004 //draw border
1005 if (int fw = style()->pixelMetric(metric: QStyle::PM_MenuBarPanelWidth, option: nullptr, widget: this)) {
1006 QRegion borderReg;
1007 borderReg += QRect(0, 0, fw, height()); //left
1008 borderReg += QRect(width()-fw, 0, fw, height()); //right
1009 borderReg += QRect(0, 0, width(), fw); //top
1010 borderReg += QRect(0, height()-fw, width(), fw); //bottom
1011 p.setClipRegion(borderReg);
1012 emptyArea -= borderReg;
1013 QStyleOptionFrame frame;
1014 frame.rect = rect();
1015 frame.palette = palette();
1016 frame.state = QStyle::State_None;
1017 frame.lineWidth = style()->pixelMetric(metric: QStyle::PM_MenuBarPanelWidth, option: &frame);
1018 frame.midLineWidth = 0;
1019 style()->drawPrimitive(pe: QStyle::PE_PanelMenuBar, opt: &frame, p: &p, w: this);
1020 }
1021 p.setClipRegion(emptyArea);
1022 QStyleOptionMenuItem menuOpt;
1023 menuOpt.palette = palette();
1024 menuOpt.state = QStyle::State_None;
1025 menuOpt.menuItemType = QStyleOptionMenuItem::EmptyArea;
1026 menuOpt.checkType = QStyleOptionMenuItem::NotCheckable;
1027 menuOpt.rect = rect();
1028 menuOpt.menuRect = rect();
1029 style()->drawControl(element: QStyle::CE_MenuBarEmptyArea, opt: &menuOpt, p: &p, w: this);
1030}
1031
1032/*!
1033 \reimp
1034*/
1035void QMenuBar::setVisible(bool visible)
1036{
1037 if (isNativeMenuBar()) {
1038 if (!visible)
1039 QWidget::setVisible(false);
1040 return;
1041 }
1042 QWidget::setVisible(visible);
1043}
1044
1045/*!
1046 \reimp
1047*/
1048void QMenuBar::mousePressEvent(QMouseEvent *e)
1049{
1050 Q_D(QMenuBar);
1051 if(e->button() != Qt::LeftButton)
1052 return;
1053
1054 d->mouseDown = true;
1055
1056 QAction *action = d->actionAt(p: e->pos());
1057 if (!action || !d->isVisible(action) || !action->isEnabled()) {
1058 d->setCurrentAction(action: nullptr);
1059#if QT_CONFIG(whatsthis)
1060 if (QWhatsThis::inWhatsThisMode())
1061 QWhatsThis::showText(pos: e->globalPos(), text: d->whatsThis, w: this);
1062#endif
1063 return;
1064 }
1065
1066 if(d->currentAction == action && d->popupState) {
1067 if(QMenu *menu = d->activeMenu) {
1068 d->activeMenu = nullptr;
1069 menu->setAttribute(Qt::WA_NoMouseReplay);
1070 menu->hide();
1071 }
1072 } else {
1073 d->setCurrentAction(action, popup: true);
1074 }
1075}
1076
1077/*!
1078 \reimp
1079*/
1080void QMenuBar::mouseReleaseEvent(QMouseEvent *e)
1081{
1082 Q_D(QMenuBar);
1083 if(e->button() != Qt::LeftButton || !d->mouseDown)
1084 return;
1085
1086 d->mouseDown = false;
1087 QAction *action = d->actionAt(p: e->pos());
1088
1089 // do noting if the action is hidden
1090 if (!d->isVisible(action))
1091 return;
1092 if((d->closePopupMode && action == d->currentAction) || !action || !action->menu()) {
1093 //we set the current action before activating
1094 //so that we let the leave event set the current back to 0
1095 d->setCurrentAction(action, popup: false);
1096 if(action)
1097 d->activateAction(action, action_e: QAction::Trigger);
1098 }
1099 d->closePopupMode = 0;
1100}
1101
1102/*!
1103 \reimp
1104*/
1105void QMenuBar::keyPressEvent(QKeyEvent *e)
1106{
1107 Q_D(QMenuBar);
1108 d->updateGeometries();
1109 int key = e->key();
1110 if(isRightToLeft()) { // in reverse mode open/close key for submenues are reversed
1111 if(key == Qt::Key_Left)
1112 key = Qt::Key_Right;
1113 else if(key == Qt::Key_Right)
1114 key = Qt::Key_Left;
1115 }
1116 if(key == Qt::Key_Tab) //means right
1117 key = Qt::Key_Right;
1118 else if(key == Qt::Key_Backtab) //means left
1119 key = Qt::Key_Left;
1120
1121 bool key_consumed = false;
1122 switch(key) {
1123 case Qt::Key_Up:
1124 case Qt::Key_Down:
1125 case Qt::Key_Enter:
1126 case Qt::Key_Space:
1127 case Qt::Key_Return: {
1128 if (!style()->styleHint(stylehint: QStyle::SH_MenuBar_AltKeyNavigation, opt: nullptr, widget: this) || !d->currentAction)
1129 break;
1130 if(d->currentAction->menu()) {
1131 d->popupAction(action: d->currentAction, activateFirst: true);
1132 } else if(key == Qt::Key_Enter || key == Qt::Key_Return || key == Qt::Key_Space) {
1133 d->activateAction(action: d->currentAction, action_e: QAction::Trigger);
1134 d->setCurrentAction(action: d->currentAction, popup: false);
1135 d->setKeyboardMode(false);
1136 }
1137 key_consumed = true;
1138 break; }
1139
1140 case Qt::Key_Right:
1141 case Qt::Key_Left: {
1142 if(d->currentAction) {
1143 int index = d->actions.indexOf(t: d->currentAction);
1144 if (QAction *nextAction = d->getNextAction(start: index, increment: key == Qt::Key_Left ? -1 : +1)) {
1145 d->setCurrentAction(action: nextAction, popup: d->popupState, activateFirst: true);
1146 key_consumed = true;
1147 }
1148 }
1149 break; }
1150
1151 default:
1152 key_consumed = false;
1153 }
1154
1155#ifndef QT_NO_SHORTCUT
1156 if (!key_consumed && e->matches(key: QKeySequence::Cancel)) {
1157 d->setCurrentAction(action: nullptr);
1158 d->setKeyboardMode(false);
1159 key_consumed = true;
1160 }
1161#endif
1162
1163 if(!key_consumed &&
1164 (!e->modifiers() ||
1165 (e->modifiers()&(Qt::MetaModifier|Qt::AltModifier))) && e->text().length()==1 && !d->popupState) {
1166 int clashCount = 0;
1167 QAction *first = nullptr, *currentSelected = nullptr, *firstAfterCurrent = nullptr;
1168 {
1169 const QChar c = e->text().at(i: 0).toUpper();
1170 for(int i = 0; i < d->actions.size(); ++i) {
1171 if (d->actionRects.at(i).isNull())
1172 continue;
1173 QAction *act = d->actions.at(i);
1174 QString s = act->text();
1175 if(!s.isEmpty()) {
1176 int ampersand = s.indexOf(c: QLatin1Char('&'));
1177 if(ampersand >= 0) {
1178 if(s[ampersand+1].toUpper() == c) {
1179 clashCount++;
1180 if(!first)
1181 first = act;
1182 if(act == d->currentAction)
1183 currentSelected = act;
1184 else if (!firstAfterCurrent && currentSelected)
1185 firstAfterCurrent = act;
1186 }
1187 }
1188 }
1189 }
1190 }
1191 QAction *next_action = nullptr;
1192 if(clashCount >= 1) {
1193 if(clashCount == 1 || !d->currentAction || (currentSelected && !firstAfterCurrent))
1194 next_action = first;
1195 else
1196 next_action = firstAfterCurrent;
1197 }
1198 if(next_action) {
1199 key_consumed = true;
1200 d->setCurrentAction(action: next_action, popup: true, activateFirst: true);
1201 }
1202 }
1203 if(key_consumed)
1204 e->accept();
1205 else
1206 e->ignore();
1207}
1208
1209/*!
1210 \reimp
1211*/
1212void QMenuBar::mouseMoveEvent(QMouseEvent *e)
1213{
1214 Q_D(QMenuBar);
1215 if (!(e->buttons() & Qt::LeftButton)) {
1216 d->mouseDown = false;
1217 // We receive mouse move and mouse press on touch.
1218 // Mouse move will open the menu and mouse press
1219 // will close it, so ignore mouse move.
1220 if (e->source() != Qt::MouseEventNotSynthesized)
1221 return;
1222 }
1223
1224 bool popupState = d->popupState || d->mouseDown;
1225 QAction *action = d->actionAt(p: e->pos());
1226 if ((action && d->isVisible(action)) || !popupState)
1227 d->setCurrentAction(action, popup: popupState);
1228}
1229
1230/*!
1231 \reimp
1232*/
1233void QMenuBar::leaveEvent(QEvent *)
1234{
1235 Q_D(QMenuBar);
1236 if((!hasFocus() && !d->popupState) ||
1237 (d->currentAction && d->currentAction->menu() == nullptr))
1238 d->setCurrentAction(action: nullptr);
1239}
1240
1241QPlatformMenu *QMenuBarPrivate::getPlatformMenu(const QAction *action)
1242{
1243 if (!action || !action->menu())
1244 return nullptr;
1245
1246 QPlatformMenu *platformMenu = action->menu()->platformMenu();
1247 if (!platformMenu && platformMenuBar) {
1248 platformMenu = platformMenuBar->createMenu();
1249 if (platformMenu)
1250 action->menu()->setPlatformMenu(platformMenu);
1251 }
1252
1253 return platformMenu;
1254}
1255
1256QPlatformMenu *QMenuBarPrivate::findInsertionPlatformMenu(const QAction *action)
1257{
1258 Q_Q(QMenuBar);
1259 QPlatformMenu *beforeMenu = nullptr;
1260 for (int beforeIndex = indexOf(act: const_cast<QAction *>(action)) + 1;
1261 !beforeMenu && (beforeIndex < q->actions().size());
1262 ++beforeIndex) {
1263 beforeMenu = getPlatformMenu(action: q->actions().at(i: beforeIndex));
1264 }
1265
1266 return beforeMenu;
1267}
1268
1269void QMenuBarPrivate::copyActionToPlatformMenu(const QAction *action, QPlatformMenu *menu)
1270{
1271 const auto tag = reinterpret_cast<quintptr>(action);
1272 if (menu->tag() != tag)
1273 menu->setTag(tag);
1274 menu->setText(action->text());
1275 menu->setVisible(action->isVisible());
1276 menu->setEnabled(action->isEnabled());
1277}
1278
1279/*!
1280 \reimp
1281*/
1282void QMenuBar::actionEvent(QActionEvent *e)
1283{
1284 Q_D(QMenuBar);
1285 d->itemsDirty = true;
1286
1287 if (d->platformMenuBar) {
1288 QPlatformMenuBar *nativeMenuBar = d->platformMenuBar;
1289 if (!nativeMenuBar)
1290 return;
1291
1292 if (e->type() == QEvent::ActionAdded) {
1293 QPlatformMenu *menu = d->getPlatformMenu(action: e->action());
1294 if (menu) {
1295 d->copyActionToPlatformMenu(action: e->action(), menu);
1296
1297 QPlatformMenu *beforeMenu = d->findInsertionPlatformMenu(action: e->action());
1298 d->platformMenuBar->insertMenu(menu, before: beforeMenu);
1299 }
1300 } else if (e->type() == QEvent::ActionRemoved) {
1301 QPlatformMenu *menu = d->getPlatformMenu(action: e->action());
1302 if (menu)
1303 d->platformMenuBar->removeMenu(menu);
1304 } else if (e->type() == QEvent::ActionChanged) {
1305 QPlatformMenu *cur = d->platformMenuBar->menuForTag(tag: reinterpret_cast<quintptr>(e->action()));
1306 QPlatformMenu *menu = d->getPlatformMenu(action: e->action());
1307
1308 // the menu associated with the action can change, need to
1309 // remove and/or insert the new platform menu
1310 if (menu != cur) {
1311 if (cur)
1312 d->platformMenuBar->removeMenu(menu: cur);
1313 if (menu) {
1314 d->copyActionToPlatformMenu(action: e->action(), menu);
1315
1316 QPlatformMenu *beforeMenu = d->findInsertionPlatformMenu(action: e->action());
1317 d->platformMenuBar->insertMenu(menu, before: beforeMenu);
1318 }
1319 } else if (menu) {
1320 d->copyActionToPlatformMenu(action: e->action(), menu);
1321 d->platformMenuBar->syncMenu(menuItem: menu);
1322 }
1323 }
1324 }
1325
1326 if(e->type() == QEvent::ActionAdded) {
1327 connect(sender: e->action(), SIGNAL(triggered()), receiver: this, SLOT(_q_actionTriggered()));
1328 connect(sender: e->action(), SIGNAL(hovered()), receiver: this, SLOT(_q_actionHovered()));
1329 } else if(e->type() == QEvent::ActionRemoved) {
1330 e->action()->disconnect(receiver: this);
1331 }
1332 // updateGeometries() is also needed for native menu bars because
1333 // it updates shortcutIndexMap
1334 if (isVisible() || isNativeMenuBar())
1335 d->updateGeometries();
1336 if (isVisible())
1337 update();
1338}
1339
1340/*!
1341 \reimp
1342*/
1343void QMenuBar::focusInEvent(QFocusEvent *)
1344{
1345 Q_D(QMenuBar);
1346 if(d->keyboardState)
1347 d->focusFirstAction();
1348}
1349
1350/*!
1351 \reimp
1352*/
1353void QMenuBar::focusOutEvent(QFocusEvent *)
1354{
1355 Q_D(QMenuBar);
1356 if(!d->popupState) {
1357 d->setCurrentAction(action: nullptr);
1358 d->setKeyboardMode(false);
1359 }
1360}
1361
1362/*!
1363 \reimp
1364 */
1365void QMenuBar::timerEvent (QTimerEvent *e)
1366{
1367 Q_D(QMenuBar);
1368 if (e->timerId() == d->autoReleaseTimer.timerId()) {
1369 d->autoReleaseTimer.stop();
1370 d->setCurrentAction(action: nullptr);
1371 }
1372 QWidget::timerEvent(event: e);
1373}
1374
1375
1376void QMenuBarPrivate::handleReparent()
1377{
1378 Q_Q(QMenuBar);
1379 QWidget *newParent = q->parentWidget();
1380
1381 //Note: if parent is reparented, then window may change even if parent doesn't.
1382 // We need to install an avent filter on each parent up to the parent that is
1383 // also a window (for shortcuts)
1384 QWidget *newWindow = newParent ? newParent->window() : nullptr;
1385
1386 QVector<QPointer<QWidget> > newParents;
1387 // Remove event filters on ex-parents, keep them on still-parents
1388 // The parents are always ordered in the vector
1389 foreach (const QPointer<QWidget> &w, oldParents) {
1390 if (w) {
1391 if (newParent == w) {
1392 newParents.append(t: w);
1393 if (newParent != newWindow) //stop at the window
1394 newParent = newParent->parentWidget();
1395 } else {
1396 w->removeEventFilter(obj: q);
1397 }
1398 }
1399 }
1400
1401 // At this point, newParent is the next one to be added to newParents
1402 while (newParent && newParent != newWindow) {
1403 //install event filters all the way up to (excluding) the window
1404 newParents.append(t: newParent);
1405 newParent->installEventFilter(filterObj: q);
1406 newParent = newParent->parentWidget();
1407 }
1408
1409 if (newParent && newWindow) {
1410 // Install the event filter on the window
1411 newParents.append(t: newParent);
1412 newParent->installEventFilter(filterObj: q);
1413 }
1414 oldParents = newParents;
1415
1416 if (platformMenuBar) {
1417 if (newWindow) {
1418 // force the underlying platform window to be created, since
1419 // the platform menubar needs it (and we have no other way to
1420 // discover when the platform window is created)
1421 newWindow->createWinId();
1422 platformMenuBar->handleReparent(newParentWindow: newWindow->windowHandle());
1423 } else {
1424 platformMenuBar->handleReparent(newParentWindow: nullptr);
1425 }
1426 }
1427}
1428
1429/*!
1430 \reimp
1431*/
1432void QMenuBar::changeEvent(QEvent *e)
1433{
1434 Q_D(QMenuBar);
1435 if(e->type() == QEvent::StyleChange) {
1436 d->itemsDirty = true;
1437 setMouseTracking(style()->styleHint(stylehint: QStyle::SH_MenuBar_MouseTracking, opt: nullptr, widget: this));
1438 if(parentWidget())
1439 resize(w: parentWidget()->width(), h: heightForWidth(parentWidget()->width()));
1440 d->updateGeometries();
1441 } else if (e->type() == QEvent::ParentChange) {
1442 d->handleReparent();
1443 } else if (e->type() == QEvent::FontChange
1444 || e->type() == QEvent::ApplicationFontChange) {
1445 d->itemsDirty = true;
1446 d->updateGeometries();
1447 }
1448
1449 QWidget::changeEvent(e);
1450}
1451
1452/*!
1453 \reimp
1454*/
1455bool QMenuBar::event(QEvent *e)
1456{
1457 Q_D(QMenuBar);
1458 switch (e->type()) {
1459 case QEvent::KeyPress: {
1460 QKeyEvent *ke = (QKeyEvent*)e;
1461#if 0
1462 if(!d->keyboardState) { //all keypresses..
1463 d->setCurrentAction(0);
1464 return ;
1465 }
1466#endif
1467 if(ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
1468 keyPressEvent(e: ke);
1469 return true;
1470 }
1471
1472 } break;
1473#ifndef QT_NO_SHORTCUT
1474 case QEvent::Shortcut: {
1475 QShortcutEvent *se = static_cast<QShortcutEvent *>(e);
1476 int shortcutId = se->shortcutId();
1477 for(int j = 0; j < d->shortcutIndexMap.size(); ++j) {
1478 if (shortcutId == d->shortcutIndexMap.value(i: j))
1479 d->_q_internalShortcutActivated(j);
1480 }
1481 } break;
1482#endif
1483 case QEvent::Show:
1484 d->_q_updateLayout();
1485 break;
1486#ifndef QT_NO_SHORTCUT
1487 case QEvent::ShortcutOverride: {
1488 QKeyEvent *kev = static_cast<QKeyEvent*>(e);
1489 //we only filter out escape if there is a current action
1490 if (kev->matches(key: QKeySequence::Cancel) && d->currentAction) {
1491 e->accept();
1492 return true;
1493 }
1494 }
1495 break;
1496#endif
1497#if QT_CONFIG(whatsthis)
1498 case QEvent::QueryWhatsThis:
1499 e->setAccepted(d->whatsThis.size());
1500 if (QAction *action = d->actionAt(p: static_cast<QHelpEvent*>(e)->pos())) {
1501 if (action->whatsThis().size() || action->menu())
1502 e->accept();
1503 }
1504 return true;
1505#endif
1506 case QEvent::LayoutDirectionChange:
1507 d->_q_updateLayout();
1508 break;
1509 default:
1510 break;
1511 }
1512 return QWidget::event(event: e);
1513}
1514
1515/*!
1516 \reimp
1517*/
1518bool QMenuBar::eventFilter(QObject *object, QEvent *event)
1519{
1520 Q_D(QMenuBar);
1521 if (object && (event->type() == QEvent::ParentChange)) //GrandparentChange
1522 d->handleReparent();
1523
1524 if (object == d->leftWidget || object == d->rightWidget) {
1525 switch (event->type()) {
1526 case QEvent::ShowToParent:
1527 case QEvent::HideToParent:
1528 d->_q_updateLayout();
1529 break;
1530 default:
1531 break;
1532 }
1533 }
1534
1535 if (isNativeMenuBar() && event->type() == QEvent::ShowToParent) {
1536 // On some desktops like Unity, the D-Bus menu bar is unregistered
1537 // when the window is hidden. So when the window is shown, we need
1538 // to forcefully re-register it. The only way to force re-registering
1539 // with D-Bus menu is the handleReparent method.
1540 QWidget *widget = qobject_cast<QWidget *>(o: object);
1541 QWindow *handle = widget ? widget->windowHandle() : nullptr;
1542 if (handle != nullptr)
1543 d->platformMenuBar->handleReparent(newParentWindow: handle);
1544 }
1545
1546 if (style()->styleHint(stylehint: QStyle::SH_MenuBar_AltKeyNavigation, opt: nullptr, widget: this)) {
1547 if (d->altPressed) {
1548 switch (event->type()) {
1549 case QEvent::KeyPress:
1550 case QEvent::KeyRelease:
1551 {
1552 QKeyEvent *kev = static_cast<QKeyEvent*>(event);
1553 if (kev->key() == Qt::Key_Alt || kev->key() == Qt::Key_Meta) {
1554 if (event->type() == QEvent::KeyPress) // Alt-press does not interest us, we have the shortcut-override event
1555 break;
1556 d->setKeyboardMode(!d->keyboardState);
1557 }
1558 }
1559 Q_FALLTHROUGH();
1560 case QEvent::MouseButtonPress:
1561 case QEvent::MouseButtonRelease:
1562 case QEvent::MouseMove:
1563 case QEvent::FocusIn:
1564 case QEvent::FocusOut:
1565 case QEvent::ActivationChange:
1566 case QEvent::Shortcut:
1567 d->altPressed = false;
1568 qApp->removeEventFilter(obj: this);
1569 break;
1570 default:
1571 break;
1572 }
1573 } else if (isVisible()) {
1574 if (event->type() == QEvent::ShortcutOverride) {
1575 QKeyEvent *kev = static_cast<QKeyEvent*>(event);
1576 if ((kev->key() == Qt::Key_Alt || kev->key() == Qt::Key_Meta)
1577 && kev->modifiers() == Qt::AltModifier) {
1578 d->altPressed = true;
1579 qApp->installEventFilter(filterObj: this);
1580 }
1581 }
1582 }
1583 }
1584
1585 return false;
1586}
1587
1588/*!
1589 Returns the QAction at \a pt. Returns \nullptr if there is no action at \a pt or if
1590the location has a separator.
1591
1592 \sa addAction(), addSeparator()
1593*/
1594QAction *QMenuBar::actionAt(const QPoint &pt) const
1595{
1596 Q_D(const QMenuBar);
1597 return d->actionAt(p: pt);
1598}
1599
1600/*!
1601 Returns the geometry of action \a act as a QRect.
1602
1603 \sa actionAt()
1604*/
1605QRect QMenuBar::actionGeometry(QAction *act) const
1606{
1607 Q_D(const QMenuBar);
1608 return d->actionRect(act);
1609}
1610
1611/*!
1612 \reimp
1613*/
1614QSize QMenuBar::minimumSizeHint() const
1615{
1616 Q_D(const QMenuBar);
1617 const bool as_gui_menubar = !isNativeMenuBar();
1618
1619 ensurePolished();
1620 QSize ret(0, 0);
1621 const_cast<QMenuBarPrivate*>(d)->updateGeometries();
1622 const int hmargin = style()->pixelMetric(metric: QStyle::PM_MenuBarHMargin, option: nullptr, widget: this);
1623 const int vmargin = style()->pixelMetric(metric: QStyle::PM_MenuBarVMargin, option: nullptr, widget: this);
1624 int fw = style()->pixelMetric(metric: QStyle::PM_MenuBarPanelWidth, option: nullptr, widget: this);
1625 int spaceBelowMenuBar = style()->styleHint(stylehint: QStyle::SH_MainWindow_SpaceBelowMenuBar, opt: nullptr, widget: this);
1626 if(as_gui_menubar) {
1627 int w = parentWidget() ? parentWidget()->width() : QDesktopWidgetPrivate::width();
1628 d->calcActionRects(max_width: w - (2 * fw), start: 0);
1629 for (int i = 0; ret.isNull() && i < d->actions.count(); ++i)
1630 ret = d->actionRects.at(i).size();
1631 if (!d->extension->isHidden())
1632 ret += QSize(d->extension->sizeHint().width(), 0);
1633 ret += QSize(2*fw + hmargin, 2*fw + vmargin);
1634 }
1635 int margin = 2*vmargin + 2*fw + spaceBelowMenuBar;
1636 if(d->leftWidget) {
1637 QSize sz = d->leftWidget->minimumSizeHint();
1638 ret.setWidth(ret.width() + sz.width());
1639 if(sz.height() + margin > ret.height())
1640 ret.setHeight(sz.height() + margin);
1641 }
1642 if(d->rightWidget) {
1643 QSize sz = d->rightWidget->minimumSizeHint();
1644 ret.setWidth(ret.width() + sz.width());
1645 if(sz.height() + margin > ret.height())
1646 ret.setHeight(sz.height() + margin);
1647 }
1648 if(as_gui_menubar) {
1649 QStyleOptionMenuItem opt;
1650 opt.rect = rect();
1651 opt.menuRect = rect();
1652 opt.state = QStyle::State_None;
1653 opt.menuItemType = QStyleOptionMenuItem::Normal;
1654 opt.checkType = QStyleOptionMenuItem::NotCheckable;
1655 opt.palette = palette();
1656 return (style()->sizeFromContents(ct: QStyle::CT_MenuBar, opt: &opt,
1657 contentsSize: ret.expandedTo(otherSize: QApplication::globalStrut()),
1658 w: this));
1659 }
1660 return ret;
1661}
1662
1663/*!
1664 \reimp
1665*/
1666QSize QMenuBar::sizeHint() const
1667{
1668 Q_D(const QMenuBar);
1669 const bool as_gui_menubar = !isNativeMenuBar();
1670
1671 ensurePolished();
1672 QSize ret(0, 0);
1673 const_cast<QMenuBarPrivate*>(d)->updateGeometries();
1674 const int hmargin = style()->pixelMetric(metric: QStyle::PM_MenuBarHMargin, option: nullptr, widget: this);
1675 const int vmargin = style()->pixelMetric(metric: QStyle::PM_MenuBarVMargin, option: nullptr, widget: this);
1676 int fw = style()->pixelMetric(metric: QStyle::PM_MenuBarPanelWidth, option: nullptr, widget: this);
1677 int spaceBelowMenuBar = style()->styleHint(stylehint: QStyle::SH_MainWindow_SpaceBelowMenuBar, opt: nullptr, widget: this);
1678 if(as_gui_menubar) {
1679 const int w = parentWidget() ? parentWidget()->width() : QDesktopWidgetPrivate::width();
1680 d->calcActionRects(max_width: w - (2 * fw), start: 0);
1681 for (int i = 0; i < d->actionRects.count(); ++i) {
1682 const QRect &actionRect = d->actionRects.at(i);
1683 ret = ret.expandedTo(otherSize: QSize(actionRect.x() + actionRect.width(), actionRect.y() + actionRect.height()));
1684 }
1685 //the action geometries already contain the top and left
1686 //margins. So we only need to add those from right and bottom.
1687 ret += QSize(fw + hmargin, fw + vmargin);
1688 }
1689 int margin = 2*vmargin + 2*fw + spaceBelowMenuBar;
1690 if(d->leftWidget) {
1691 QSize sz = d->leftWidget->sizeHint();
1692 sz.rheight() += margin;
1693 ret = ret.expandedTo(otherSize: sz);
1694 }
1695 if(d->rightWidget) {
1696 QSize sz = d->rightWidget->sizeHint();
1697 ret.setWidth(ret.width() + sz.width());
1698 if(sz.height() + margin > ret.height())
1699 ret.setHeight(sz.height() + margin);
1700 }
1701 if(as_gui_menubar) {
1702 QStyleOptionMenuItem opt;
1703 opt.rect = rect();
1704 opt.menuRect = rect();
1705 opt.state = QStyle::State_None;
1706 opt.menuItemType = QStyleOptionMenuItem::Normal;
1707 opt.checkType = QStyleOptionMenuItem::NotCheckable;
1708 opt.palette = palette();
1709 return (style()->sizeFromContents(ct: QStyle::CT_MenuBar, opt: &opt,
1710 contentsSize: ret.expandedTo(otherSize: QApplication::globalStrut()),
1711 w: this));
1712 }
1713 return ret;
1714}
1715
1716/*!
1717 \reimp
1718*/
1719int QMenuBar::heightForWidth(int) const
1720{
1721 Q_D(const QMenuBar);
1722 const bool as_gui_menubar = !isNativeMenuBar();
1723
1724 const_cast<QMenuBarPrivate*>(d)->updateGeometries();
1725 int height = 0;
1726 const int vmargin = style()->pixelMetric(metric: QStyle::PM_MenuBarVMargin, option: nullptr, widget: this);
1727 int fw = style()->pixelMetric(metric: QStyle::PM_MenuBarPanelWidth, option: nullptr, widget: this);
1728 int spaceBelowMenuBar = style()->styleHint(stylehint: QStyle::SH_MainWindow_SpaceBelowMenuBar, opt: nullptr, widget: this);
1729 if(as_gui_menubar) {
1730 for (int i = 0; i < d->actionRects.count(); ++i)
1731 height = qMax(a: height, b: d->actionRects.at(i).height());
1732 if (height) //there is at least one non-null item
1733 height += spaceBelowMenuBar;
1734 height += 2*fw;
1735 height += 2*vmargin;
1736 }
1737 int margin = 2*vmargin + 2*fw + spaceBelowMenuBar;
1738 if(d->leftWidget)
1739 height = qMax(a: d->leftWidget->sizeHint().height() + margin, b: height);
1740 if(d->rightWidget)
1741 height = qMax(a: d->rightWidget->sizeHint().height() + margin, b: height);
1742 if(as_gui_menubar) {
1743 QStyleOptionMenuItem opt;
1744 opt.init(w: this);
1745 opt.menuRect = rect();
1746 opt.state = QStyle::State_None;
1747 opt.menuItemType = QStyleOptionMenuItem::Normal;
1748 opt.checkType = QStyleOptionMenuItem::NotCheckable;
1749 return style()->sizeFromContents(ct: QStyle::CT_MenuBar, opt: &opt, contentsSize: QSize(0, height), w: this).height(); //not pretty..
1750 }
1751 return height;
1752}
1753
1754/*!
1755 \internal
1756*/
1757void QMenuBarPrivate::_q_internalShortcutActivated(int id)
1758{
1759 Q_Q(QMenuBar);
1760 QAction *act = actions.at(i: id);
1761 if (act && act->menu()) {
1762 if (QPlatformMenu *platformMenu = act->menu()->platformMenu()) {
1763 platformMenu->showPopup(parentWindow: q->windowHandle(), targetRect: actionRects.at(i: id), item: nullptr);
1764 return;
1765 }
1766 }
1767
1768 keyboardFocusWidget = QApplication::focusWidget();
1769 setCurrentAction(action: act, popup: true, activateFirst: true);
1770 if (act && !act->menu()) {
1771 activateAction(action: act, action_e: QAction::Trigger);
1772 //100 is the same as the default value in QPushButton::animateClick
1773 autoReleaseTimer.start(msec: 100, obj: q);
1774 } else if (act && q->style()->styleHint(stylehint: QStyle::SH_MenuBar_AltKeyNavigation, opt: nullptr, widget: q)) {
1775 // When we open a menu using a shortcut, we should end up in keyboard state
1776 setKeyboardMode(true);
1777 }
1778}
1779
1780void QMenuBarPrivate::_q_updateLayout()
1781{
1782 Q_Q(QMenuBar);
1783 itemsDirty = true;
1784 if (q->isVisible()) {
1785 updateGeometries();
1786 q->update();
1787 }
1788}
1789
1790/*!
1791 \fn void QMenuBar::setCornerWidget(QWidget *widget, Qt::Corner corner)
1792
1793 This sets the given \a widget to be shown directly on the left of the first
1794 menu item, or on the right of the last menu item, depending on \a corner.
1795
1796 The menu bar takes ownership of \a widget, reparenting it into the menu bar.
1797 However, if the \a corner already contains a widget, this previous widget
1798 will no longer be managed and will still be a visible child of the menu bar.
1799
1800 \note Using a corner other than Qt::TopRightCorner or Qt::TopLeftCorner
1801 will result in a warning.
1802*/
1803void QMenuBar::setCornerWidget(QWidget *w, Qt::Corner corner)
1804{
1805 Q_D(QMenuBar);
1806 switch (corner) {
1807 case Qt::TopLeftCorner:
1808 if (d->leftWidget)
1809 d->leftWidget->removeEventFilter(obj: this);
1810 d->leftWidget = w;
1811 break;
1812 case Qt::TopRightCorner:
1813 if (d->rightWidget)
1814 d->rightWidget->removeEventFilter(obj: this);
1815 d->rightWidget = w;
1816 break;
1817 default:
1818 qWarning(msg: "QMenuBar::setCornerWidget: Only TopLeftCorner and TopRightCorner are supported");
1819 return;
1820 }
1821
1822 if (w) {
1823 w->setParent(this);
1824 w->installEventFilter(filterObj: this);
1825 }
1826
1827 d->_q_updateLayout();
1828}
1829
1830/*!
1831 Returns the widget on the left of the first or on the right of the last menu
1832 item, depending on \a corner.
1833
1834 \note Using a corner other than Qt::TopRightCorner or Qt::TopLeftCorner
1835 will result in a warning.
1836*/
1837QWidget *QMenuBar::cornerWidget(Qt::Corner corner) const
1838{
1839 Q_D(const QMenuBar);
1840 QWidget *w = nullptr;
1841 switch(corner) {
1842 case Qt::TopLeftCorner:
1843 w = d->leftWidget;
1844 break;
1845 case Qt::TopRightCorner:
1846 w = d->rightWidget;
1847 break;
1848 default:
1849 qWarning(msg: "QMenuBar::cornerWidget: Only TopLeftCorner and TopRightCorner are supported");
1850 break;
1851 }
1852
1853 return w;
1854}
1855
1856/*!
1857 \property QMenuBar::nativeMenuBar
1858 \brief Whether or not a menubar will be used as a native menubar on platforms that support it
1859 \since 4.6
1860
1861 This property specifies whether or not the menubar should be used as a native menubar on
1862 platforms that support it. The currently supported platforms are \macos, and
1863 Linux desktops which use the com.canonical.dbusmenu D-Bus interface (such as Ubuntu Unity).
1864 If this property is \c true, the menubar is used in the native menubar and is not in the window of
1865 its parent; if \c false the menubar remains in the window. On other platforms,
1866 setting this attribute has no effect, and reading this attribute will always return \c false.
1867
1868 The default is to follow whether the Qt::AA_DontUseNativeMenuBar attribute
1869 is set for the application. Explicitly setting this property overrides
1870 the presence (or absence) of the attribute.
1871*/
1872
1873void QMenuBar::setNativeMenuBar(bool nativeMenuBar)
1874{
1875 Q_D(QMenuBar);
1876 if (nativeMenuBar != bool(d->platformMenuBar)) {
1877 if (!nativeMenuBar) {
1878 delete d->platformMenuBar;
1879 d->platformMenuBar = nullptr;
1880 } else {
1881 if (!d->platformMenuBar)
1882 d->platformMenuBar = QGuiApplicationPrivate::platformTheme()->createPlatformMenuBar();
1883 }
1884
1885 updateGeometry();
1886 if (!nativeMenuBar && parentWidget())
1887 setVisible(true);
1888 }
1889}
1890
1891bool QMenuBar::isNativeMenuBar() const
1892{
1893 Q_D(const QMenuBar);
1894 return bool(d->platformMenuBar);
1895}
1896
1897/*!
1898 \internal
1899*/
1900QPlatformMenuBar *QMenuBar::platformMenuBar()
1901{
1902 Q_D(const QMenuBar);
1903 return d->platformMenuBar;
1904}
1905
1906/*!
1907 \fn void QMenuBar::triggered(QAction *action)
1908
1909 This signal is emitted when an action in a menu belonging to this menubar
1910 is triggered as a result of a mouse click; \a action is the action that
1911 caused the signal to be emitted.
1912
1913 \note QMenuBar has to have ownership of the QMenu in order this signal to work.
1914
1915 Normally, you connect each menu action to a single slot using
1916 QAction::triggered(), but sometimes you will want to connect
1917 several items to a single slot (most often if the user selects
1918 from an array). This signal is useful in such cases.
1919
1920 \sa hovered(), QAction::triggered()
1921*/
1922
1923/*!
1924 \fn void QMenuBar::hovered(QAction *action)
1925
1926 This signal is emitted when a menu action is highlighted; \a action
1927 is the action that caused the event to be sent.
1928
1929 Often this is used to update status information.
1930
1931 \sa triggered(), QAction::hovered()
1932*/
1933
1934// for private slots
1935
1936QT_END_NAMESPACE
1937
1938#include <moc_qmenubar.cpp>
1939

source code of qtbase/src/widgets/widgets/qmenubar.cpp