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_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(pos: 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 QQuickMenuBarPrivate::get(menuBar)->contentModel->clear();
343}
344
345QPalette QQuickMenuBarPrivate::defaultPalette() const
346{
347 return QQuickTheme::palette(scope: QQuickTheme::MenuBar);
348}
349
350QWindow* QQuickMenuBarPrivate::window() const
351{
352 Q_Q(const QQuickMenuBar);
353 QObject *obj = q->parent();
354 while (obj) {
355 if (QWindow *window = qobject_cast<QWindow *>(o: obj))
356 return window;
357 QQuickItem *item = qobject_cast<QQuickItem *>(o: obj);
358 if (item && item->window())
359 return item->window();
360 obj = obj->parent();
361 }
362 return nullptr;
363}
364
365int QQuickMenuBarPrivate::menuIndex(QQuickMenu *menu) const
366{
367 Q_Q(const QQuickMenuBar);
368 for (int i = 0; i < q->count(); ++i) {
369 if (q->menuAt(index: i) == menu)
370 return i;
371 }
372
373 return -1;
374}
375
376QPlatformMenuBar* QQuickMenuBarPrivate::nativeHandle() const
377{
378 return handle.get();
379}
380
381void QQuickMenuBarPrivate::insertNativeMenu(QQuickMenu *menu)
382{
383 Q_Q(QQuickMenuBar);
384 Q_ASSERT(handle);
385 Q_ASSERT(menu);
386
387 QPlatformMenu *insertBeforeHandle = nullptr;
388
389 // This function assumes that the QQuickMenuBarItem that corresponds to \a menu
390 // has already been added to the container at the correct index. So we search for
391 // it, to determine where to insert it in the native menubar. Since the QPA API
392 // expects a pointer to the QPlatformMenu that comes after it, we need to search
393 // for that one as well, since some MenuBarItems in the container can be hidden.
394 bool foundInContainer = false;
395 for (int i = 0; i < q->count(); ++i) {
396 if (q->menuAt(index: i) != menu)
397 continue;
398 foundInContainer = true;
399
400 for (int j = i + 1; j < q->count(); ++j) {
401 insertBeforeHandle = QQuickMenuPrivate::get(menu: q->menuAt(index: j))->maybeNativeHandle();
402 if (insertBeforeHandle)
403 break;
404 }
405
406 break;
407 }
408
409 Q_ASSERT(foundInContainer);
410 QQuickMenuPrivate *menuPrivate = QQuickMenuPrivate::get(menu);
411 if (QPlatformMenu *menuHandle = menuPrivate->nativeHandle()) {
412 qCDebug(lcMenuBar) << "insert native menu:" << menu->title() << menuHandle << "before:" << insertBeforeHandle;
413 handle->insertMenu(menu: menuPrivate->nativeHandle(), before: insertBeforeHandle);
414 } else {
415 qmlWarning(me: q) << "failed to create native menu for:" << menu->title();
416 }
417}
418
419void QQuickMenuBarPrivate::removeNativeMenu(QQuickMenu *menu)
420{
421 Q_ASSERT(handle);
422 Q_ASSERT(menu);
423
424 QQuickMenuPrivate *menuPrivate = QQuickMenuPrivate::get(menu);
425 if (!menuPrivate->maybeNativeHandle())
426 return;
427
428 qCDebug(lcMenuBar) << "remove native menu:" << menu << menu->title();
429 handle->removeMenu(menu: menuPrivate->nativeHandle());
430 menuPrivate->removeNativeMenu();
431}
432
433void QQuickMenuBarPrivate::syncMenuBarItemVisibilty(QQuickMenuBarItem *menuBarItem)
434{
435 if (!handle) {
436 // We only need to update visibility on native menu bar items
437 return;
438 }
439
440 QQuickMenu *menu = menuBarItem->menu();
441 if (!menu)
442 return;
443 QQuickMenuPrivate *menuPrivate = QQuickMenuPrivate::get(menu);
444
445 if (menuBarItem->isVisible()) {
446 Q_ASSERT(!menuPrivate->maybeNativeHandle());
447 insertNativeMenu(menu);
448 } else {
449 if (menuPrivate->maybeNativeHandle())
450 removeNativeMenu(menu);
451 }
452}
453
454void QQuickMenuBarPrivate::insertMenu(int index, QQuickMenu *menu, QQuickMenuBarItem *menuBarItem)
455{
456 Q_Q(QQuickMenuBar);
457 if (!menu) {
458 qmlWarning(me: q) << "cannot insert menu: menu is null.";
459 return;
460 }
461
462 auto menuPrivate = QQuickMenuPrivate::get(menu);
463 menuPrivate->menuBar = q;
464
465 QObject::connect(sender: menuBarItem, signal: &QQuickMenuBarItem::visibleChanged, slot: [this, menuBarItem]{
466 syncMenuBarItemVisibilty(menuBarItem);
467 });
468
469 // Always insert menu into the container, even when using a native
470 // menubar, so that container API such as 'count' and 'itemAt'
471 // continues to work as expected.
472 q->insertItem(index, item: menuBarItem);
473
474 // Create or remove a native (QPlatformMenu) menu. Note that we should only create
475 // a native menu if it's supposed to be visible in the menu bar.
476 if (menuBarItem->isVisible()) {
477 if (handle)
478 insertNativeMenu(menu);
479 } else {
480 if (menuPrivate->maybeNativeHandle()) {
481 // If the menu was added from an explicit call to addMenu(m), it will have been
482 // created before we enter here. And in that case, QQuickMenuBar::useNativeMenu(m)
483 // was never called, and a QPlatformMenu might have been created for it. In that
484 // case, we remove it again now, since the menu is not supposed to be visible in
485 // the menu bar.
486 menuPrivate->removeNativeMenu();
487 }
488 }
489}
490
491QQuickMenu *QQuickMenuBarPrivate::takeMenu(int index)
492{
493 Q_Q(QQuickMenuBar);
494 QQuickItem *item = q->itemAt(index);
495 Q_ASSERT(item);
496 QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(object: item);
497 if (!menuBarItem) {
498 qmlWarning(me: q) << "cannot take/remove menu: item at index " << index << " is not a MenuBarItem.";
499 return nullptr;
500 }
501 QQuickMenu *menu = menuBarItem->menu();
502 if (!menu) {
503 qmlWarning(me: q) << "cannot take/remove menu: MenuBarItem.menu at index " << index << " is null.";
504 return nullptr;
505 }
506
507 // Dismiss the menu if it's open. Otherwise, when we now remove it from
508 // the menubar, it will stay open without the user being able to dismiss
509 // it (at least if it's non-native).
510 menu->dismiss();
511
512 if (item == currentItem)
513 activateItem(item: nullptr);
514
515 if (QQuickMenuPrivate::get(menu)->maybeNativeHandle())
516 removeNativeMenu(menu);
517
518 removeItem(index, item);
519
520 // Delete the MenuBarItem. This will also cause the menu to be deleted by
521 // the garbage collector, unless other QML references are being held to it.
522 // Note: We might consider leaving it to the garbage collector to also
523 // delete the MenuBarItem in the future.
524 item->deleteLater();
525
526 QQuickMenuPrivate::get(menu)->menuBar = nullptr;
527 menuBarItem->disconnect(receiver: q);
528
529 return menu;
530}
531
532bool QQuickMenuBarPrivate::useNativeMenuBar() const
533{
534 // We current only use native menu bars on macOS. Especially, the
535 // QPA menu bar for Windows is old and unused, and looks broken and non-native.
536#ifdef Q_OS_MACOS
537 return !QCoreApplication::testAttribute(Qt::AA_DontUseNativeMenuBar);
538#else
539 return false;
540#endif
541}
542
543bool QQuickMenuBarPrivate::useNativeMenu(const QQuickMenu *menu) const
544{
545 Q_Q(const QQuickMenuBar);
546 if (!useNativeMenuBar())
547 return false;
548
549 // Since we cannot hide a QPlatformMenu, we have to avoid
550 // creating it if it shouldn't be visible in the menu bar.
551 for (int i = 0; i < q->count(); ++i) {
552 if (q->menuAt(index: i) == menu) {
553 QQuickItem *itemAtI = itemAt(index: i);
554 return itemAtI && itemAtI->isVisible();
555 }
556 }
557
558 return true;
559}
560
561void QQuickMenuBarPrivate::syncNativeMenuBarVisible()
562{
563 Q_Q(QQuickMenuBar);
564 if (!componentComplete)
565 return;
566
567 const bool shouldBeVisible = q->isVisible() && useNativeMenuBar();
568 qCDebug(lcMenuBar) << "syncNativeMenuBarVisible called - q->isVisible()" << q->isVisible()
569 << "useNativeMenuBar()" << useNativeMenuBar() << "handle" << handle.get();
570 if (shouldBeVisible && !handle)
571 createNativeMenuBar();
572 else if (!shouldBeVisible && handle)
573 removeNativeMenuBar();
574}
575
576void QQuickMenuBarPrivate::createNativeMenuBar()
577{
578 Q_Q(QQuickMenuBar);
579 Q_ASSERT(!handle);
580 qCDebug(lcMenuBar) << "creating native menubar";
581
582 handle.reset(p: QGuiApplicationPrivate::platformTheme()->createPlatformMenuBar());
583 if (!handle) {
584 qCDebug(lcMenuBar) << "QPlatformTheme failed to create a QPlatformMenuBar!";
585 return;
586 }
587
588 handle->handleReparent(newParentWindow: window());
589 qCDebug(lcMenuBar) << "native menubar parented to window:" << handle->parentWindow();
590
591 // Add all the native menus. We need to do this right-to-left
592 // because of the QPA API (insertBefore).
593 for (int i = q->count() - 1; i >= 0; --i) {
594 if (QQuickMenu *menu = q->menuAt(index: i)) {
595 if (useNativeMenu(menu))
596 insertNativeMenu(menu);
597 }
598 }
599
600 // Hide the non-native menubar and set it's height to 0. The
601 // latter will cause a relayout to happen in ApplicationWindow
602 // which effectively removes the menubar from the contentItem.
603 setCulled(true);
604 q->setHeight(0);
605}
606
607void QQuickMenuBarPrivate::removeNativeMenuBar()
608{
609 Q_Q(QQuickMenuBar);
610 Q_ASSERT(handle);
611 qCDebug(lcMenuBar) << "removing native menubar";
612
613 // Remove all native menus.
614 for (int i = 0; i < q->count(); ++i) {
615 if (QQuickMenu *menu = q->menuAt(index: i))
616 removeNativeMenu(menu);
617 }
618
619 // Delete the menubar
620 handle.reset();
621
622 // Show the non-native menubar and reset it's height. The
623 // latter will cause a relayout to happen in ApplicationWindow
624 // which will effectively add the menubar to the contentItem.
625 setCulled(false);
626 q->resetHeight();
627}
628
629QQuickMenuBar::QQuickMenuBar(QQuickItem *parent)
630 : QQuickContainer(*(new QQuickMenuBarPrivate), parent)
631{
632 Q_D(QQuickMenuBar);
633 d->changeTypes |= QQuickItemPrivate::Geometry;
634 setFlag(flag: ItemIsFocusScope);
635 setFocusPolicy(Qt::ClickFocus);
636}
637
638QQuickMenuBar::~QQuickMenuBar()
639{
640 Q_D(QQuickMenuBar);
641 if (d->handle)
642 d->removeNativeMenuBar();
643}
644
645/*!
646 \qmlproperty Component QtQuick.Controls::MenuBar::delegate
647
648 This property holds the component that is used to create menu bar
649 items to present menus in the menu bar.
650
651 \sa MenuBarItem
652*/
653QQmlComponent *QQuickMenuBar::delegate() const
654{
655 Q_D(const QQuickMenuBar);
656 return d->delegate;
657}
658
659void QQuickMenuBar::setDelegate(QQmlComponent *delegate)
660{
661 Q_D(QQuickMenuBar);
662 if (d->delegate == delegate)
663 return;
664
665 d->delegate = delegate;
666
667 for (int i = count() - 1; i >= 0; --i) {
668 auto item = itemAt(index: i);
669 if (!item || !item->property(name: kCreatedFromDelegate).toBool())
670 continue;
671
672 QQuickMenuBarItem *menuBarItem = static_cast<QQuickMenuBarItem *>(item);
673 if (QQuickMenu *menu = menuBarItem->menu()) {
674 removeMenu(menu);
675 d->insertMenu(index: i, menu, menuBarItem: d->createMenuBarItem(menu));
676 } else {
677 removeItem(item: menuBarItem);
678 }
679 }
680
681 emit delegateChanged();
682}
683
684/*!
685 \qmlmethod Menu QtQuick.Controls::MenuBar::menuAt(int index)
686
687 Returns the menu at \a index, or \c null if it does not exist.
688*/
689QQuickMenu *QQuickMenuBar::menuAt(int index) const
690{
691 Q_D(const QQuickMenuBar);
692 QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(object: d->itemAt(index));
693 if (!item)
694 return nullptr;
695 return item->menu();
696}
697
698/*!
699 \qmlmethod void QtQuick.Controls::MenuBar::addMenu(Menu menu)
700
701 Adds \a menu to the end of the list of menus.
702*/
703void QQuickMenuBar::addMenu(QQuickMenu *menu)
704{
705 Q_D(QQuickMenuBar);
706 if (d->menuIndex(menu) >= 0) {
707 qmlWarning(me: this) << "cannot add menu: '" << menu->title() << "' is already in the MenuBar.";
708 return;
709 }
710
711 d->insertMenu(index: count(), menu, menuBarItem: d->createMenuBarItem(menu));
712}
713
714/*!
715 \qmlmethod void QtQuick.Controls::MenuBar::insertMenu(int index, Menu menu)
716
717 Inserts \a menu at \a index.
718*/
719void QQuickMenuBar::insertMenu(int index, QQuickMenu *menu)
720{
721 Q_D(QQuickMenuBar);
722 if (d->menuIndex(menu) >= 0) {
723 qmlWarning(me: this) << "cannot insert menu: '" << menu->title() << "' is already in the MenuBar.";
724 return;
725 }
726
727 d->insertMenu(index, menu, menuBarItem: d->createMenuBarItem(menu));
728}
729
730/*!
731 \qmlmethod void QtQuick.Controls::MenuBar::removeMenu(Menu menu)
732
733 Removes specified \a menu. If the menu is \l {Menu::popup()}{open},
734 it will first be \l {Menu::dismiss()}{dismissed}.
735 The \a menu will eventually be deleted by the garbage collector when the
736 application no longer holds any QML references to it.
737*/
738void QQuickMenuBar::removeMenu(QQuickMenu *menu)
739{
740 Q_D(QQuickMenuBar);
741 const int index = d->menuIndex(menu);
742 if (index < 0) {
743 qmlWarning(me: this) << "cannot remove menu: '" << menu->title() << "' is not in the MenuBar.";
744 return;
745 }
746
747 d->takeMenu(index);
748}
749
750/*!
751 \qmlmethod Menu QtQuick.Controls::MenuBar::takeMenu(int index)
752
753 Removes and returns the menu at \a index. If the menu is
754 \l {Menu::popup()}{open}, it will first be
755 \l {Menu::dismiss()}{dismissed}.
756 The menu will eventually be deleted by the garbage collector when the
757 application no longer holds any QML references to it.
758*/
759QQuickMenu *QQuickMenuBar::takeMenu(int index)
760{
761 Q_D(QQuickMenuBar);
762 if (index < 0 || index > count() - 1) {
763 qmlWarning(me: this) << "index out of range: " << index;
764 return nullptr;
765 }
766
767 return d->takeMenu(index);
768}
769
770/*!
771 \since QtQuick.Controls 2.3 (Qt 5.10)
772 \qmlproperty real QtQuick.Controls::MenuBar::contentWidth
773
774 This property holds the content width. It is used for calculating the total
775 implicit width of the menu bar.
776
777 \note This property is available in MenuBar since QtQuick.Controls 2.3 (Qt 5.10),
778 but it was promoted to the Container base type in QtQuick.Controls 2.5 (Qt 5.12).
779
780 \sa Container::contentWidth
781*/
782
783/*!
784 \since QtQuick.Controls 2.3 (Qt 5.10)
785 \qmlproperty real QtQuick.Controls::MenuBar::contentHeight
786
787 This property holds the content height. It is used for calculating the total
788 implicit height of the menu bar.
789
790 \note This property is available in MenuBar since QtQuick.Controls 2.3 (Qt 5.10),
791 but it was promoted to the Container base type in QtQuick.Controls 2.5 (Qt 5.12).
792
793 \sa Container::contentHeight
794*/
795
796/*!
797 \qmlproperty list<Menu> QtQuick.Controls::MenuBar::menus
798
799 This property holds the list of menus.
800
801 The list contains all menus that have been declared in QML as children
802 of the menu bar, and also menus that have been dynamically added or
803 inserted using the \l addMenu() and \l insertMenu() methods, respectively.
804*/
805QQmlListProperty<QQuickMenu> QQuickMenuBarPrivate::menus()
806{
807 Q_Q(QQuickMenuBar);
808 return QQmlListProperty<QQuickMenu>(q, nullptr,
809 QQuickMenuBarPrivate::menus_append,
810 QQuickMenuBarPrivate::menus_count,
811 QQuickMenuBarPrivate::menus_at,
812 QQuickMenuBarPrivate::menus_clear);
813}
814
815QQmlListProperty<QObject> QQuickMenuBarPrivate::contentData()
816{
817 Q_Q(QQuickMenuBar);
818 return QQmlListProperty<QObject>(q, nullptr,
819 QQuickMenuBarPrivate::contentData_append,
820 QQuickContainerPrivate::contentData_count,
821 QQuickContainerPrivate::contentData_at,
822 QQuickContainerPrivate::contentData_clear);
823}
824
825bool QQuickMenuBar::eventFilter(QObject *object, QEvent *event)
826{
827 Q_D(QQuickMenuBar);
828
829 if (d->altPressed) {
830 switch (event->type()) {
831 case QEvent::KeyRelease: {
832 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
833 if ((keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta)
834 && keyEvent->modifiers() == Qt::NoModifier) {
835 for (int i = 0; i < count(); ++i) {
836 if (auto *item = qobject_cast<QQuickMenuBarItem *>(object: d->itemAt(index: i))) {
837 d->activateItem(item);
838 setFocusReason(Qt::MenuBarFocusReason);
839 setFocus(true);
840 break;
841 }
842 }
843 }
844 Q_FALLTHROUGH();
845 }
846 case QEvent::MouseButtonPress:
847 case QEvent::MouseButtonRelease:
848 case QEvent::MouseMove:
849 case QEvent::TabletPress:
850 case QEvent::TabletMove:
851 case QEvent::TabletRelease:
852 case QEvent::TouchBegin:
853 case QEvent::TouchUpdate:
854 case QEvent::TouchEnd:
855 case QEvent::FocusIn:
856 case QEvent::FocusOut:
857 case QEvent::ActivationChange:
858 case QEvent::Shortcut:
859 d->altPressed = false;
860 qApp->removeEventFilter(obj: this);
861 break;
862 default:
863 break;
864 }
865 } else if (isVisible() && event->type() == QEvent::ShortcutOverride) {
866 const bool altKeyNavigation = QGuiApplicationPrivate::platformTheme()
867 ->themeHint(hint: QPlatformTheme::MenuBarFocusOnAltPressRelease).toBool();
868 if (altKeyNavigation) {
869 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
870 if ((keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta)
871 && keyEvent->modifiers() == Qt::AltModifier) {
872 d->altPressed = true;
873 qApp->installEventFilter(filterObj: this);
874 }
875 }
876 }
877 return QObject::eventFilter(watched: object, event);
878}
879
880void QQuickMenuBar::keyPressEvent(QKeyEvent *event)
881{
882 Q_D(QQuickMenuBar);
883 QQuickContainer::keyReleaseEvent(event);
884
885 switch (event->key()) {
886 case Qt::Key_Up:
887 d->closeCurrentMenu();
888 break;
889
890 case Qt::Key_Down:
891 d->openCurrentMenu();
892 d->activateMenuItem(index: 0);
893 break;
894
895 case Qt::Key_Left:
896 case Qt::Key_Right:
897 if (isMirrored() == (event->key() == Qt::Key_Left))
898 d->activateNextItem();
899 else
900 d->activatePreviousItem();
901 break;
902 // This is triggered when no popup is open but a menu bar item is highlighted and has focus.
903 case Qt::Key_Escape:
904 if (d->currentItem) {
905 d->activateItem(item: nullptr);
906 setFocus(false);
907 }
908 break;
909 default:
910#if QT_CONFIG(shortcut)
911 if (!event->text().isEmpty() && event->modifiers() == Qt::NoModifier) {
912 const QKeyCombination mnemonic(Qt::AltModifier, Qt::Key(event->key()));
913 for (int i = 0; i < count(); ++i) {
914 if (auto *item = qobject_cast<QQuickMenuBarItem *>(object: d->itemAt(index: i))) {
915 if (item->shortcut() == mnemonic) {
916 d->activateItem(item);
917 d->openCurrentMenu();
918 d->activateMenuItem(index: 0);
919 }
920 }
921 }
922 }
923#endif
924 break;
925 }
926}
927
928void QQuickMenuBar::keyReleaseEvent(QKeyEvent *event)
929{
930 QQuickContainer::keyReleaseEvent(event);
931
932 switch (event->key()) {
933 case Qt::Key_Up:
934 case Qt::Key_Down:
935 case Qt::Key_Left:
936 case Qt::Key_Right:
937 case Qt::Key_Escape:
938 event->accept();
939 break;
940
941 default:
942 event->ignore();
943 break;
944 }
945}
946
947void QQuickMenuBar::hoverLeaveEvent(QHoverEvent *event)
948{
949 Q_D(QQuickMenuBar);
950 QQuickContainer::hoverLeaveEvent(event);
951 if (!d->currentMenuOpen && d->currentItem)
952 d->activateItem(item: nullptr);
953}
954
955bool QQuickMenuBar::isContent(QQuickItem *item) const
956{
957 return qobject_cast<QQuickMenuBarItem *>(object: item);
958}
959
960void QQuickMenuBar::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
961{
962 Q_D(QQuickMenuBar);
963 QQuickContainer::itemChange(change, data: value);
964 switch (change) {
965 case ItemSceneChange:
966 if (d->windowContentItem)
967 d->windowContentItem->removeEventFilter(obj: this);
968 if (value.window) {
969 d->windowContentItem = value.window->contentItem();
970 if (d->windowContentItem)
971 d->windowContentItem->installEventFilter(filterObj: this);
972 }
973 break;
974 case ItemVisibleHasChanged:
975 qCDebug(lcMenuBar) << "visibility of" << this << "changed to" << isVisible();
976 d->syncNativeMenuBarVisible();
977 break;
978 default:
979 break;
980 }
981}
982
983void QQuickMenuBar::itemAdded(int index, QQuickItem *item)
984{
985 Q_D(QQuickMenuBar);
986 QQuickContainer::itemAdded(index, item);
987 if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(object: item)) {
988 QQuickMenuBarItemPrivate::get(item: menuBarItem)->setMenuBar(this);
989 QObjectPrivate::connect(sender: menuBarItem, signal: &QQuickControl::hoveredChanged, receiverPrivate: d, slot: &QQuickMenuBarPrivate::onItemHovered);
990 QObjectPrivate::connect(sender: menuBarItem, signal: &QQuickMenuBarItem::triggered, receiverPrivate: d, slot: &QQuickMenuBarPrivate::onItemTriggered);
991 if (QQuickMenu *menu = menuBarItem->menu())
992 connect(sender: menu, signal: &QQuickPopup::aboutToHide, slot: [this, menu]{ d_func()->onMenuAboutToHide(menu); });
993 }
994 d->updateImplicitContentSize();
995 emit menusChanged();
996}
997
998void QQuickMenuBar::itemMoved(int index, QQuickItem *item)
999{
1000 QQuickContainer::itemMoved(index, item);
1001 emit menusChanged();
1002}
1003
1004void QQuickMenuBar::itemRemoved(int index, QQuickItem *item)
1005{
1006 Q_D(QQuickMenuBar);
1007 QQuickContainer::itemRemoved(index, item);
1008 if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(object: item)) {
1009 QQuickMenuBarItemPrivate::get(item: menuBarItem)->setMenuBar(nullptr);
1010 QObjectPrivate::disconnect(sender: menuBarItem, signal: &QQuickControl::hoveredChanged, receiverPrivate: d, slot: &QQuickMenuBarPrivate::onItemHovered);
1011 QObjectPrivate::disconnect(sender: menuBarItem, signal: &QQuickMenuBarItem::triggered, receiverPrivate: d, slot: &QQuickMenuBarPrivate::onItemTriggered);
1012 if (QQuickMenu *menu = menuBarItem->menu())
1013 menu->disconnect(receiver: this);
1014 }
1015 d->updateImplicitContentSize();
1016 emit menusChanged();
1017}
1018
1019void QQuickMenuBar::componentComplete()
1020{
1021 Q_D(QQuickMenuBar);
1022 QQuickContainer::componentComplete();
1023 d->syncNativeMenuBarVisible();
1024}
1025
1026QFont QQuickMenuBar::defaultFont() const
1027{
1028 return QQuickTheme::font(scope: QQuickTheme::MenuBar);
1029}
1030
1031#if QT_CONFIG(accessibility)
1032QAccessible::Role QQuickMenuBar::accessibleRole() const
1033{
1034 return QAccessible::MenuBar;
1035}
1036#endif
1037
1038QT_END_NAMESPACE
1039
1040#include "moc_qquickmenubar_p.cpp"
1041

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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