Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qquicknativemenuitem.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 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 "qquickmenuitem_p.h"
5
6#include <QtCore/qloggingcategory.h>
7//#include <QtGui/qicon.h>
8//#if QT_CONFIG(shortcut)
9//#include <QtGui/qkeysequence.h>
10//#endif
11#include <QtGui/qpa/qplatformmenu.h>
12#include <QtGui/qpa/qplatformtheme.h>
13#include <QtGui/private/qguiapplication_p.h>
14//#include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h>
15#include <QtQuickTemplates2/private/qquickabstractbutton_p_p.h>
16#include <QtQuickTemplates2/private/qquickaction_p.h>
17#include <QtQuickTemplates2/private/qquickmenu_p_p.h>
18#include <QtQuickTemplates2/private/qquickmenuseparator_p.h>
19#include <QtQuickTemplates2/private/qquicknativeiconloader_p.h>
20#include <QtQuickTemplates2/private/qquicknativemenuitem_p.h>
21#include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h>
22
24
25Q_STATIC_LOGGING_CATEGORY(lcNativeMenuItem, "qt.quick.controls.nativemenuitem")
26
27
41 QQuickMenu *parentMenu, QQuickItem *nonNativeItem)
42{
43 auto *menuItem = qobject_cast<QQuickMenuItem *>(nonNativeItem);
44 Type type = Type::Unknown;
45 if (menuItem) {
46 if (menuItem->action()) {
47 type = Type::Action;
48 } else if (menuItem->subMenu()) {
49 type = Type::SubMenu;
50 } else {
51 // It's a plain MenuItem, rather than a MenuItem created by us for an Action or Menu.
52 type = Type::MenuItem;
53 }
54 } else if (qobject_cast<QQuickMenuSeparator *>(nonNativeItem)) {
55 type = Type::Separator;
56 }
57
58 std::unique_ptr<QQuickNativeMenuItem> nativeMenuItemPtr(new QQuickNativeMenuItem(
59 parentMenu, nonNativeItem, type));
60 if (type == Type::Unknown) {
61 // It's not a Menu/Action/MenuSeparator that we're dealing with, but we still need
62 // to create the QQuickNativeMenu item for it so that our container has the same
63 // indices as the menu's contentModel.
64 return nativeMenuItemPtr.release();
65 }
66
67 qCDebug(lcNativeMenuItem) << "attemping to create native menu item for"
68 << nativeMenuItemPtr->debugText();
69 auto *parentMenuPrivate = QQuickMenuPrivate::get(parentMenu);
70 nativeMenuItemPtr->m_handle.reset(parentMenuPrivate->handle->createMenuItem());
71 if (!nativeMenuItemPtr->m_handle)
72 nativeMenuItemPtr->m_handle.reset(QGuiApplicationPrivate::platformTheme()->createPlatformMenuItem());
73 if (!nativeMenuItemPtr->m_handle)
74 return nullptr;
75
76 auto *nativeMenuItem = nativeMenuItemPtr.release();
77 switch (type) {
78 case Type::Action:
79 // Ensure that the action is triggered when the user clicks on a native menu item.
80 connect(nativeMenuItem->m_handle.get(), &QPlatformMenuItem::activated,
81 nativeMenuItem->action(), [nativeMenuItem, parentMenu](){
82 nativeMenuItem->action()->trigger(parentMenu);
83 });
84 // Handle programmatic changes in the Action.
85 connect(nativeMenuItem->action(), &QQuickAction::textChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
86 connect(nativeMenuItem->action(), &QQuickAction::iconChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
87 connect(nativeMenuItem->action(), &QQuickAction::enabledChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
88 connect(nativeMenuItem->action(), &QQuickAction::checkedChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
89 connect(nativeMenuItem->action(), &QQuickAction::checkableChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
90 break;
91 case Type::SubMenu:
92 nativeMenuItem->m_handle->setMenu(QQuickMenuPrivate::get(
93 nativeMenuItem->subMenu())->handle.get());
94
95 // Handle programmatic changes in the Menu.
96 connect(nativeMenuItem->subMenu(), &QQuickMenu::enabledChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
97 connect(nativeMenuItem->subMenu(), &QQuickMenu::titleChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
98 break;
99 case Type::MenuItem:
100 // Ensure that the MenuItem is clicked when the user clicks on a native menu item.
101 connect(nativeMenuItem->m_handle.get(), &QPlatformMenuItem::activated,
102 menuItem, [menuItem](){
103 // This changes the checked state, which we need when syncing but also to ensure that
104 // the user can still use MenuItem's API even though they can't actually interact with it.
105 menuItem->toggle();
106 // The same applies here: allow users to respond to the MenuItem's clicked signal.
107 QQuickAbstractButtonPrivate::get(menuItem)->click();
108 });
109 // Handle programmatic changes in the MenuItem.
111 connect(menuItem, &QQuickMenuItem::iconChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
115 break;
116 case Type::Separator:
117 case Type::Unknown:
118 break;
119 }
120
121 return nativeMenuItem;
122}
123
124QQuickNativeMenuItem::QQuickNativeMenuItem(QQuickMenu *parentMenu, QQuickItem *nonNativeItem,
125 QQuickNativeMenuItem::Type type)
126 : QObject(parentMenu)
127 , m_parentMenu(parentMenu)
128 , m_nonNativeItem(nonNativeItem)
129 , m_type(type)
130{
131}
132
134{
135 qCDebug(lcNativeMenuItem) << "destroying" << this << debugText();
136}
137
139{
140 return m_type == Type::Action ? qobject_cast<QQuickMenuItem *>(m_nonNativeItem)->action() : nullptr;
141}
142
144{
145 return m_type == Type::SubMenu ? qobject_cast<QQuickMenuItem *>(m_nonNativeItem)->subMenu() : nullptr;
146}
147
149{
150 return m_type == Type::Separator ? qobject_cast<QQuickMenuSeparator *>(m_nonNativeItem) : nullptr;
151}
152
154{
155 return m_handle.get();
156}
157
159{
160 if (m_type == Type::Unknown)
161 return;
162
163 if (m_syncing)
164 return;
165
166 QScopedValueRollback recursionGuard(m_syncing, true);
167
168 const auto *action = this->action();
169 const auto *separator = this->separator();
170 auto *subMenu = this->subMenu();
171 auto *menuItem = qobject_cast<QQuickMenuItem *>(m_nonNativeItem);
172
173 // Store the values in variables so that we can use it in the debug output below.
174 const bool enabled = action ? action->isEnabled()
175 : subMenu ? subMenu->isEnabled()
176 : menuItem && menuItem->isEnabled();
177 m_handle->setEnabled(enabled);
178// m_handle->setVisible(isVisible());
179
180 const bool isSeparator = separator != nullptr;
181 m_handle->setIsSeparator(isSeparator);
182
183 const bool checkable = action ? action->isCheckable() : menuItem && menuItem->isCheckable();
184 m_handle->setCheckable(checkable);
185
186 const bool checked = action ? action->isChecked() : menuItem && menuItem->isChecked();
187 m_handle->setChecked(checked);
188
189 m_handle->setRole(QPlatformMenuItem::TextHeuristicRole);
190
191 const QString text = action ? action->text()
192 : subMenu ? subMenu->title()
193 : menuItem ? menuItem->text() : QString();
194 m_handle->setText(text);
195
196// m_handle->setFont(m_font);
197// m_handle->setHasExclusiveGroup(m_group && m_group->isExclusive());
198 m_handle->setHasExclusiveGroup(false);
199
200 const QQuickIcon icon = effectiveIcon();
201 const auto *menuPrivate = QQuickMenuPrivate::get(m_parentMenu);
202 const auto *window = qGuiApp->topLevelWindows().first();
203 // We should reload the icon if the window's DPR has changed, regardless if its properties have changed.
204 // We can't check for ItemDevicePixelRatioHasChanged in QQuickMenu::itemChange,
205 // because that isn't sent when the menu isn't visible, and will never
206 // be sent for native menus. We instead store lastDevicePixelRatio in QQuickMenu
207 // (to avoid storing it for each menu item) and set it whenever it's opened.
208 const bool dprChanged = !qFuzzyCompare(window->devicePixelRatio(), menuPrivate->lastDevicePixelRatio);
209 if (!icon.isEmpty() && (icon != iconLoader()->icon() || dprChanged)) {
210 // This will load the icon, which will call sync() recursively, hence the m_syncing check.
211 reloadIcon();
212 }
213
214 if (subMenu) {
215 // Sync first as dynamically created menus may need to get the handle recreated.
216 auto *subMenuPrivate = QQuickMenuPrivate::get(subMenu);
217 subMenuPrivate->syncWithNativeMenu();
218 if (subMenuPrivate->handle)
219 m_handle->setMenu(subMenuPrivate->handle.get());
220 }
221
222#if QT_CONFIG(shortcut)
223 if (action)
224 m_handle->setShortcut(action->shortcut());
225#endif
226
227 if (m_parentMenu) {
228 auto *menuPrivate = QQuickMenuPrivate::get(m_parentMenu);
229 if (menuPrivate->handle)
230 menuPrivate->handle->syncMenuItem(m_handle.get());
231 }
232
233 qCDebug(lcNativeMenuItem) << "sync called on" << debugText() << "handle" << m_handle.get()
234 << "enabled:" << enabled << "isSeparator" << isSeparator << "checkable" << checkable
235 << "checked" << checked << "text" << text;
236}
237
239{
240 if (const auto *action = this->action())
241 return action->icon();
242 if (const auto *subMenu = this->subMenu())
243 return subMenu->icon();
244 if (const auto *menuItem = qobject_cast<QQuickMenuItem *>(m_nonNativeItem))
245 return menuItem->icon();
246 return {};
247}
248
250{
251 if (!m_iconLoader) {
252 QQuickNativeMenuItem *that = const_cast<QQuickNativeMenuItem *>(this);
253 static int slot = staticMetaObject.indexOfSlot("updateIcon()");
254 m_iconLoader = new QQuickNativeIconLoader(slot, that);
255 // Qt Labs Platform's QQuickMenuItem would call m_iconLoader->setEnabled(m_complete) here,
256 // but since QQuickMenuPrivate::maybeCreateAndInsertNativeItem asserts that the menu's
257 // completed loading, we can just set it to true.
258 m_iconLoader->setEnabled(true);
259 }
260 return m_iconLoader;
261}
262
264{
266 m_handle->setIcon(iconLoader()->toQIcon());
267}
268
269void QQuickNativeMenuItem::updateIcon()
270{
271 sync();
272}
273
274void QQuickNativeMenuItem::addShortcut()
275{
276#if QT_CONFIG(shortcut)
277 const auto *action = this->action();
278 const QKeySequence sequence = action ? action->shortcut() : QKeySequence();
279 if (!sequence.isEmpty() && action->isEnabled()) {
280 m_shortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(this, sequence,
282 } else {
283 m_shortcutId = -1;
284 }
285#endif
286}
287
288void QQuickNativeMenuItem::removeShortcut()
289{
290#if QT_CONFIG(shortcut)
291 if (m_shortcutId == -1)
292 return;
293
294 QKeySequence sequence;
295 switch (m_type) {
296 case Type::Action:
297 sequence = action()->shortcut();
298 break;
299 default:
300 // TODO
301 break;
302 }
303
304 QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(m_shortcutId, this, sequence);
305#endif
306}
307
309{
310 switch (m_type) {
311 case Type::Action:
312 return QString::fromLatin1("Action(text = %1)").arg(action()->text());
313 case Type::SubMenu:
314 return QString::fromLatin1("Sub-menu(title = %1)").arg(subMenu()->title());
315 case Type::MenuItem:
316 return QString::fromLatin1("MenuItem(text = %1)").arg(
317 qobject_cast<QQuickMenuItem *>(m_nonNativeItem)->text());
318 case Type::Separator:
319 return QStringLiteral("Separator");
320 case Type::Unknown:
321 return QStringLiteral("(Unknown)");
322 }
323
324 Q_UNREACHABLE();
325}
326
328
329#include "moc_qquicknativemenuitem_p.cpp"
static QGuiApplicationPrivate * instance()
static QPlatformTheme * platformTheme()
The QKeySequence class encapsulates a key sequence as used by shortcuts.
bool isEmpty() const
Returns true if the key sequence is empty; otherwise returns false.
\inmodule QtCore
Definition qobject.h:103
bool isCheckable() const
\qmlproperty bool QtQuick.Controls::Action::checkable
void checkedChanged(bool checked)
void checkableChanged(bool checkable)
void enabledChanged(bool enabled)
QQuickIcon icon
void textChanged(const QString &text)
void iconChanged(const QQuickIcon &icon)
bool isEnabled() const
\qmlproperty bool QtQuick.Controls::Action::enabled
bool isChecked() const
\qmlproperty bool QtQuick.Controls::Action::checked
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:63
void enabledChanged()
static QQuickMenuPrivate * get(QQuickMenu *menu)
QString title
QQuickIcon icon
void titleChanged(const QString &title)
void setIcon(const QQuickIcon &icon)
QQuickIcon effectiveIcon() const
QQuickNativeIconLoader * iconLoader() const
QQuickMenu * subMenu() const
QPlatformMenuItem * handle() const
QQuickMenuSeparator * separator() const
QQuickAction * action() const
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5881
QString text
Combined button and popup list for selecting options.
@ WindowShortcut
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
#define qGuiApp
static bool isSeparator(char c)
Definition qhsts.cpp:280
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLenum type
#define QStringLiteral(str)
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
QString title
[35]
aWidget window() -> setWindowTitle("New Window Title")
[2]
static bool matcher(QObject *object, Qt::ShortcutContext context)
Definition moc.h:23