Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickmenubar.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 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// Qt-Security score:significant reason:default
4
8#include "qquickmenu_p.h"
10
11#include <QtGui/private/qguiapplication_p.h>
12#include <QtGui/qpa/qplatformtheme.h>
13
14#include <QtQml/qqmlcontext.h>
15#include <QtQml/qqmlcomponent.h>
16#include <QtQml/qqmlengine.h>
17
19
20/*!
21 \qmltype MenuBar
22 \inherits Container
23//! \nativetype QQuickMenuBar
24 \inqmlmodule QtQuick.Controls
25 \since 5.10
26 \ingroup qtquickcontrols-menus
27 \ingroup qtquickcontrols-focusscopes
28 \brief Provides a window menu bar.
29
30 \image qtquickcontrols-menubar.png
31 {Menu bar with File, Edit, and View menus}
32
33 MenuBar consists of drop-down menus, and is normally located at the top
34 edge of the window.
35
36 \quotefromfile qtquickcontrols-menubar.qml
37 \skipuntil begin
38 \printto skipfrom
39 \skipuntil skipto
40 \printto end
41
42 Typically, menus are statically declared as children of the menu bar, but
43 MenuBar also provides API to \l {addMenu}{add}, \l {insertMenu}{insert},
44 \l {removeMenu}{remove}, and \l {takeMenu}{take} menus dynamically. The
45 menus in a menu bar can be accessed using \l menuAt().
46
47 \section1 Native menu bars
48
49 Since Qt 6.8, a MenuBar is implemented as a native menu bar on \macos. As a
50 result, all Menus, MenuItems and MenuBarItems within a MenuBar will also be native.
51 While this has the advantage that everything will look native, it also comes with the
52 disadvantage that the delegates set on the mentioned controls will not be used
53 for rendering.
54 If a native MenuBar is not wanted, you can set
55 \l {Qt::AA_DontUseNativeMenuBar}{QGuiApplication::setAttribute(Qt::AA_DontUseNativeMenuBar)}
56 to disable it.
57
58 \sa {Customizing MenuBar}, Menu, MenuBarItem, {Menu Controls},
59 {Focus Management in Qt Quick Controls}
60*/
61
62Q_STATIC_LOGGING_CATEGORY(lcMenuBar, "qt.quick.controls.menubar")
63
64static const char* kCreatedFromDelegate = "_qt_createdFromDelegate";
65
66QQuickItem *QQuickMenuBarPrivate::createItemFromDelegate()
67{
68 Q_Q(QQuickMenuBar);
69 Q_ASSERT(delegate);
70 QQmlContext *context = delegate->creationContext();
71 if (!context)
72 context = qmlContext(q);
73
74 QObject *object = delegate->beginCreate(context);
75 QQuickItem *item = qobject_cast<QQuickItem *>(object);
76 if (!item) {
77 delete object;
78 return nullptr;
79 }
80
81 QQml_setParent_noEvent(item, q);
82 delegate->completeCreate();
83
84 return item;
85}
86
87QQuickMenuBarItem *QQuickMenuBarPrivate::createMenuBarItem(QQuickMenu *menu)
88{
89 Q_Q(QQuickMenuBar);
90
91 QQuickMenuBarItem *menuBarItem = nullptr;
92 if (delegate) {
93 QQuickItem *item = createItemFromDelegate();
94 menuBarItem = qobject_cast<QQuickMenuBarItem *>(item);
95 if (!menuBarItem) {
96 qmlWarning(q) << "cannot insert menu: the delegate is not a MenuBarItem.";
97 delete item;
98 }
99 }
100
101 if (!menuBarItem) {
102 // When we fail to create a delegate item, create a hidden placeholder
103 // instead. This is needed, since we store the menus inside the container
104 // using MenuBarItem. And without a MenuBarItem, we would therefore lose
105 // the menu, even if the delegate is changed later.
106 qCDebug(lcMenuBar) << "creating hidden placeholder MenuBarItem for:" << menu->title();
107 menuBarItem = new QQuickMenuBarItem(q);
108 menuBarItem->setParentItem(q);
109 menuBarItem->setVisible(false);
110 }
111
112 menuBarItem->setMenu(menu);
113
114 // Tag the menuBarItem, so that we know which container items to change if the
115 // delegate is changed. This is needed since you can add MenuBarItems directly
116 // to the menu bar, which should not change when the delegate changes.
117 menuBarItem->setProperty(kCreatedFromDelegate, true);
118
119 return menuBarItem;
120}
121
122void QQuickMenuBarPrivate::openCurrentMenu()
123{
124 if (!currentItem || currentMenuOpen)
125 return;
126 QQuickMenu *menu = currentItem->menu();
127 if (!menu || menu->isOpened())
128 return;
129
130#ifdef Q_OS_MACOS
131 // On macOS, the menu should open underneath the MenuBar
132 Q_Q(QQuickMenuBar);
133 const QPointF posInParentItem = q->mapToItem(currentItem, {currentItem->x(), q->height()});
134#else
135 // On other platforms, it should open underneath the MenuBarItem
136 const QPointF posInParentItem{0, currentItem->y() + currentItem->height()};
137#endif
138
139 // Store explicit if the current menu is logically supposed to be open.
140 // menu->isVisible() is async when using top-level menus, and will not become
141 // "true" before the menu is actually shown by the OS. This will cause us to
142 // lose track of if a menu is (supposed to be) open, if relying on menu->isVisible().
143 currentMenuOpen = true;
144
145 // The position should be the coordinate system of the parent item. Note that
146 // the parentItem() of a menu will be the MenuBarItem (currentItem), and not the
147 // MenuBar (even if parent() usually points to the MenuBar).
148 menu->popup(posInParentItem);
149}
150
151void QQuickMenuBarPrivate::closeCurrentMenu()
152{
153 if (!currentItem || !currentMenuOpen)
154 return;
155 currentMenuOpen = false;
156 QQuickMenu *menu = currentItem->menu();
157 QScopedValueRollback triggerRollback(closingCurrentMenu, true);
158 menu->dismiss();
159}
160
161void QQuickMenuBarPrivate::activateMenuItem(int index)
162{
163 if (!currentItem)
164 return;
165 QQuickMenu *menu = currentItem->menu();
166 if (!menu)
167 return;
168 menu->setCurrentIndex(index);
169}
170
171void QQuickMenuBarPrivate::activateItem(QQuickMenuBarItem *item)
172{
173 if (currentItem == item)
174 return;
175
176 const bool stayOpen = currentMenuOpen;
177
178 if (currentItem) {
179 currentItem->setHighlighted(false);
180 closeCurrentMenu();
181 }
182
183 currentItem = item;
184
185 if (currentItem) {
186 currentItem->setHighlighted(true);
187 if (stayOpen)
188 openCurrentMenu();
189 }
190}
191
192void QQuickMenuBarPrivate::activateNextItem()
193{
194 int index = currentItem ? contentModel->indexOf(currentItem, nullptr) : -1;
195 if (index >= contentModel->count() - 1)
196 index = -1;
197 activateItem(qobject_cast<QQuickMenuBarItem *>(itemAt(++index)));
198}
199
200void QQuickMenuBarPrivate::activatePreviousItem()
201{
202 int index = currentItem ? contentModel->indexOf(currentItem, nullptr) : contentModel->count();
203 if (index <= 0)
204 index = contentModel->count();
205 activateItem(qobject_cast<QQuickMenuBarItem *>(itemAt(--index)));
206}
207
208void QQuickMenuBarPrivate::onItemHovered()
209{
210 Q_Q(QQuickMenuBar);
211 QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(q->sender());
212 if (!item || item == currentItem || !item->isHovered() || !item->isEnabled() || QQuickMenuBarItemPrivate::get(item)->touchId != -1)
213 return;
214
215 activateItem(item);
216}
217
218void QQuickMenuBarPrivate::onItemTriggered()
219{
220 Q_Q(QQuickMenuBar);
221 QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(q->sender());
222 if (!item)
223 return;
224
225 if (item == currentItem) {
226 if (currentMenuOpen) {
227 closeCurrentMenu();
228 currentItem->forceActiveFocus();
229 } else {
230 openCurrentMenu();
231 }
232 } else {
233 activateItem(item);
234 openCurrentMenu();
235 }
236}
237
238void QQuickMenuBarPrivate::onMenuAboutToHide(QQuickMenu *menu)
239{
240 if (closingCurrentMenu) {
241 // We only react on a menu closing if it's
242 // initiated from outside of QQuickMenuBar.
243 return;
244 }
245
246 if (!currentItem || currentItem->menu() != menu)
247 return;
248
249 currentMenuOpen = false;
250
251 if (!currentItem->isHighlighted() || currentItem->isHovered())
252 return;
253
254 activateItem(nullptr);
255}
256
257qreal QQuickMenuBarPrivate::getContentWidth() const
258{
259 Q_Q(const QQuickMenuBar);
260 const int count = contentModel->count();
261 qreal totalWidth = qMax(0, count - 1) * spacing;
262 for (int i = 0; i < count; ++i) {
263 QQuickItem *item = q->itemAt(i);
264 if (item)
265 totalWidth += item->implicitWidth();
266 }
267 return totalWidth;
268}
269
270qreal QQuickMenuBarPrivate::getContentHeight() const
271{
272 Q_Q(const QQuickMenuBar);
273 const int count = contentModel->count();
274 qreal maxHeight = 0;
275 for (int i = 0; i < count; ++i) {
276 QQuickItem *item = q->itemAt(i);
277 if (item)
278 maxHeight = qMax(maxHeight, item->implicitHeight());
279 }
280 return maxHeight;
281}
282
283void QQuickMenuBarPrivate::itemImplicitWidthChanged(QQuickItem *item)
284{
285 QQuickContainerPrivate::itemImplicitWidthChanged(item);
286 if (item != contentItem)
287 updateImplicitContentWidth();
288}
289
290void QQuickMenuBarPrivate::itemImplicitHeightChanged(QQuickItem *item)
291{
292 QQuickContainerPrivate::itemImplicitHeightChanged(item);
293 if (item != contentItem)
294 updateImplicitContentHeight();
295}
296
297void QQuickMenuBarPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj)
298{
299 auto menuBar = static_cast<QQuickMenuBar *>(prop->object);
300 auto menuBarPriv = QQuickMenuBarPrivate::get(menuBar);
301
302 if (auto *menu = qobject_cast<QQuickMenu *>(obj)) {
303 QQuickMenuBarItem *delegateItem = menuBarPriv->createMenuBarItem(menu);
304 menuBarPriv->insertMenu(menuBar->count(), menu, delegateItem);
305 QQuickContainerPrivate::contentData_append(prop, delegateItem);
306 return;
307 }
308
309 if (auto *menuBarItem = qobject_cast<QQuickMenuBarItem *>(obj)) {
310 menuBarPriv->insertMenu(menuBar->count(), menuBarItem->menu(), menuBarItem);
311 QQuickContainerPrivate::contentData_append(prop, menuBarItem);
312 return;
313 }
314
315 QQuickContainerPrivate::contentData_append(prop, obj);
316}
317
318void QQuickMenuBarPrivate::menus_append(QQmlListProperty<QQuickMenu> *prop, QQuickMenu *obj)
319{
320 // This function is only called if the application assigns a list of menus
321 // directly to the 'menus' property. Otherwise, contentData_append is used.
322 // Since the functions belonging to the 'menus' list anyway returns data from
323 // the menuBar, calls such as "menuBar.menus.length" works as expected
324 // regardless of how the menus were added.
325 QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object);
326 menuBar->addMenu(obj);
327}
328
329qsizetype QQuickMenuBarPrivate::menus_count(QQmlListProperty<QQuickMenu> *prop)
330{
331 QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object);
332 return menuBar->count();
333}
334
335QQuickMenu *QQuickMenuBarPrivate::menus_at(QQmlListProperty<QQuickMenu> *prop, qsizetype index)
336{
337 QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object);
338 return menuBar->menuAt(index);
339}
340
341void QQuickMenuBarPrivate::menus_clear(QQmlListProperty<QQuickMenu> *prop)
342{
343 QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object);
344 for (int count = menuBar->count(); count > 0; count = menuBar->count())
345 menuBar->takeMenu(count - 1);
346}
347
348QPalette QQuickMenuBarPrivate::defaultPalette() const
349{
350 return QQuickTheme::palette(QQuickTheme::MenuBar);
351}
352
353QWindow* QQuickMenuBarPrivate::window() const
354{
355 Q_Q(const QQuickMenuBar);
356 QObject *obj = q->parent();
357 while (obj) {
358 if (QWindow *window = qobject_cast<QWindow *>(obj))
359 return window;
360 QQuickItem *item = qobject_cast<QQuickItem *>(obj);
361 if (item && item->window())
362 return item->window();
363 obj = obj->parent();
364 }
365 return nullptr;
366}
367
368int QQuickMenuBarPrivate::menuIndex(QQuickMenu *menu) const
369{
370 Q_Q(const QQuickMenuBar);
371 for (int i = 0; i < q->count(); ++i) {
372 if (q->menuAt(i) == menu)
373 return i;
374 }
375
376 return -1;
377}
378
379QPlatformMenuBar* QQuickMenuBarPrivate::nativeHandle() const
380{
381 return handle.get();
382}
383
384void QQuickMenuBarPrivate::insertNativeMenu(QQuickMenu *menu)
385{
386 Q_Q(QQuickMenuBar);
387 Q_ASSERT(handle);
388 Q_ASSERT(menu);
389
390 QPlatformMenu *insertBeforeHandle = nullptr;
391
392 // This function assumes that the QQuickMenuBarItem that corresponds to \a menu
393 // has already been added to the container at the correct index. So we search for
394 // it, to determine where to insert it in the native menubar. Since the QPA API
395 // expects a pointer to the QPlatformMenu that comes after it, we need to search
396 // for that one as well, since some MenuBarItems in the container can be hidden.
397 bool foundInContainer = false;
398 for (int i = 0; i < q->count(); ++i) {
399 if (q->menuAt(i) != menu)
400 continue;
401 foundInContainer = true;
402
403 for (int j = i + 1; j < q->count(); ++j) {
404 insertBeforeHandle = QQuickMenuPrivate::get(q->menuAt(j))->maybeNativeHandle();
405 if (insertBeforeHandle)
406 break;
407 }
408
409 break;
410 }
411
412 Q_ASSERT(foundInContainer);
413 QQuickMenuPrivate *menuPrivate = QQuickMenuPrivate::get(menu);
414 if (QPlatformMenu *menuHandle = menuPrivate->nativeHandle()) {
415 qCDebug(lcMenuBar) << "insert native menu:" << menu->title() << menuHandle << "before:" << insertBeforeHandle;
416 handle->insertMenu(menuPrivate->nativeHandle(), insertBeforeHandle);
417 } else {
418 qmlWarning(q) << "failed to create native menu for:" << menu->title();
419 }
420}
421
422void QQuickMenuBarPrivate::removeNativeMenu(QQuickMenu *menu)
423{
424 Q_ASSERT(handle);
425 Q_ASSERT(menu);
426
427 QQuickMenuPrivate *menuPrivate = QQuickMenuPrivate::get(menu);
428 if (!menuPrivate->maybeNativeHandle())
429 return;
430
431 qCDebug(lcMenuBar) << "remove native menu:" << menu << menu->title();
432 handle->removeMenu(menuPrivate->nativeHandle());
433 menuPrivate->removeNativeMenu();
434}
435
436void QQuickMenuBarPrivate::syncMenuBarItemVisibilty(QQuickMenuBarItem *menuBarItem)
437{
438 if (!handle) {
439 // We only need to update visibility on native menu bar items
440 return;
441 }
442
443 QQuickMenu *menu = menuBarItem->menu();
444 if (!menu)
445 return;
446 QQuickMenuPrivate *menuPrivate = QQuickMenuPrivate::get(menu);
447
448 if (menuBarItem->isVisible()) {
449 Q_ASSERT(!menuPrivate->maybeNativeHandle());
450 insertNativeMenu(menu);
451 } else {
452 if (menuPrivate->maybeNativeHandle())
453 removeNativeMenu(menu);
454 }
455}
456
457void QQuickMenuBarPrivate::insertMenu(int index, QQuickMenu *menu, QQuickMenuBarItem *menuBarItem)
458{
459 Q_Q(QQuickMenuBar);
460 if (!menu) {
461 qmlWarning(q) << "cannot insert menu: menu is null.";
462 return;
463 }
464
465 auto menuPrivate = QQuickMenuPrivate::get(menu);
466 menuPrivate->menuBar = q;
467
468 QObject::connect(menuBarItem, &QQuickMenuBarItem::visibleChanged, q, [this, menuBarItem]{
469 syncMenuBarItemVisibilty(menuBarItem);
470 });
471
472 // Always insert menu into the container, even when using a native
473 // menubar, so that container API such as 'count' and 'itemAt'
474 // continues to work as expected.
475 q->insertItem(index, menuBarItem);
476
477 // Create or remove a native (QPlatformMenu) menu. Note that we should only create
478 // a native menu if it's supposed to be visible in the menu bar.
479 if (menuBarItem->isVisible()) {
480 if (handle)
481 insertNativeMenu(menu);
482 } else {
483 if (menuPrivate->maybeNativeHandle()) {
484 // If the menu was added from an explicit call to addMenu(m), it will have been
485 // created before we enter here. And in that case, QQuickMenuBar::useNativeMenu(m)
486 // was never called, and a QPlatformMenu might have been created for it. In that
487 // case, we remove it again now, since the menu is not supposed to be visible in
488 // the menu bar.
489 menuPrivate->removeNativeMenu();
490 }
491 }
492}
493
494QQuickMenu *QQuickMenuBarPrivate::takeMenu(int index)
495{
496 Q_Q(QQuickMenuBar);
497 QQuickItem *item = q->itemAt(index);
498 Q_ASSERT(item);
499 QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(item);
500 if (!menuBarItem) {
501 qmlWarning(q) << "cannot take/remove menu: item at index " << index << " is not a MenuBarItem.";
502 return nullptr;
503 }
504 QQuickMenu *menu = menuBarItem->menu();
505 if (!menu) {
506 qmlWarning(q) << "cannot take/remove menu: MenuBarItem.menu at index " << index << " is null.";
507 return nullptr;
508 }
509
510 // Dismiss the menu if it's open. Otherwise, when we now remove it from
511 // the menubar, it will stay open without the user being able to dismiss
512 // it (at least if it's non-native).
513 menu->dismiss();
514
515 if (item == currentItem)
516 activateItem(nullptr);
517
518 if (QQuickMenuPrivate::get(menu)->maybeNativeHandle())
519 removeNativeMenu(menu);
520
521 removeItem(index, item);
522
523 // Delete the MenuBarItem. This will also cause the menu to be deleted by
524 // the garbage collector, unless other QML references are being held to it.
525 // Note: We might consider leaving it to the garbage collector to also
526 // delete the MenuBarItem in the future.
527 item->deleteLater();
528
529 QQuickMenuPrivate::get(menu)->menuBar = nullptr;
530 menuBarItem->disconnect(q);
531
532 return menu;
533}
534
535bool QQuickMenuBarPrivate::useNativeMenuBar() const
536{
537 // We current only use native menu bars on macOS. Especially, the
538 // QPA menu bar for Windows is old and unused, and looks broken and non-native.
539#ifdef Q_OS_MACOS
540 return !QCoreApplication::testAttribute(Qt::AA_DontUseNativeMenuBar);
541#else
542 return false;
543#endif
544}
545
546bool QQuickMenuBarPrivate::useNativeMenu(const QQuickMenu *menu) const
547{
548 Q_Q(const QQuickMenuBar);
549 if (!useNativeMenuBar())
550 return false;
551
552 // Since we cannot hide a QPlatformMenu, we have to avoid
553 // creating it if it shouldn't be visible in the menu bar.
554 for (int i = 0; i < q->count(); ++i) {
555 if (q->menuAt(i) == menu) {
556 QQuickItem *itemAtI = itemAt(i);
557 return itemAtI && itemAtI->isVisible();
558 }
559 }
560
561 return true;
562}
563
564void QQuickMenuBarPrivate::syncNativeMenuBarVisible()
565{
566 Q_Q(QQuickMenuBar);
567 if (!componentComplete)
568 return;
569
570 const bool shouldBeVisible = q->isVisible() && useNativeMenuBar();
571 qCDebug(lcMenuBar) << "syncNativeMenuBarVisible called - q->isVisible()" << q->isVisible()
572 << "useNativeMenuBar()" << useNativeMenuBar() << "handle" << handle.get();
573 if (shouldBeVisible && !handle)
574 createNativeMenuBar();
575 else if (!shouldBeVisible && handle)
576 removeNativeMenuBar();
577}
578
579void QQuickMenuBarPrivate::createNativeMenuBar()
580{
581 Q_Q(QQuickMenuBar);
582 Q_ASSERT(!handle);
583 qCDebug(lcMenuBar) << "creating native menubar";
584
585 handle.reset(QGuiApplicationPrivate::platformTheme()->createPlatformMenuBar());
586 if (!handle) {
587 qCDebug(lcMenuBar) << "QPlatformTheme failed to create a QPlatformMenuBar!";
588 return;
589 }
590
591 handle->handleReparent(window());
592 qCDebug(lcMenuBar) << "native menubar parented to window:" << handle->parentWindow();
593
594 // Add all the native menus. We need to do this right-to-left
595 // because of the QPA API (insertBefore).
596 for (int i = q->count() - 1; i >= 0; --i) {
597 if (QQuickMenu *menu = q->menuAt(i)) {
598 if (useNativeMenu(menu))
599 insertNativeMenu(menu);
600 }
601 }
602
603 // Hide the non-native menubar and set it's height to 0. The
604 // latter will cause a relayout to happen in ApplicationWindow
605 // which effectively removes the menubar from the contentItem.
606 setCulled(true);
607 q->setHeight(0);
608}
609
610void QQuickMenuBarPrivate::removeNativeMenuBar()
611{
612 Q_Q(QQuickMenuBar);
613 Q_ASSERT(handle);
614 qCDebug(lcMenuBar) << "removing native menubar";
615
616 // Remove all native menus.
617 for (int i = 0; i < q->count(); ++i) {
618 if (QQuickMenu *menu = q->menuAt(i))
619 removeNativeMenu(menu);
620 }
621
622 // Delete the menubar
623 handle.reset();
624
625 // Show the non-native menubar and reset it's height. The
626 // latter will cause a relayout to happen in ApplicationWindow
627 // which will effectively add the menubar to the contentItem.
628 setCulled(false);
629 q->resetHeight();
630}
631
632QQuickMenuBar::QQuickMenuBar(QQuickItem *parent)
633 : QQuickContainer(*(new QQuickMenuBarPrivate), parent)
634{
635 Q_D(QQuickMenuBar);
636 d->changeTypes |= QQuickItemPrivate::Geometry;
637 setFlag(ItemIsFocusScope);
638 setFocusPolicy(Qt::ClickFocus);
639}
640
641QQuickMenuBar::~QQuickMenuBar()
642{
643 Q_D(QQuickMenuBar);
644 if (d->handle)
645 d->removeNativeMenuBar();
646}
647
648/*!
649 \qmlproperty Component QtQuick.Controls::MenuBar::delegate
650
651 This property holds the component that is used to create menu bar
652 items to present menus in the menu bar.
653
654 \include delegate-ownership.qdocinc {no-ownership} {MenuBar}
655
656 \sa MenuBarItem
657*/
658QQmlComponent *QQuickMenuBar::delegate() const
659{
660 Q_D(const QQuickMenuBar);
661 return d->delegate;
662}
663
664void QQuickMenuBar::setDelegate(QQmlComponent *delegate)
665{
666 Q_D(QQuickMenuBar);
667 if (d->delegate == delegate)
668 return;
669
670 d->delegate = delegate;
671
672 for (int i = count() - 1; i >= 0; --i) {
673 auto item = itemAt(i);
674 if (!item || !item->property(kCreatedFromDelegate).toBool())
675 continue;
676
677 QQuickMenuBarItem *menuBarItem = static_cast<QQuickMenuBarItem *>(item);
678 if (QQuickMenu *menu = menuBarItem->menu()) {
679 removeMenu(menu);
680 d->insertMenu(i, menu, d->createMenuBarItem(menu));
681 } else {
682 removeItem(menuBarItem);
683 }
684 }
685
686 emit delegateChanged();
687}
688
689/*!
690 \qmlmethod Menu QtQuick.Controls::MenuBar::menuAt(int index)
691
692 Returns the menu at \a index, or \c null if it does not exist.
693*/
694QQuickMenu *QQuickMenuBar::menuAt(int index) const
695{
696 Q_D(const QQuickMenuBar);
697 QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(d->itemAt(index));
698 if (!item)
699 return nullptr;
700 return item->menu();
701}
702
703/*!
704 \qmlmethod void QtQuick.Controls::MenuBar::addMenu(Menu menu)
705
706 Adds \a menu to the end of the list of menus.
707*/
708void QQuickMenuBar::addMenu(QQuickMenu *menu)
709{
710 Q_D(QQuickMenuBar);
711 if (d->menuIndex(menu) >= 0) {
712 qmlWarning(this) << "cannot add menu: '" << menu->title() << "' is already in the MenuBar.";
713 return;
714 }
715
716 d->insertMenu(count(), menu, d->createMenuBarItem(menu));
717}
718
719/*!
720 \qmlmethod void QtQuick.Controls::MenuBar::insertMenu(int index, Menu menu)
721
722 Inserts \a menu at \a index.
723*/
724void QQuickMenuBar::insertMenu(int index, QQuickMenu *menu)
725{
726 Q_D(QQuickMenuBar);
727 if (d->menuIndex(menu) >= 0) {
728 qmlWarning(this) << "cannot insert menu: '" << menu->title() << "' is already in the MenuBar.";
729 return;
730 }
731
732 d->insertMenu(index, menu, d->createMenuBarItem(menu));
733}
734
735/*!
736 \qmlmethod void QtQuick.Controls::MenuBar::removeMenu(Menu menu)
737
738 Removes specified \a menu. If the menu is \l {Menu::popup()}{open},
739 it will first be \l {Menu::dismiss()}{dismissed}.
740 The \a menu will eventually be deleted by the garbage collector when the
741 application no longer holds any QML references to it.
742*/
743void QQuickMenuBar::removeMenu(QQuickMenu *menu)
744{
745 Q_D(QQuickMenuBar);
746 const int index = d->menuIndex(menu);
747 if (index < 0) {
748 qmlWarning(this) << "cannot remove menu: '" << menu->title() << "' is not in the MenuBar.";
749 return;
750 }
751
752 d->takeMenu(index);
753}
754
755/*!
756 \qmlmethod Menu QtQuick.Controls::MenuBar::takeMenu(int index)
757
758 Removes and returns the menu at \a index. If the menu is
759 \l {Menu::popup()}{open}, it will first be
760 \l {Menu::dismiss()}{dismissed}.
761 The menu will eventually be deleted by the garbage collector when the
762 application no longer holds any QML references to it.
763*/
764QQuickMenu *QQuickMenuBar::takeMenu(int index)
765{
766 Q_D(QQuickMenuBar);
767 if (index < 0 || index > count() - 1) {
768 qmlWarning(this) << "index out of range: " << index;
769 return nullptr;
770 }
771
772 return d->takeMenu(index);
773}
774
775/*!
776 \qmlproperty list<Menu> QtQuick.Controls::MenuBar::menus
777
778 This property holds the list of menus.
779
780 The list contains all menus that have been declared in QML as children
781 of the menu bar, and also menus that have been dynamically added or
782 inserted using the \l addMenu() and \l insertMenu() methods, respectively.
783*/
784QQmlListProperty<QQuickMenu> QQuickMenuBarPrivate::menus()
785{
786 Q_Q(QQuickMenuBar);
787 return QQmlListProperty<QQuickMenu>(q, nullptr,
788 QQuickMenuBarPrivate::menus_append,
789 QQuickMenuBarPrivate::menus_count,
790 QQuickMenuBarPrivate::menus_at,
791 QQuickMenuBarPrivate::menus_clear);
792}
793
794QQmlListProperty<QObject> QQuickMenuBarPrivate::contentData()
795{
796 Q_Q(QQuickMenuBar);
797 return QQmlListProperty<QObject>(q, nullptr,
798 QQuickMenuBarPrivate::contentData_append,
799 QQuickContainerPrivate::contentData_count,
800 QQuickContainerPrivate::contentData_at,
801 QQuickContainerPrivate::contentData_clear);
802}
803
804bool QQuickMenuBar::eventFilter(QObject *object, QEvent *event)
805{
806 Q_D(QQuickMenuBar);
807
808 if (d->altPressed) {
809 switch (event->type()) {
810 case QEvent::KeyRelease: {
811 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
812 if ((keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta)
813 && keyEvent->modifiers() == Qt::NoModifier) {
814 for (int i = 0; i < count(); ++i) {
815 if (auto *item = qobject_cast<QQuickMenuBarItem *>(d->itemAt(i))) {
816 d->activateItem(item);
817 setFocusReason(Qt::MenuBarFocusReason);
818 setFocus(true);
819 break;
820 }
821 }
822 }
823 Q_FALLTHROUGH();
824 }
825 case QEvent::MouseButtonPress:
826 case QEvent::MouseButtonRelease:
827 case QEvent::MouseMove:
828 case QEvent::TabletPress:
829 case QEvent::TabletMove:
830 case QEvent::TabletRelease:
831 case QEvent::TouchBegin:
832 case QEvent::TouchUpdate:
833 case QEvent::TouchEnd:
834 case QEvent::FocusIn:
835 case QEvent::FocusOut:
836 case QEvent::ActivationChange:
837 case QEvent::Shortcut:
838 d->altPressed = false;
839 qApp->removeEventFilter(this);
840 break;
841 default:
842 break;
843 }
844 } else if (isVisible() && event->type() == QEvent::ShortcutOverride) {
845 const bool altKeyNavigation = QGuiApplicationPrivate::platformTheme()
846 ->themeHint(QPlatformTheme::MenuBarFocusOnAltPressRelease).toBool();
847 if (altKeyNavigation) {
848 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
849 if ((keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta)
850 && keyEvent->modifiers() == Qt::AltModifier) {
851 d->altPressed = true;
852 qApp->installEventFilter(this);
853 }
854 }
855 }
856 return QObject::eventFilter(object, event);
857}
858
859void QQuickMenuBar::keyPressEvent(QKeyEvent *event)
860{
861 Q_D(QQuickMenuBar);
862 QQuickContainer::keyReleaseEvent(event);
863
864 switch (event->key()) {
865 case Qt::Key_Up:
866 d->closeCurrentMenu();
867 break;
868
869 case Qt::Key_Down:
870 d->openCurrentMenu();
871 d->activateMenuItem(0);
872 break;
873
874 case Qt::Key_Left:
875 case Qt::Key_Right:
876 if (isMirrored() == (event->key() == Qt::Key_Left))
877 d->activateNextItem();
878 else
879 d->activatePreviousItem();
880 break;
881 // This is triggered when no popup is open but a menu bar item is highlighted and has focus.
882 case Qt::Key_Escape:
883 if (d->currentItem) {
884 d->activateItem(nullptr);
885 setFocus(false);
886 }
887 break;
888 default:
889#if QT_CONFIG(shortcut)
890 if (!event->text().isEmpty() && event->modifiers() == Qt::NoModifier) {
891 const QKeyCombination mnemonic(Qt::AltModifier, Qt::Key(event->key()));
892 for (int i = 0; i < count(); ++i) {
893 if (auto *item = qobject_cast<QQuickMenuBarItem *>(d->itemAt(i))) {
894 if (item->shortcut() == mnemonic) {
895 d->activateItem(item);
896 d->openCurrentMenu();
897 d->activateMenuItem(0);
898 }
899 }
900 }
901 }
902#endif
903 break;
904 }
905}
906
907void QQuickMenuBar::keyReleaseEvent(QKeyEvent *event)
908{
909 QQuickContainer::keyReleaseEvent(event);
910
911 switch (event->key()) {
912 case Qt::Key_Up:
913 case Qt::Key_Down:
914 case Qt::Key_Left:
915 case Qt::Key_Right:
916 case Qt::Key_Escape:
917 event->accept();
918 break;
919
920 default:
921 event->ignore();
922 break;
923 }
924}
925
926void QQuickMenuBar::hoverLeaveEvent(QHoverEvent *event)
927{
928 Q_D(QQuickMenuBar);
929 QQuickContainer::hoverLeaveEvent(event);
930 if (!d->currentMenuOpen && d->currentItem)
931 d->activateItem(nullptr);
932}
933
934bool QQuickMenuBar::isContent(QQuickItem *item) const
935{
936 return qobject_cast<QQuickMenuBarItem *>(item);
937}
938
939void QQuickMenuBar::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
940{
941 Q_D(QQuickMenuBar);
942 QQuickContainer::itemChange(change, value);
943 switch (change) {
944 case ItemSceneChange:
945 if (d->windowContentItem)
946 d->windowContentItem->removeEventFilter(this);
947 if (value.window) {
948 d->windowContentItem = value.window->contentItem();
949 if (d->windowContentItem)
950 d->windowContentItem->installEventFilter(this);
951 }
952 break;
953 case ItemVisibleHasChanged:
954 qCDebug(lcMenuBar) << "visibility of" << this << "changed to" << isVisible();
955 d->syncNativeMenuBarVisible();
956 break;
957 default:
958 break;
959 }
960}
961
962void QQuickMenuBar::itemAdded(int index, QQuickItem *item)
963{
964 Q_D(QQuickMenuBar);
965 QQuickContainer::itemAdded(index, item);
966 if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(item)) {
967 QQuickMenuBarItemPrivate::get(menuBarItem)->setMenuBar(this);
968 QObjectPrivate::connect(menuBarItem, &QQuickControl::hoveredChanged, d, &QQuickMenuBarPrivate::onItemHovered);
969 QObjectPrivate::connect(menuBarItem, &QQuickMenuBarItem::triggered, d, &QQuickMenuBarPrivate::onItemTriggered);
970 if (QQuickMenu *menu = menuBarItem->menu())
971 connect(menu, &QQuickPopup::aboutToHide, this,
972 [this, menu] { d_func()->onMenuAboutToHide(menu); });
973 }
974 d->updateImplicitContentSize();
975 emit menusChanged();
976}
977
978void QQuickMenuBar::itemMoved(int index, QQuickItem *item)
979{
980 QQuickContainer::itemMoved(index, item);
981 emit menusChanged();
982}
983
984void QQuickMenuBar::itemRemoved(int index, QQuickItem *item)
985{
986 Q_D(QQuickMenuBar);
987 QQuickContainer::itemRemoved(index, item);
988 if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(item)) {
989 QQuickMenuBarItemPrivate::get(menuBarItem)->setMenuBar(nullptr);
990 QObjectPrivate::disconnect(menuBarItem, &QQuickControl::hoveredChanged, d, &QQuickMenuBarPrivate::onItemHovered);
991 QObjectPrivate::disconnect(menuBarItem, &QQuickMenuBarItem::triggered, d, &QQuickMenuBarPrivate::onItemTriggered);
992 if (QQuickMenu *menu = menuBarItem->menu())
993 menu->disconnect(this);
994 }
995 d->updateImplicitContentSize();
996 emit menusChanged();
997}
998
999void QQuickMenuBar::componentComplete()
1000{
1001 Q_D(QQuickMenuBar);
1002 QQuickContainer::componentComplete();
1003 d->syncNativeMenuBarVisible();
1004}
1005
1006QFont QQuickMenuBar::defaultFont() const
1007{
1008 return QQuickTheme::font(QQuickTheme::MenuBar);
1009}
1010
1011#if QT_CONFIG(accessibility)
1012QAccessible::Role QQuickMenuBar::accessibleRole() const
1013{
1014 return QAccessible::MenuBar;
1015}
1016#endif
1017
1018QT_END_NAMESPACE
1019
1020#include "moc_qquickmenubar_p.cpp"
Combined button and popup list for selecting options.