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

source code of qtdeclarative/src/quicktemplates/qquickmenubar.cpp