1// Copyright (C) 2017 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 "qquicklabsplatformmenuitem_p.h"
5#include "qquicklabsplatformmenu_p.h"
6#include "qquicklabsplatformmenuitemgroup_p.h"
7#include "qquicklabsplatformiconloader_p.h"
8
9#include <QtGui/qicon.h>
10#if QT_CONFIG(shortcut)
11#include <QtGui/qkeysequence.h>
12#endif
13#include <QtGui/qpa/qplatformtheme.h>
14#include <QtGui/private/qguiapplication_p.h>
15#include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h>
16
17#include "widgets/qwidgetplatform_p.h"
18
19QT_BEGIN_NAMESPACE
20
21/*!
22 \qmltype MenuItem
23 \inherits QtObject
24//! \instantiates QQuickLabsPlatformMenuItem
25 \inqmlmodule Qt.labs.platform
26 \since 5.8
27 \brief A native menu item.
28
29 The MenuItem type provides a QML API for native platform menu items.
30
31 \image qtlabsplatform-menu.png
32
33 A menu item consists of an \l icon, \l text, and \l shortcut.
34
35 \code
36 Menu {
37 id: zoomMenu
38
39 MenuItem {
40 text: qsTr("Zoom In")
41 shortcut: StandardKey.ZoomIn
42 onTriggered: zoomIn()
43 }
44
45 MenuItem {
46 text: qsTr("Zoom Out")
47 shortcut: StandardKey.ZoomOut
48 onTriggered: zoomOut()
49 }
50 }
51 \endcode
52
53 \labs
54
55 \sa Menu, MenuItemGroup
56*/
57
58/*!
59 \qmlsignal Qt.labs.platform::MenuItem::triggered()
60
61 This signal is emitted when the menu item is triggered by the user.
62*/
63
64/*!
65 \qmlsignal Qt.labs.platform::MenuItem::hovered()
66
67 This signal is emitted when the menu item is hovered by the user.
68*/
69
70QQuickLabsPlatformMenuItem::QQuickLabsPlatformMenuItem(QObject *parent)
71 : QObject(parent),
72 m_complete(false),
73 m_enabled(true),
74 m_visible(true),
75 m_separator(false),
76 m_checkable(false),
77 m_checked(false),
78 m_role(QPlatformMenuItem::TextHeuristicRole),
79 m_menu(nullptr),
80 m_subMenu(nullptr),
81 m_group(nullptr),
82 m_iconLoader(nullptr),
83 m_handle(nullptr)
84{
85}
86
87QQuickLabsPlatformMenuItem::~QQuickLabsPlatformMenuItem()
88{
89 if (m_menu)
90 m_menu->removeItem(item: this);
91 if (m_group)
92 m_group->removeItem(item: this);
93 removeShortcut();
94 delete m_iconLoader;
95 m_iconLoader = nullptr;
96 delete m_handle;
97 m_handle = nullptr;
98}
99
100QPlatformMenuItem *QQuickLabsPlatformMenuItem::handle() const
101{
102 return m_handle;
103}
104
105QPlatformMenuItem *QQuickLabsPlatformMenuItem::create()
106{
107 if (!m_handle && m_menu && m_menu->handle()) {
108 m_handle = m_menu->handle()->createMenuItem();
109
110 // TODO: implement QCocoaMenu::createMenuItem()
111 if (!m_handle)
112 m_handle = QGuiApplicationPrivate::platformTheme()->createPlatformMenuItem();
113
114 if (!m_handle)
115 m_handle = QWidgetPlatform::createMenuItem();
116
117 if (m_handle) {
118 connect(sender: m_handle, signal: &QPlatformMenuItem::activated, context: this, slot: &QQuickLabsPlatformMenuItem::activate);
119 connect(sender: m_handle, signal: &QPlatformMenuItem::hovered, context: this, slot: &QQuickLabsPlatformMenuItem::hovered);
120 }
121 }
122 return m_handle;
123}
124
125void QQuickLabsPlatformMenuItem::sync()
126{
127 if (!m_complete || !create())
128 return;
129
130 m_handle->setEnabled(isEnabled());
131 m_handle->setVisible(isVisible());
132 m_handle->setIsSeparator(m_separator);
133 m_handle->setCheckable(m_checkable);
134 m_handle->setChecked(m_checked);
135 m_handle->setRole(m_role);
136 m_handle->setText(m_text);
137 m_handle->setFont(m_font);
138 m_handle->setHasExclusiveGroup(m_group && m_group->isExclusive());
139
140 if (m_iconLoader)
141 m_handle->setIcon(m_iconLoader->toQIcon());
142
143 if (m_subMenu) {
144 // Sync first as dynamically created menus may need to get the
145 // handle recreated
146 m_subMenu->sync();
147 if (m_subMenu->handle())
148 m_handle->setMenu(m_subMenu->handle());
149 }
150
151#if QT_CONFIG(shortcut)
152 QKeySequence sequence;
153 if (m_shortcut.metaType().id() == QMetaType::Int)
154 sequence = QKeySequence(static_cast<QKeySequence::StandardKey>(m_shortcut.toInt()));
155 else if (m_shortcut.metaType().id() == QMetaType::QKeySequence)
156 sequence = m_shortcut.value<QKeySequence>();
157 else
158 sequence = QKeySequence::fromString(str: m_shortcut.toString());
159 m_handle->setShortcut(sequence.toString());
160#endif
161
162 if (m_menu && m_menu->handle())
163 m_menu->handle()->syncMenuItem(menuItem: m_handle);
164}
165
166/*!
167 \readonly
168 \qmlproperty Menu Qt.labs.platform::MenuItem::menu
169
170 This property holds the menu that the item belongs to, or \c null if the
171 item is not in a menu.
172*/
173QQuickLabsPlatformMenu *QQuickLabsPlatformMenuItem::menu() const
174{
175 return m_menu;
176}
177
178void QQuickLabsPlatformMenuItem::setMenu(QQuickLabsPlatformMenu *menu)
179{
180 if (m_menu == menu)
181 return;
182
183 m_menu = menu;
184 emit menuChanged();
185}
186
187/*!
188 \readonly
189 \qmlproperty Menu Qt.labs.platform::MenuItem::subMenu
190
191 This property holds the sub-menu that the item contains, or \c null if
192 the item is not a sub-menu item.
193*/
194QQuickLabsPlatformMenu *QQuickLabsPlatformMenuItem::subMenu() const
195{
196 return m_subMenu;
197}
198
199void QQuickLabsPlatformMenuItem::setSubMenu(QQuickLabsPlatformMenu *menu)
200{
201 if (m_subMenu == menu)
202 return;
203
204 m_subMenu = menu;
205 sync();
206 emit subMenuChanged();
207}
208
209/*!
210 \qmlproperty MenuItemGroup Qt.labs.platform::MenuItem::group
211
212 This property holds the group that the item belongs to, or \c null if the
213 item is not in a group.
214*/
215QQuickLabsPlatformMenuItemGroup *QQuickLabsPlatformMenuItem::group() const
216{
217 return m_group;
218}
219
220void QQuickLabsPlatformMenuItem::setGroup(QQuickLabsPlatformMenuItemGroup *group)
221{
222 if (m_group == group)
223 return;
224
225 bool wasEnabled = isEnabled();
226 bool wasVisible = isVisible();
227
228 if (group)
229 group->addItem(item: this);
230
231 m_group = group;
232 sync();
233 emit groupChanged();
234
235 if (isEnabled() != wasEnabled)
236 emit enabledChanged();
237 if (isVisible() != wasVisible)
238 emit visibleChanged();
239}
240
241/*!
242 \qmlproperty bool Qt.labs.platform::MenuItem::enabled
243
244 This property holds whether the item is enabled. The default value is \c true.
245
246 Disabled items cannot be triggered by the user. They do not disappear from menus,
247 but they are displayed in a way which indicates that they are unavailable. For
248 example, they might be displayed using only shades of gray.
249
250 When an item is disabled, it is not possible to trigger it through its \l shortcut.
251*/
252bool QQuickLabsPlatformMenuItem::isEnabled() const
253{
254 return m_enabled && (!m_group || m_group->isEnabled());
255}
256
257void QQuickLabsPlatformMenuItem::setEnabled(bool enabled)
258{
259 if (m_enabled == enabled)
260 return;
261
262 if (!enabled)
263 removeShortcut();
264
265 bool wasEnabled = isEnabled();
266 m_enabled = enabled;
267
268 if (enabled)
269 addShortcut();
270
271 sync();
272 if (isEnabled() != wasEnabled)
273 emit enabledChanged();
274}
275
276/*!
277 \qmlproperty bool Qt.labs.platform::MenuItem::visible
278
279 This property holds whether the item is visible. The default value is \c true.
280*/
281bool QQuickLabsPlatformMenuItem::isVisible() const
282{
283 return m_visible && (!m_group || m_group->isVisible());
284}
285
286void QQuickLabsPlatformMenuItem::setVisible(bool visible)
287{
288 if (m_visible == visible)
289 return;
290
291 bool wasVisible = isVisible();
292 m_visible = visible;
293 sync();
294 if (isVisible() != wasVisible)
295 emit visibleChanged();
296}
297
298/*!
299 \qmlproperty bool Qt.labs.platform::MenuItem::separator
300
301 This property holds whether the item is a separator line. The default value
302 is \c false.
303
304 \sa MenuSeparator
305*/
306bool QQuickLabsPlatformMenuItem::isSeparator() const
307{
308 return m_separator;
309}
310
311void QQuickLabsPlatformMenuItem::setSeparator(bool separator)
312{
313 if (m_separator == separator)
314 return;
315
316 m_separator = separator;
317 sync();
318 emit separatorChanged();
319}
320
321/*!
322 \qmlproperty bool Qt.labs.platform::MenuItem::checkable
323
324 This property holds whether the item is checkable.
325
326 A checkable menu item has an on/off state. For example, in a word processor,
327 a "Bold" menu item may be either on or off. A menu item that is not checkable
328 is a command item that is simply executed, e.g. file save.
329
330 The default value is \c false.
331
332 \sa checked, MenuItemGroup
333*/
334bool QQuickLabsPlatformMenuItem::isCheckable() const
335{
336 return m_checkable;
337}
338
339void QQuickLabsPlatformMenuItem::setCheckable(bool checkable)
340{
341 if (m_checkable == checkable)
342 return;
343
344 m_checkable = checkable;
345 sync();
346 emit checkableChanged();
347}
348
349/*!
350 \qmlproperty bool Qt.labs.platform::MenuItem::checked
351
352 This property holds whether the item is checked (on) or unchecked (off).
353 The default value is \c false.
354
355 \sa checkable, MenuItemGroup
356*/
357bool QQuickLabsPlatformMenuItem::isChecked() const
358{
359 return m_checked;
360}
361
362void QQuickLabsPlatformMenuItem::setChecked(bool checked)
363{
364 if (m_checked == checked)
365 return;
366
367 if (checked && !m_checkable)
368 setCheckable(true);
369
370 m_checked = checked;
371 sync();
372 emit checkedChanged();
373}
374
375/*!
376 \qmlproperty enumeration Qt.labs.platform::MenuItem::role
377
378 This property holds the role of the item. The role determines whether
379 the item should be placed into the application menu on macOS.
380
381 Available values:
382 \value MenuItem.NoRole The item should not be put into the application menu
383 \value MenuItem.TextHeuristicRole The item should be put in the application menu based on the action's text (default)
384 \value MenuItem.ApplicationSpecificRole The item should be put in the application menu with an application-specific role
385 \value MenuItem.AboutQtRole The item handles the "About Qt" menu item.
386 \value MenuItem.AboutRole The item should be placed where the "About" menu item is in the application menu. The text of
387 the menu item will be set to "About <application name>". The application name is fetched from the
388 \c{Info.plist} file in the application's bundle (See \l{Qt for macOS - Deployment}).
389 \value MenuItem.PreferencesRole The item should be placed where the "Preferences..." menu item is in the application menu.
390 \value MenuItem.QuitRole The item should be placed where the Quit menu item is in the application menu.
391
392 Specifying the role only has effect on items that are in the immediate
393 menus of a menubar, not in the submenus of those menus. For example, if
394 you have a "File" menu in your menubar and the "File" menu has a submenu,
395 specifying a role for the items in that submenu has no effect. They will
396 never be moved to the application menu.
397*/
398QPlatformMenuItem::MenuRole QQuickLabsPlatformMenuItem::role() const
399{
400 return m_role;
401}
402
403void QQuickLabsPlatformMenuItem::setRole(QPlatformMenuItem::MenuRole role)
404{
405 if (m_role == role)
406 return;
407
408 m_role = role;
409 sync();
410 emit roleChanged();
411}
412
413/*!
414 \qmlproperty string Qt.labs.platform::MenuItem::text
415
416 This property holds the menu item's text.
417*/
418QString QQuickLabsPlatformMenuItem::text() const
419{
420 return m_text;
421}
422
423void QQuickLabsPlatformMenuItem::setText(const QString &text)
424{
425 if (m_text == text)
426 return;
427
428 m_text = text;
429 sync();
430 emit textChanged();
431}
432
433/*!
434 \qmlproperty keysequence Qt.labs.platform::MenuItem::shortcut
435
436 This property holds the menu item's shortcut.
437
438 The shortcut key sequence can be set to one of the
439 \l{QKeySequence::StandardKey}{standard keyboard shortcuts}, or it can be
440 specified by a string containing a sequence of up to four key presses
441 that are needed to \l{triggered}{trigger} the shortcut.
442
443 The default value is an empty key sequence.
444
445 \code
446 MenuItem {
447 shortcut: "Ctrl+E,Ctrl+W"
448 onTriggered: edit.wrapMode = TextEdit.Wrap
449 }
450 \endcode
451*/
452QVariant QQuickLabsPlatformMenuItem::shortcut() const
453{
454 return m_shortcut;
455}
456
457bool QQuickLabsPlatformMenuItem::event(QEvent *e)
458{
459#if QT_CONFIG(shortcut)
460 if (e->type() == QEvent::Shortcut) {
461 QShortcutEvent *se = static_cast<QShortcutEvent *>(e);
462 if (se->shortcutId() == m_shortcutId) {
463 activate();
464 return true;
465 }
466 }
467#endif
468 return QObject::event(event: e);
469}
470
471void QQuickLabsPlatformMenuItem::setShortcut(const QVariant& shortcut)
472{
473 if (m_shortcut == shortcut)
474 return;
475
476 removeShortcut();
477 m_shortcut = shortcut;
478 sync();
479 addShortcut();
480 emit shortcutChanged();
481}
482
483/*!
484 \qmlproperty font Qt.labs.platform::MenuItem::font
485
486 This property holds the menu item's font.
487
488 \sa text
489*/
490QFont QQuickLabsPlatformMenuItem::font() const
491{
492 return m_font;
493}
494
495void QQuickLabsPlatformMenuItem::setFont(const QFont& font)
496{
497 if (m_font == font)
498 return;
499
500 m_font = font;
501 sync();
502 emit fontChanged();
503}
504
505/*!
506 \since Qt.labs.platform 1.1 (Qt 5.12)
507 \qmlproperty url Qt.labs.platform::MenuItem::icon.source
508 \qmlproperty string Qt.labs.platform::MenuItem::icon.name
509 \qmlproperty bool Qt.labs.platform::MenuItem::icon.mask
510
511 This property holds the menu item's icon.
512
513 \code
514 MenuItem {
515 icon.mask: true
516 icon.name: "edit-undo"
517 icon.source: "qrc:/images/undo.png"
518 }
519 \endcode
520
521 \sa QIcon::fromTheme()
522*/
523QQuickLabsPlatformIcon QQuickLabsPlatformMenuItem::icon() const
524{
525 if (!m_iconLoader)
526 return QQuickLabsPlatformIcon();
527
528 return m_iconLoader->icon();
529}
530
531void QQuickLabsPlatformMenuItem::setIcon(const QQuickLabsPlatformIcon &icon)
532{
533 if (iconLoader()->icon() == icon)
534 return;
535
536 iconLoader()->setIcon(icon);
537 emit iconChanged();
538}
539
540/*!
541 \qmlmethod void Qt.labs.platform::MenuItem::toggle()
542
543 Toggles the \l checked state to its opposite state.
544*/
545void QQuickLabsPlatformMenuItem::toggle()
546{
547 if (m_checkable)
548 setChecked(!m_checked);
549}
550
551void QQuickLabsPlatformMenuItem::classBegin()
552{
553}
554
555void QQuickLabsPlatformMenuItem::componentComplete()
556{
557 if (m_iconLoader)
558 m_iconLoader->setEnabled(true);
559 m_complete = true;
560 sync();
561}
562
563QQuickLabsPlatformIconLoader *QQuickLabsPlatformMenuItem::iconLoader() const
564{
565 if (!m_iconLoader) {
566 QQuickLabsPlatformMenuItem *that = const_cast<QQuickLabsPlatformMenuItem *>(this);
567 static int slot = staticMetaObject.indexOfSlot(slot: "updateIcon()");
568 m_iconLoader = new QQuickLabsPlatformIconLoader(slot, that);
569 m_iconLoader->setEnabled(m_complete);
570 }
571 return m_iconLoader;
572}
573
574void QQuickLabsPlatformMenuItem::activate()
575{
576 toggle();
577 emit triggered();
578}
579
580void QQuickLabsPlatformMenuItem::updateIcon()
581{
582 sync();
583}
584
585void QQuickLabsPlatformMenuItem::addShortcut()
586{
587#if QT_CONFIG(shortcut)
588 QKeySequence sequence;
589 if (m_shortcut.metaType().id() == QMetaType::Int)
590 sequence = QKeySequence(static_cast<QKeySequence::StandardKey>(m_shortcut.toInt()));
591 else if (m_shortcut.metaType().id() == QMetaType::QKeySequence)
592 sequence = m_shortcut.value<QKeySequence>();
593 else
594 sequence = QKeySequence::fromString(str: m_shortcut.toString());
595 if (!sequence.isEmpty() && m_enabled) {
596 m_shortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(owner: this, key: sequence,
597 context: Qt::WindowShortcut, matcher: QQuickShortcutContext::matcher);
598 } else {
599 m_shortcutId = -1;
600 }
601#endif
602}
603
604void QQuickLabsPlatformMenuItem::removeShortcut()
605{
606#if QT_CONFIG(shortcut)
607 if (m_shortcutId == -1)
608 return;
609
610 QKeySequence sequence;
611 if (m_shortcut.metaType().id() == QMetaType::Int)
612 sequence = QKeySequence(static_cast<QKeySequence::StandardKey>(m_shortcut.toInt()));
613 else if (m_shortcut.metaType().id() == QMetaType::QKeySequence)
614 sequence = m_shortcut.value<QKeySequence>();
615 else
616 sequence = QKeySequence::fromString(str: m_shortcut.toString());
617 QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(id: m_shortcutId, owner: this, key: sequence);
618#endif
619}
620
621QT_END_NAMESPACE
622
623#include "moc_qquicklabsplatformmenuitem_p.cpp"
624

source code of qtdeclarative/src/labs/platform/qquicklabsplatformmenuitem.cpp