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
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// Qt-Security score:significant reason:default
4
6
7#include <QtCore/qloggingcategory.h>
8//#include <QtGui/qicon.h>
9//#if QT_CONFIG(shortcut)
10//#include <QtGui/qkeysequence.h>
11//#endif
12#include <QtGui/qpa/qplatformmenu.h>
13#include <QtGui/qpa/qplatformtheme.h>
14#include <QtGui/private/qguiapplication_p.h>
15//#include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h>
16#include <QtQuickTemplates2/private/qquickabstractbutton_p_p.h>
17#include <QtQuickTemplates2/private/qquickaction_p.h>
18#include <QtQuickTemplates2/private/qquickmenu_p_p.h>
19#include <QtQuickTemplates2/private/qquickmenuseparator_p.h>
20#include <QtQuickTemplates2/private/qquicknativeiconloader_p.h>
21#include <QtQuickTemplates2/private/qquicknativemenuitem_p.h>
22#include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h>
23
25
26Q_STATIC_LOGGING_CATEGORY(lcNativeMenuItem, "qt.quick.controls.nativemenuitem")
27
28/*!
29 \class QQuickNativeMenuItem
30 \brief A native menu item.
31 \since 6.7
32 \internal
33
34 Creates a native menu item from an Action/MenuItem/Menu,
35 and syncs the properties and signals. It can also represent a
36 MenuSeparator.
37
38 \sa Menu, Action
39*/
40
41QQuickNativeMenuItem *QQuickNativeMenuItem::createFromNonNativeItem(
42 QQuickMenu *parentMenu, QQuickItem *nonNativeItem)
43{
44 auto *menuItem = qobject_cast<QQuickMenuItem *>(nonNativeItem);
45 Type type = Type::Unknown;
46 if (menuItem) {
47 if (menuItem->action()) {
48 type = Type::Action;
49 } else if (menuItem->subMenu()) {
50 type = Type::SubMenu;
51 } else {
52 // It's a plain MenuItem, rather than a MenuItem created by us for an Action or Menu.
53 type = Type::MenuItem;
54 }
55 } else if (qobject_cast<QQuickMenuSeparator *>(nonNativeItem)) {
56 type = Type::Separator;
57 }
58
59 std::unique_ptr<QQuickNativeMenuItem> nativeMenuItemPtr(new QQuickNativeMenuItem(
60 parentMenu, nonNativeItem, type));
61 if (type == Type::Unknown) {
62 // It's not a Menu/Action/MenuSeparator that we're dealing with, but we still need
63 // to create the QQuickNativeMenu item for it so that our container has the same
64 // indices as the menu's contentModel.
65 return nativeMenuItemPtr.release();
66 }
67
68 qCDebug(lcNativeMenuItem) << "attemping to create native menu item for"
69 << nativeMenuItemPtr->debugText();
70 auto *parentMenuPrivate = QQuickMenuPrivate::get(parentMenu);
71 nativeMenuItemPtr->m_handle.reset(parentMenuPrivate->handle->createMenuItem());
72 if (!nativeMenuItemPtr->m_handle)
73 nativeMenuItemPtr->m_handle.reset(QGuiApplicationPrivate::platformTheme()->createPlatformMenuItem());
74 if (!nativeMenuItemPtr->m_handle)
75 return nullptr;
76
77 auto *nativeMenuItem = nativeMenuItemPtr.release();
78 switch (type) {
79 case Type::Action:
80 // Ensure that the action is triggered when the user clicks on a native menu item.
81 connect(nativeMenuItem->m_handle.get(), &QPlatformMenuItem::activated,
82 nativeMenuItem->action(), [nativeMenuItem, parentMenu](){
83 nativeMenuItem->action()->trigger(parentMenu);
84 });
85 // Handle programmatic changes in the Action.
86 connect(nativeMenuItem->action(), &QQuickAction::textChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
87 connect(nativeMenuItem->action(), &QQuickAction::iconChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
88 connect(nativeMenuItem->action(), &QQuickAction::enabledChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
89 connect(nativeMenuItem->action(), &QQuickAction::checkedChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
90 connect(nativeMenuItem->action(), &QQuickAction::checkableChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
91 break;
92 case Type::SubMenu:
93 nativeMenuItem->m_handle->setMenu(QQuickMenuPrivate::get(
94 nativeMenuItem->subMenu())->handle.get());
95
96 // Handle programmatic changes in the Menu.
97 connect(nativeMenuItem->subMenu(), &QQuickMenu::enabledChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
98 connect(nativeMenuItem->subMenu(), &QQuickMenu::titleChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
99 break;
100 case Type::MenuItem:
101 // Ensure that the MenuItem is clicked when the user clicks on a native menu item.
102 connect(nativeMenuItem->m_handle.get(), &QPlatformMenuItem::activated,
103 menuItem, [menuItem](){
104 if (menuItem->isCheckable()) {
105 // This changes the checked state, which we need when syncing but also to ensure that
106 // the user can still use MenuItem's API even though they can't actually interact with it.
107 menuItem->toggle();
108 }
109 // The same applies here: allow users to respond to the MenuItem's clicked signal.
110 QQuickAbstractButtonPrivate::get(menuItem)->click();
111 });
112 // Handle programmatic changes in the MenuItem.
113 connect(menuItem, &QQuickMenuItem::textChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
114 connect(menuItem, &QQuickMenuItem::iconChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
115 connect(menuItem, &QQuickMenuItem::enabledChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
116 connect(menuItem, &QQuickMenuItem::checkedChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
117 connect(menuItem, &QQuickMenuItem::checkableChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
118 break;
119 case Type::Separator:
120 case Type::Unknown:
121 break;
122 }
123
124 return nativeMenuItem;
125}
126
127QQuickNativeMenuItem::QQuickNativeMenuItem(QQuickMenu *parentMenu, QQuickItem *nonNativeItem,
128 QQuickNativeMenuItem::Type type)
129 : QObject(parentMenu)
130 , m_parentMenu(parentMenu)
131 , m_nonNativeItem(nonNativeItem)
132 , m_type(type)
133{
134}
135
136QQuickNativeMenuItem::~QQuickNativeMenuItem()
137{
138 qCDebug(lcNativeMenuItem) << "destroying" << this << debugText();
139}
140
141QQuickAction *QQuickNativeMenuItem::action() const
142{
143 return m_type == Type::Action ? qobject_cast<QQuickMenuItem *>(m_nonNativeItem)->action() : nullptr;
144}
145
146QQuickMenu *QQuickNativeMenuItem::subMenu() const
147{
148 return m_type == Type::SubMenu ? qobject_cast<QQuickMenuItem *>(m_nonNativeItem)->subMenu() : nullptr;
149}
150
151QQuickMenuSeparator *QQuickNativeMenuItem::separator() const
152{
153 return m_type == Type::Separator ? qobject_cast<QQuickMenuSeparator *>(m_nonNativeItem) : nullptr;
154}
155
156QPlatformMenuItem *QQuickNativeMenuItem::handle() const
157{
158 return m_handle.get();
159}
160
161void QQuickNativeMenuItem::sync()
162{
163 if (m_type == Type::Unknown)
164 return;
165
166 if (m_syncing)
167 return;
168
169 QScopedValueRollback recursionGuard(m_syncing, true);
170
171 const auto *action = this->action();
172 const auto *separator = this->separator();
173 auto *subMenu = this->subMenu();
174 auto *menuItem = qobject_cast<QQuickMenuItem *>(m_nonNativeItem);
175
176 // Store the values in variables so that we can use it in the debug output below.
177 const bool enabled = action ? action->isEnabled()
178 : subMenu ? subMenu->isEnabled()
179 : menuItem && menuItem->isEnabled();
180 m_handle->setEnabled(enabled);
181// m_handle->setVisible(isVisible());
182
183 const bool isSeparator = separator != nullptr;
184 m_handle->setIsSeparator(isSeparator);
185
186 const bool checkable = action ? action->isCheckable() : menuItem && menuItem->isCheckable();
187 m_handle->setCheckable(checkable);
188
189 const bool checked = action ? action->isChecked() : menuItem && menuItem->isChecked();
190 m_handle->setChecked(checked);
191
192 m_handle->setRole(QPlatformMenuItem::TextHeuristicRole);
193
194 const QString text = action ? action->text()
195 : subMenu ? subMenu->title()
196 : menuItem ? menuItem->text() : QString();
197 m_handle->setText(text);
198
199// m_handle->setFont(m_font);
200// m_handle->setHasExclusiveGroup(m_group && m_group->isExclusive());
201 m_handle->setHasExclusiveGroup(false);
202
203 const QQuickIcon icon = effectiveIcon();
204 const auto *menuPrivate = QQuickMenuPrivate::get(m_parentMenu);
205
206 // We should reload the icon if the window's DPR has changed, regardless if its properties have changed.
207 // We can't check for ItemDevicePixelRatioHasChanged in QQuickMenu::itemChange,
208 // because that isn't sent when the menu isn't visible, and will never
209 // be sent for native menus. We instead store lastDevicePixelRatio in QQuickMenu
210 // (to avoid storing it for each menu item) and set it whenever it's opened.
211 bool dprChanged = false;
212 if (!qGuiApp->topLevelWindows().isEmpty()) {
213 const auto *window = qGuiApp->topLevelWindows().first();
214 dprChanged = !qFuzzyCompare(window->devicePixelRatio(), menuPrivate->lastDevicePixelRatio);
215 }
216
217 if (!icon.isEmpty() && (icon != iconLoader()->icon() || dprChanged)) {
218 // This will load the icon, which will call sync() recursively, hence the m_syncing check.
219 reloadIcon();
220 }
221
222 if (subMenu) {
223 // Sync first as dynamically created menus may need to get the handle recreated.
224 auto *subMenuPrivate = QQuickMenuPrivate::get(subMenu);
225 subMenuPrivate->syncWithNativeMenu();
226 if (subMenuPrivate->handle)
227 m_handle->setMenu(subMenuPrivate->handle.get());
228 }
229
230#if QT_CONFIG(shortcut)
231 if (action)
232 m_handle->setShortcut(action->shortcut());
233#endif
234
235 if (m_parentMenu) {
236 auto *menuPrivate = QQuickMenuPrivate::get(m_parentMenu);
237 if (menuPrivate->handle)
238 menuPrivate->handle->syncMenuItem(m_handle.get());
239 }
240
241 qCDebug(lcNativeMenuItem) << "sync called on" << debugText() << "handle" << m_handle.get()
242 << "enabled:" << enabled << "isSeparator" << isSeparator << "checkable" << checkable
243 << "checked" << checked << "text" << text;
244}
245
246QQuickIcon QQuickNativeMenuItem::effectiveIcon() const
247{
248 if (const auto *action = this->action())
249 return action->icon();
250 if (const auto *subMenu = this->subMenu())
251 return subMenu->icon();
252 if (const auto *menuItem = qobject_cast<QQuickMenuItem *>(m_nonNativeItem))
253 return menuItem->icon();
254 return {};
255}
256
257QQuickNativeIconLoader *QQuickNativeMenuItem::iconLoader() const
258{
259 if (!m_iconLoader) {
260 QQuickNativeMenuItem *that = const_cast<QQuickNativeMenuItem *>(this);
261 static int slot = staticMetaObject.indexOfSlot("updateIcon()");
262 m_iconLoader = new QQuickNativeIconLoader(slot, that);
263 // Qt Labs Platform's QQuickMenuItem would call m_iconLoader->setEnabled(m_complete) here,
264 // but since QQuickMenuPrivate::maybeCreateAndInsertNativeItem asserts that the menu's
265 // completed loading, we can just set it to true.
266 m_iconLoader->setEnabled(true);
267 }
268 return m_iconLoader;
269}
270
271void QQuickNativeMenuItem::reloadIcon()
272{
273 iconLoader()->setIcon(effectiveIcon());
274 m_handle->setIcon(iconLoader()->toQIcon());
275}
276
277void QQuickNativeMenuItem::updateIcon()
278{
279 sync();
280}
281
282void QQuickNativeMenuItem::addShortcut()
283{
284#if QT_CONFIG(shortcut)
285 const auto *action = this->action();
286 const QKeySequence sequence = action ? action->shortcut() : QKeySequence();
287 if (!sequence.isEmpty() && action->isEnabled()) {
288 m_shortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(this, sequence,
289 Qt::WindowShortcut, QQuickShortcutContext::matcher);
290 } else {
291 m_shortcutId = -1;
292 }
293#endif
294}
295
296void QQuickNativeMenuItem::removeShortcut()
297{
298#if QT_CONFIG(shortcut)
299 if (m_shortcutId == -1)
300 return;
301
302 QKeySequence sequence;
303 switch (m_type) {
304 case Type::Action:
305 sequence = action()->shortcut();
306 break;
307 default:
308 // TODO
309 break;
310 }
311
312 QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(m_shortcutId, this, sequence);
313#endif
314}
315
316QString QQuickNativeMenuItem::debugText() const
317{
318 // Prepend below to avoid extra lines from break statements.
319 QString text = QDebug::toString(m_handle.get());
320
321 switch (m_type) {
322 case Type::Action:
323 return text.prepend(QString::fromLatin1("Action(text = %1) ").arg(action()->text()));
324 case Type::SubMenu:
325 return text.prepend(QString::fromLatin1("Sub-menu(title = %1) ").arg(subMenu()->title()));
326 case Type::MenuItem:
327 return text.prepend(QString::fromLatin1("MenuItem(text = %1) ").arg(
328 qobject_cast<QQuickMenuItem *>(m_nonNativeItem)->text()));
329 case Type::Separator:
330 return text.prepend(QStringLiteral("Separator "));
331 case Type::Unknown:
332 return text.prepend(QStringLiteral("(Unknown) "));
333 }
334
335 Q_UNREACHABLE();
336}
337
338QT_END_NAMESPACE
339
340#include "moc_qquicknativemenuitem_p.cpp"