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//! \instantiates 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 \sa {Customizing MenuBar}, Menu, MenuBarItem, {Menu Controls},
46 {Focus Management in Qt Quick Controls}
47*/
48
49QQuickItem *QQuickMenuBarPrivate::beginCreateItem(QQuickMenu *menu)
50{
51 Q_Q(QQuickMenuBar);
52 if (!delegate)
53 return nullptr;
54
55 QQmlContext *context = delegate->creationContext();
56 if (!context)
57 context = qmlContext(q);
58
59 QObject *object = delegate->beginCreate(context);
60 QQuickItem *item = qobject_cast<QQuickItem *>(o: object);
61 if (!item) {
62 delete object;
63 return nullptr;
64 }
65
66 if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(object: item))
67 menuBarItem->setMenu(menu);
68 item->setParentItem(q);
69 QQml_setParent_noEvent(object: item, parent: q);
70
71 return item;
72}
73
74void QQuickMenuBarPrivate::completeCreateItem()
75{
76 if (!delegate)
77 return;
78
79 delegate->completeCreate();
80}
81
82QQuickItem *QQuickMenuBarPrivate::createItem(QQuickMenu *menu)
83{
84 QQuickItem *item = beginCreateItem(menu);
85 completeCreateItem();
86 return item;
87}
88
89void QQuickMenuBarPrivate::toggleCurrentMenu(bool visible, bool activate)
90{
91 if (!currentItem || visible == popupMode)
92 return;
93
94 QQuickMenu *menu = currentItem->menu();
95
96 triggering = true;
97 popupMode = visible;
98 if (menu)
99 menu->setVisible(visible);
100 if (!visible)
101 currentItem->forceActiveFocus();
102 else if (menu && activate)
103 menu->setCurrentIndex(0);
104 triggering = false;
105}
106
107void QQuickMenuBarPrivate::activateItem(QQuickMenuBarItem *item)
108{
109 if (currentItem == item)
110 return;
111
112 if (currentItem) {
113 currentItem->setHighlighted(false);
114 if (popupMode) {
115 if (QQuickMenu *menu = currentItem->menu())
116 menu->dismiss();
117 }
118 }
119
120 if (item) {
121 item->setHighlighted(true);
122 if (popupMode) {
123 if (QQuickMenu *menu = item->menu())
124 menu->open();
125 }
126 }
127
128 currentItem = item;
129}
130
131void QQuickMenuBarPrivate::activateNextItem()
132{
133 int index = currentItem ? contentModel->indexOf(object: currentItem, objectContext: nullptr) : -1;
134 if (index >= contentModel->count() - 1)
135 index = -1;
136 activateItem(item: qobject_cast<QQuickMenuBarItem *>(object: itemAt(index: ++index)));
137}
138
139void QQuickMenuBarPrivate::activatePreviousItem()
140{
141 int index = currentItem ? contentModel->indexOf(object: currentItem, objectContext: nullptr) : contentModel->count();
142 if (index <= 0)
143 index = contentModel->count();
144 activateItem(item: qobject_cast<QQuickMenuBarItem *>(object: itemAt(index: --index)));
145}
146
147void QQuickMenuBarPrivate::onItemHovered()
148{
149 Q_Q(QQuickMenuBar);
150 QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(object: q->sender());
151 if (!item || item == currentItem || !item->isHovered() || !item->isEnabled() || QQuickMenuBarItemPrivate::get(item)->touchId != -1)
152 return;
153
154 activateItem(item);
155}
156
157void QQuickMenuBarPrivate::onItemTriggered()
158{
159 Q_Q(QQuickMenuBar);
160 QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(object: q->sender());
161 if (!item)
162 return;
163
164 if (item == currentItem) {
165 toggleCurrentMenu(visible: !popupMode, activate: false);
166 } else {
167 popupMode = true;
168 activateItem(item);
169 }
170}
171
172void QQuickMenuBarPrivate::onMenuAboutToHide()
173{
174 if (triggering || !currentItem || (currentItem->isHovered() && currentItem->isEnabled()) || !currentItem->isHighlighted())
175 return;
176
177 popupMode = false;
178 activateItem(item: nullptr);
179}
180
181qreal QQuickMenuBarPrivate::getContentWidth() const
182{
183 Q_Q(const QQuickMenuBar);
184 const int count = contentModel->count();
185 qreal totalWidth = qMax(a: 0, b: count - 1) * spacing;
186 for (int i = 0; i < count; ++i) {
187 QQuickItem *item = q->itemAt(index: i);
188 if (item)
189 totalWidth += item->implicitWidth();
190 }
191 return totalWidth;
192}
193
194qreal QQuickMenuBarPrivate::getContentHeight() const
195{
196 Q_Q(const QQuickMenuBar);
197 const int count = contentModel->count();
198 qreal maxHeight = 0;
199 for (int i = 0; i < count; ++i) {
200 QQuickItem *item = q->itemAt(index: i);
201 if (item)
202 maxHeight = qMax(a: maxHeight, b: item->implicitHeight());
203 }
204 return maxHeight;
205}
206
207void QQuickMenuBarPrivate::itemImplicitWidthChanged(QQuickItem *item)
208{
209 QQuickContainerPrivate::itemImplicitWidthChanged(item);
210 if (item != contentItem)
211 updateImplicitContentWidth();
212}
213
214void QQuickMenuBarPrivate::itemImplicitHeightChanged(QQuickItem *item)
215{
216 QQuickContainerPrivate::itemImplicitHeightChanged(item);
217 if (item != contentItem)
218 updateImplicitContentHeight();
219}
220
221void QQuickMenuBarPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj)
222{
223 QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object);
224 if (QQuickMenu *menu = qobject_cast<QQuickMenu *>(object: obj))
225 obj = QQuickMenuBarPrivate::get(menuBar)->createItem(menu);
226 QQuickContainerPrivate::contentData_append(prop, obj);
227}
228
229void QQuickMenuBarPrivate::menus_append(QQmlListProperty<QQuickMenu> *prop, QQuickMenu *obj)
230{
231 QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object);
232 menuBar->addMenu(menu: obj);
233}
234
235qsizetype QQuickMenuBarPrivate::menus_count(QQmlListProperty<QQuickMenu> *prop)
236{
237 QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object);
238 return menuBar->count();
239}
240
241QQuickMenu *QQuickMenuBarPrivate::menus_at(QQmlListProperty<QQuickMenu> *prop, qsizetype index)
242{
243 QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object);
244 return menuBar->menuAt(index);
245}
246
247void QQuickMenuBarPrivate::menus_clear(QQmlListProperty<QQuickMenu> *prop)
248{
249 QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object);
250 QQuickMenuBarPrivate::get(menuBar)->contentModel->clear();
251}
252
253QPalette QQuickMenuBarPrivate::defaultPalette() const
254{
255 return QQuickTheme::palette(scope: QQuickTheme::MenuBar);
256}
257
258QQuickMenuBar::QQuickMenuBar(QQuickItem *parent)
259 : QQuickContainer(*(new QQuickMenuBarPrivate), parent)
260{
261 Q_D(QQuickMenuBar);
262 d->changeTypes |= QQuickItemPrivate::Geometry;
263 setFlag(flag: ItemIsFocusScope);
264 setFocusPolicy(Qt::ClickFocus);
265}
266
267/*!
268 \qmlproperty Component QtQuick.Controls::MenuBar::delegate
269
270 This property holds the component that is used to create menu bar
271 items to present menus in the menu bar.
272
273 \sa MenuBarItem
274*/
275QQmlComponent *QQuickMenuBar::delegate() const
276{
277 Q_D(const QQuickMenuBar);
278 return d->delegate;
279}
280
281void QQuickMenuBar::setDelegate(QQmlComponent *delegate)
282{
283 Q_D(QQuickMenuBar);
284 if (d->delegate == delegate)
285 return;
286
287 d->delegate = delegate;
288 emit delegateChanged();
289}
290
291/*!
292 \qmlmethod Menu QtQuick.Controls::MenuBar::menuAt(int index)
293
294 Returns the menu at \a index, or \c null if it does not exist.
295*/
296QQuickMenu *QQuickMenuBar::menuAt(int index) const
297{
298 Q_D(const QQuickMenuBar);
299 QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(object: d->itemAt(index));
300 if (!item)
301 return nullptr;
302 return item->menu();
303}
304
305/*!
306 \qmlmethod void QtQuick.Controls::MenuBar::addMenu(Menu menu)
307
308 Adds \a menu to the end of the list of menus.
309*/
310void QQuickMenuBar::addMenu(QQuickMenu *menu)
311{
312 Q_D(QQuickMenuBar);
313 addItem(item: d->createItem(menu));
314}
315
316/*!
317 \qmlmethod void QtQuick.Controls::MenuBar::insertMenu(int index, Menu menu)
318
319 Inserts \a menu at \a index.
320*/
321void QQuickMenuBar::insertMenu(int index, QQuickMenu *menu)
322{
323 Q_D(QQuickMenuBar);
324 insertItem(index, item: d->createItem(menu));
325}
326
327/*!
328 \qmlmethod void QtQuick.Controls::MenuBar::removeMenu(Menu menu)
329
330 Removes and destroys the specified \a menu.
331*/
332void QQuickMenuBar::removeMenu(QQuickMenu *menu)
333{
334 Q_D(QQuickMenuBar);
335 if (!menu)
336 return;
337
338 const int count = d->contentModel->count();
339 for (int i = 0; i < count; ++i) {
340 QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(object: itemAt(index: i));
341 if (!item || item->menu() != menu)
342 continue;
343
344 removeItem(item);
345 break;
346 }
347
348 menu->deleteLater();
349}
350
351/*!
352 \qmlmethod Menu QtQuick.Controls::MenuBar::takeMenu(int index)
353
354 Removes and returns the menu at \a index.
355
356 \note The ownership of the item is transferred to the caller.
357*/
358QQuickMenu *QQuickMenuBar::takeMenu(int index)
359{
360 Q_D(QQuickMenuBar);
361 QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(object: itemAt(index));
362 if (!item)
363 return nullptr;
364
365 QQuickMenu *menu = item->menu();
366 if (!menu)
367 return nullptr;
368
369 d->removeItem(index, item);
370 item->deleteLater();
371 return menu;
372}
373
374/*!
375 \since QtQuick.Controls 2.3 (Qt 5.10)
376 \qmlproperty real QtQuick.Controls::MenuBar::contentWidth
377
378 This property holds the content width. It is used for calculating the total
379 implicit width of the menu bar.
380
381 \note This property is available in MenuBar since QtQuick.Controls 2.3 (Qt 5.10),
382 but it was promoted to the Container base type in QtQuick.Controls 2.5 (Qt 5.12).
383
384 \sa Container::contentWidth
385*/
386
387/*!
388 \since QtQuick.Controls 2.3 (Qt 5.10)
389 \qmlproperty real QtQuick.Controls::MenuBar::contentHeight
390
391 This property holds the content height. It is used for calculating the total
392 implicit height of the menu bar.
393
394 \note This property is available in MenuBar since QtQuick.Controls 2.3 (Qt 5.10),
395 but it was promoted to the Container base type in QtQuick.Controls 2.5 (Qt 5.12).
396
397 \sa Container::contentHeight
398*/
399
400/*!
401 \qmlproperty list<Menu> QtQuick.Controls::MenuBar::menus
402
403 This property holds the list of menus.
404
405 The list contains all menus that have been declared in QML as children
406 of the menu bar, and also menus that have been dynamically added or
407 inserted using the \l addMenu() and \l insertMenu() methods, respectively.
408*/
409QQmlListProperty<QQuickMenu> QQuickMenuBarPrivate::menus()
410{
411 Q_Q(QQuickMenuBar);
412 return QQmlListProperty<QQuickMenu>(q, nullptr,
413 QQuickMenuBarPrivate::menus_append,
414 QQuickMenuBarPrivate::menus_count,
415 QQuickMenuBarPrivate::menus_at,
416 QQuickMenuBarPrivate::menus_clear);
417}
418
419QQmlListProperty<QObject> QQuickMenuBarPrivate::contentData()
420{
421 Q_Q(QQuickMenuBar);
422 return QQmlListProperty<QObject>(q, nullptr,
423 QQuickMenuBarPrivate::contentData_append,
424 QQuickContainerPrivate::contentData_count,
425 QQuickContainerPrivate::contentData_at,
426 QQuickContainerPrivate::contentData_clear);
427}
428
429bool QQuickMenuBar::eventFilter(QObject *object, QEvent *event)
430{
431 Q_D(QQuickMenuBar);
432
433 if (d->altPressed) {
434 switch (event->type()) {
435 case QEvent::KeyRelease: {
436 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
437 if ((keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta)
438 && keyEvent->modifiers() == Qt::NoModifier) {
439 for (int i = 0; i < count(); ++i) {
440 if (auto *item = qobject_cast<QQuickMenuBarItem *>(object: d->itemAt(index: i))) {
441 d->activateItem(item);
442 setFocusReason(Qt::MenuBarFocusReason);
443 setFocus(true);
444 break;
445 }
446 }
447 }
448 Q_FALLTHROUGH();
449 }
450 case QEvent::MouseButtonPress:
451 case QEvent::MouseButtonRelease:
452 case QEvent::MouseMove:
453 case QEvent::TabletPress:
454 case QEvent::TabletMove:
455 case QEvent::TabletRelease:
456 case QEvent::TouchBegin:
457 case QEvent::TouchUpdate:
458 case QEvent::TouchEnd:
459 case QEvent::FocusIn:
460 case QEvent::FocusOut:
461 case QEvent::ActivationChange:
462 case QEvent::Shortcut:
463 d->altPressed = false;
464 qApp->removeEventFilter(obj: this);
465 break;
466 default:
467 break;
468 }
469 } else if (isVisible() && event->type() == QEvent::ShortcutOverride) {
470 const bool altKeyNavigation = QGuiApplicationPrivate::platformTheme()
471 ->themeHint(hint: QPlatformTheme::MenuBarFocusOnAltPressRelease).toBool();
472 if (altKeyNavigation) {
473 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
474 if ((keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta)
475 && keyEvent->modifiers() == Qt::AltModifier) {
476 d->altPressed = true;
477 qApp->installEventFilter(filterObj: this);
478 }
479 }
480 }
481 return QObject::eventFilter(watched: object, event);
482}
483
484void QQuickMenuBar::keyPressEvent(QKeyEvent *event)
485{
486 Q_D(QQuickMenuBar);
487 QQuickContainer::keyReleaseEvent(event);
488
489 switch (event->key()) {
490 case Qt::Key_Up:
491 d->toggleCurrentMenu(visible: false, activate: false);
492 break;
493
494 case Qt::Key_Down:
495 d->toggleCurrentMenu(visible: true, activate: true);
496 break;
497
498 case Qt::Key_Left:
499 case Qt::Key_Right:
500 if (isMirrored() == (event->key() == Qt::Key_Left))
501 d->activateNextItem();
502 else
503 d->activatePreviousItem();
504 break;
505 case Qt::Key_Escape:
506 if (d->currentItem) {
507 d->activateItem(item: nullptr);
508 setFocus(false);
509 }
510 break;
511 default:
512#if QT_CONFIG(shortcut)
513 if (!event->text().isEmpty() && event->modifiers() == Qt::NoModifier) {
514 const QKeyCombination mnemonic(Qt::AltModifier, Qt::Key(event->key()));
515 for (int i = 0; i < count(); ++i) {
516 if (auto *item = qobject_cast<QQuickMenuBarItem *>(object: d->itemAt(index: i))) {
517 if (item->shortcut() == mnemonic) {
518 d->activateItem(item);
519 d->toggleCurrentMenu(visible: true, activate: true);
520 }
521 }
522 }
523 }
524#endif
525 break;
526 }
527}
528
529void QQuickMenuBar::keyReleaseEvent(QKeyEvent *event)
530{
531 QQuickContainer::keyReleaseEvent(event);
532
533 switch (event->key()) {
534 case Qt::Key_Up:
535 case Qt::Key_Down:
536 case Qt::Key_Left:
537 case Qt::Key_Right:
538 case Qt::Key_Escape:
539 event->accept();
540 break;
541
542 default:
543 event->ignore();
544 break;
545 }
546}
547
548void QQuickMenuBar::hoverLeaveEvent(QHoverEvent *event)
549{
550 Q_D(QQuickMenuBar);
551 QQuickContainer::hoverLeaveEvent(event);
552 if (!d->popupMode && d->currentItem)
553 d->activateItem(item: nullptr);
554}
555
556bool QQuickMenuBar::isContent(QQuickItem *item) const
557{
558 return qobject_cast<QQuickMenuBarItem *>(object: item);
559}
560
561void QQuickMenuBar::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
562{
563 Q_D(QQuickMenuBar);
564 QQuickContainer::itemChange(change, data: value);
565 switch (change) {
566 case ItemSceneChange:
567 if (d->windowContentItem)
568 d->windowContentItem->removeEventFilter(obj: this);
569 if (value.window) {
570 d->windowContentItem = value.window->contentItem();
571 if (d->windowContentItem)
572 d->windowContentItem->installEventFilter(filterObj: this);
573 }
574 break;
575 default:
576 break;
577 }
578}
579
580void QQuickMenuBar::itemAdded(int index, QQuickItem *item)
581{
582 Q_D(QQuickMenuBar);
583 QQuickContainer::itemAdded(index, item);
584 if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(object: item)) {
585 QQuickMenuBarItemPrivate::get(item: menuBarItem)->setMenuBar(this);
586 QObjectPrivate::connect(sender: menuBarItem, signal: &QQuickControl::hoveredChanged, receiverPrivate: d, slot: &QQuickMenuBarPrivate::onItemHovered);
587 QObjectPrivate::connect(sender: menuBarItem, signal: &QQuickMenuBarItem::triggered, receiverPrivate: d, slot: &QQuickMenuBarPrivate::onItemTriggered);
588 if (QQuickMenu *menu = menuBarItem->menu())
589 QObjectPrivate::connect(sender: menu, signal: &QQuickPopup::aboutToHide, receiverPrivate: d, slot: &QQuickMenuBarPrivate::onMenuAboutToHide);
590 }
591 d->updateImplicitContentSize();
592 emit menusChanged();
593}
594
595void QQuickMenuBar::itemMoved(int index, QQuickItem *item)
596{
597 QQuickContainer::itemMoved(index, item);
598 emit menusChanged();
599}
600
601void QQuickMenuBar::itemRemoved(int index, QQuickItem *item)
602{
603 Q_D(QQuickMenuBar);
604 QQuickContainer::itemRemoved(index, item);
605 if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(object: item)) {
606 QQuickMenuBarItemPrivate::get(item: menuBarItem)->setMenuBar(nullptr);
607 QObjectPrivate::disconnect(sender: menuBarItem, signal: &QQuickControl::hoveredChanged, receiverPrivate: d, slot: &QQuickMenuBarPrivate::onItemHovered);
608 QObjectPrivate::disconnect(sender: menuBarItem, signal: &QQuickMenuBarItem::triggered, receiverPrivate: d, slot: &QQuickMenuBarPrivate::onItemTriggered);
609 if (QQuickMenu *menu = menuBarItem->menu())
610 QObjectPrivate::disconnect(sender: menu, signal: &QQuickPopup::aboutToHide, receiverPrivate: d, slot: &QQuickMenuBarPrivate::onMenuAboutToHide);
611 }
612 d->updateImplicitContentSize();
613 emit menusChanged();
614}
615
616QFont QQuickMenuBar::defaultFont() const
617{
618 return QQuickTheme::font(scope: QQuickTheme::MenuBar);
619}
620
621#if QT_CONFIG(accessibility)
622QAccessible::Role QQuickMenuBar::accessibleRole() const
623{
624 return QAccessible::MenuBar;
625}
626#endif
627
628QT_END_NAMESPACE
629
630#include "moc_qquickmenubar_p.cpp"
631

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