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 | |
50 | QT_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 | |
101 | 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 | |
118 | 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 | |
140 | QPlatformMenuItem *QQuickPlatformMenuItem::handle() const |
141 | { |
142 | return m_handle; |
143 | } |
144 | |
145 | QPlatformMenuItem *QQuickPlatformMenuItem::() |
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 | |
165 | void QQuickPlatformMenuItem::() |
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 | */ |
207 | QQuickPlatformMenu *QQuickPlatformMenuItem::() const |
208 | { |
209 | return m_menu; |
210 | } |
211 | |
212 | void QQuickPlatformMenuItem::(QQuickPlatformMenu *) |
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 | */ |
228 | QQuickPlatformMenu *QQuickPlatformMenuItem::() const |
229 | { |
230 | return m_subMenu; |
231 | } |
232 | |
233 | void QQuickPlatformMenuItem::(QQuickPlatformMenu *) |
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 | */ |
249 | QQuickPlatformMenuItemGroup *QQuickPlatformMenuItem::() const |
250 | { |
251 | return m_group; |
252 | } |
253 | |
254 | void QQuickPlatformMenuItem::(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 | */ |
286 | bool QQuickPlatformMenuItem::() const |
287 | { |
288 | return m_enabled && (!m_group || m_group->isEnabled()); |
289 | } |
290 | |
291 | void QQuickPlatformMenuItem::(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 | */ |
308 | bool QQuickPlatformMenuItem::() const |
309 | { |
310 | return m_visible && (!m_group || m_group->isVisible()); |
311 | } |
312 | |
313 | void QQuickPlatformMenuItem::(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 | */ |
333 | bool QQuickPlatformMenuItem::() const |
334 | { |
335 | return m_separator; |
336 | } |
337 | |
338 | void QQuickPlatformMenuItem::(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 | */ |
361 | bool QQuickPlatformMenuItem::() const |
362 | { |
363 | return m_checkable; |
364 | } |
365 | |
366 | void QQuickPlatformMenuItem::(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 | */ |
384 | bool QQuickPlatformMenuItem::() const |
385 | { |
386 | return m_checked; |
387 | } |
388 | |
389 | void QQuickPlatformMenuItem::(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 | */ |
425 | QPlatformMenuItem::MenuRole QQuickPlatformMenuItem::() const |
426 | { |
427 | return m_role; |
428 | } |
429 | |
430 | void QQuickPlatformMenuItem::(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 | */ |
445 | QString QQuickPlatformMenuItem::() const |
446 | { |
447 | return m_text; |
448 | } |
449 | |
450 | void QQuickPlatformMenuItem::(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 | */ |
464 | QUrl QQuickPlatformMenuItem::() const |
465 | { |
466 | return icon().source(); |
467 | } |
468 | |
469 | void QQuickPlatformMenuItem::(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 | */ |
484 | QString QQuickPlatformMenuItem::() const |
485 | { |
486 | return icon().name(); |
487 | } |
488 | |
489 | void QQuickPlatformMenuItem::(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 | */ |
519 | QVariant QQuickPlatformMenuItem::() const |
520 | { |
521 | return m_shortcut; |
522 | } |
523 | |
524 | bool QQuickPlatformMenuItem::(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 | |
538 | void QQuickPlatformMenuItem::(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 | */ |
578 | QFont QQuickPlatformMenuItem::() const |
579 | { |
580 | return m_font; |
581 | } |
582 | |
583 | void QQuickPlatformMenuItem::(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 | */ |
611 | QQuickPlatformIcon QQuickPlatformMenuItem::() const |
612 | { |
613 | if (!m_iconLoader) |
614 | return QQuickPlatformIcon(); |
615 | |
616 | return m_iconLoader->icon(); |
617 | } |
618 | |
619 | void QQuickPlatformMenuItem::(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 | */ |
633 | void QQuickPlatformMenuItem::() |
634 | { |
635 | if (m_checkable) |
636 | setChecked(!m_checked); |
637 | } |
638 | |
639 | void QQuickPlatformMenuItem::() |
640 | { |
641 | } |
642 | |
643 | void QQuickPlatformMenuItem::() |
644 | { |
645 | if (m_handle && m_iconLoader) |
646 | m_iconLoader->setEnabled(true); |
647 | m_complete = true; |
648 | sync(); |
649 | } |
650 | |
651 | QQuickPlatformIconLoader *QQuickPlatformMenuItem::() 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 | |
662 | void QQuickPlatformMenuItem::() |
663 | { |
664 | toggle(); |
665 | emit triggered(); |
666 | } |
667 | |
668 | void QQuickPlatformMenuItem::() |
669 | { |
670 | if (!m_handle || !m_iconLoader) |
671 | return; |
672 | |
673 | m_handle->setIcon(m_iconLoader->toQIcon()); |
674 | sync(); |
675 | } |
676 | |
677 | QT_END_NAMESPACE |
678 | |