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 | |
19 | QT_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 | |
70 | 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 | |
87 | 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 | |
100 | QPlatformMenuItem *QQuickLabsPlatformMenuItem::handle() const |
101 | { |
102 | return m_handle; |
103 | } |
104 | |
105 | QPlatformMenuItem *QQuickLabsPlatformMenuItem::() |
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 | |
125 | void QQuickLabsPlatformMenuItem::() |
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 | */ |
173 | QQuickLabsPlatformMenu *QQuickLabsPlatformMenuItem::() const |
174 | { |
175 | return m_menu; |
176 | } |
177 | |
178 | void QQuickLabsPlatformMenuItem::(QQuickLabsPlatformMenu *) |
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 | */ |
194 | QQuickLabsPlatformMenu *QQuickLabsPlatformMenuItem::() const |
195 | { |
196 | return m_subMenu; |
197 | } |
198 | |
199 | void QQuickLabsPlatformMenuItem::(QQuickLabsPlatformMenu *) |
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 | */ |
215 | QQuickLabsPlatformMenuItemGroup *QQuickLabsPlatformMenuItem::() const |
216 | { |
217 | return m_group; |
218 | } |
219 | |
220 | void QQuickLabsPlatformMenuItem::(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 | */ |
252 | bool QQuickLabsPlatformMenuItem::() const |
253 | { |
254 | return m_enabled && (!m_group || m_group->isEnabled()); |
255 | } |
256 | |
257 | void QQuickLabsPlatformMenuItem::(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 | */ |
281 | bool QQuickLabsPlatformMenuItem::() const |
282 | { |
283 | return m_visible && (!m_group || m_group->isVisible()); |
284 | } |
285 | |
286 | void QQuickLabsPlatformMenuItem::(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 | */ |
306 | bool QQuickLabsPlatformMenuItem::() const |
307 | { |
308 | return m_separator; |
309 | } |
310 | |
311 | void QQuickLabsPlatformMenuItem::(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 | */ |
334 | bool QQuickLabsPlatformMenuItem::() const |
335 | { |
336 | return m_checkable; |
337 | } |
338 | |
339 | void QQuickLabsPlatformMenuItem::(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 | */ |
357 | bool QQuickLabsPlatformMenuItem::() const |
358 | { |
359 | return m_checked; |
360 | } |
361 | |
362 | void QQuickLabsPlatformMenuItem::(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 | */ |
398 | QPlatformMenuItem::MenuRole QQuickLabsPlatformMenuItem::() const |
399 | { |
400 | return m_role; |
401 | } |
402 | |
403 | void QQuickLabsPlatformMenuItem::(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 | */ |
418 | QString QQuickLabsPlatformMenuItem::() const |
419 | { |
420 | return m_text; |
421 | } |
422 | |
423 | void QQuickLabsPlatformMenuItem::(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 | */ |
452 | QVariant QQuickLabsPlatformMenuItem::() const |
453 | { |
454 | return m_shortcut; |
455 | } |
456 | |
457 | bool QQuickLabsPlatformMenuItem::(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 | |
471 | void QQuickLabsPlatformMenuItem::(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 | */ |
490 | QFont QQuickLabsPlatformMenuItem::() const |
491 | { |
492 | return m_font; |
493 | } |
494 | |
495 | void QQuickLabsPlatformMenuItem::(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 | */ |
523 | QQuickLabsPlatformIcon QQuickLabsPlatformMenuItem::() const |
524 | { |
525 | if (!m_iconLoader) |
526 | return QQuickLabsPlatformIcon(); |
527 | |
528 | return m_iconLoader->icon(); |
529 | } |
530 | |
531 | void QQuickLabsPlatformMenuItem::(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 | */ |
545 | void QQuickLabsPlatformMenuItem::() |
546 | { |
547 | if (m_checkable) |
548 | setChecked(!m_checked); |
549 | } |
550 | |
551 | void QQuickLabsPlatformMenuItem::() |
552 | { |
553 | } |
554 | |
555 | void QQuickLabsPlatformMenuItem::() |
556 | { |
557 | if (m_iconLoader) |
558 | m_iconLoader->setEnabled(true); |
559 | m_complete = true; |
560 | sync(); |
561 | } |
562 | |
563 | QQuickLabsPlatformIconLoader *QQuickLabsPlatformMenuItem::() 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 | |
574 | void QQuickLabsPlatformMenuItem::() |
575 | { |
576 | toggle(); |
577 | emit triggered(); |
578 | } |
579 | |
580 | void QQuickLabsPlatformMenuItem::() |
581 | { |
582 | sync(); |
583 | } |
584 | |
585 | void QQuickLabsPlatformMenuItem::() |
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 | |
604 | void QQuickLabsPlatformMenuItem::() |
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 | |
621 | QT_END_NAMESPACE |
622 | |
623 | #include "moc_qquicklabsplatformmenuitem_p.cpp" |
624 | |