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