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