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

Provided by KDAB

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

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