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

source code of qtquickcontrols2/src/imports/platform/qquickplatformmenuitem.cpp