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

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