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 "qquickaction_p.h" |
5 | #include "qquickaction_p_p.h" |
6 | #include "qquickactiongroup_p.h" |
7 | #include "qquickshortcutcontext_p_p.h" |
8 | |
9 | #include <QtGui/qevent.h> |
10 | #if QT_CONFIG(shortcut) |
11 | # include <QtGui/private/qshortcutmap_p.h> |
12 | #endif |
13 | #include <QtGui/private/qguiapplication_p.h> |
14 | #include <QtQuick/private/qquickitem_p.h> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | /*! |
19 | \qmltype Action |
20 | \inherits QtObject |
21 | //! \instantiates QQuickAction |
22 | \inqmlmodule QtQuick.Controls |
23 | \since 5.10 |
24 | \ingroup utilities |
25 | \brief Abstract user interface action. |
26 | |
27 | Action represents an abstract user interface action that can have shortcuts |
28 | and can be assigned to menu items and toolbar buttons. |
29 | |
30 | Actions may contain \l text, an \l icon, and a \l shortcut. Actions are normally |
31 | \l triggered by the user via menu items, toolbar buttons, or keyboard shortcuts. |
32 | A \l checkable Action toggles its \l checked state when triggered. |
33 | |
34 | \snippet qtquickcontrols-action.qml action |
35 | |
36 | Action is commonly used to implement application commands that can be invoked |
37 | via menu items, toolbar buttons, and keyboard shortcuts. Since the user expects |
38 | the commands to be performed in the same way, regardless of the user interface |
39 | used, it is useful to represent the commands as shareable actions. |
40 | |
41 | Action can be also used to separate the logic and the visual presentation. For |
42 | example, when declaring buttons and menu items in \c .ui.qml files, actions can |
43 | be declared elsewhere and assigned from the outside. |
44 | |
45 | \snippet qtquickcontrols-action.qml toolbutton |
46 | |
47 | When an action is paired with buttons and menu items, the \c enabled, \c checkable, |
48 | and \c checked states are synced automatically. For example, in a word processor, |
49 | if the user clicks a "Bold" toolbar button, the "Bold" menu item will automatically |
50 | be checked. Buttons and menu items get their \c text and \c icon from the action by |
51 | default. An action-specific \c text or \c icon can be overridden for a specific |
52 | control by specifying \c text or \c icon directly on the control. |
53 | |
54 | \snippet qtquickcontrols-action.qml menuitem |
55 | |
56 | Since Action presents a user interface action, it is intended to be assigned to |
57 | a \l MenuItem, \l ToolButton, or any other control that inherits \l AbstractButton. |
58 | For keyboard shortcuts, the simpler \l Shortcut type is more appropriate. |
59 | |
60 | \sa MenuItem, ToolButton, Shortcut |
61 | */ |
62 | |
63 | /*! |
64 | \qmlsignal QtQuick.Controls::Action::toggled(QtObject source) |
65 | |
66 | This signal is emitted when the action is toggled. The \a source argument |
67 | identifies the object that toggled the action. |
68 | |
69 | For example, if the action is assigned to a menu item and a toolbar button, the |
70 | action is toggled when the control is toggled, the shortcut is activated, or |
71 | when \l toggle() is called directly. |
72 | */ |
73 | |
74 | /*! |
75 | \qmlsignal QtQuick.Controls::Action::triggered(QtObject source) |
76 | |
77 | This signal is emitted when the action is triggered. The \a source argument |
78 | identifies the object that triggered the action. |
79 | |
80 | For example, if the action is assigned to a menu item and a toolbar button, the |
81 | action is triggered when the control is clicked, the shortcut is activated, or |
82 | when \l trigger() is called directly. |
83 | */ |
84 | |
85 | #if QT_CONFIG(shortcut) |
86 | static QKeySequence variantToKeySequence(const QVariant &var) |
87 | { |
88 | if (var.metaType().id() == QMetaType::Int) |
89 | return QKeySequence(static_cast<QKeySequence::StandardKey>(var.toInt())); |
90 | return QKeySequence::fromString(str: var.toString()); |
91 | } |
92 | |
93 | QQuickActionPrivate::ShortcutEntry::ShortcutEntry(QObject *target) |
94 | : m_target(target) |
95 | { |
96 | } |
97 | |
98 | QQuickActionPrivate::ShortcutEntry::~ShortcutEntry() |
99 | { |
100 | ungrab(); |
101 | } |
102 | |
103 | QObject *QQuickActionPrivate::ShortcutEntry::target() const |
104 | { |
105 | return m_target; |
106 | } |
107 | |
108 | int QQuickActionPrivate::ShortcutEntry::shortcutId() const |
109 | { |
110 | return m_shortcutId; |
111 | } |
112 | |
113 | void QQuickActionPrivate::ShortcutEntry::grab(const QKeySequence &shortcut, bool enabled) |
114 | { |
115 | if (shortcut.isEmpty() || m_shortcutId) |
116 | return; |
117 | |
118 | Qt::ShortcutContext context = Qt::WindowShortcut; // TODO |
119 | m_shortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(owner: m_target, key: shortcut, context, matcher: QQuickShortcutContext::matcher); |
120 | |
121 | if (!enabled) |
122 | QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enable: false, id: m_shortcutId, owner: m_target); |
123 | } |
124 | |
125 | void QQuickActionPrivate::ShortcutEntry::ungrab() |
126 | { |
127 | if (!m_shortcutId) |
128 | return; |
129 | |
130 | QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(id: m_shortcutId, owner: m_target); |
131 | m_shortcutId = 0; |
132 | } |
133 | |
134 | void QQuickActionPrivate::ShortcutEntry::setEnabled(bool enabled) |
135 | { |
136 | if (!m_shortcutId) |
137 | return; |
138 | |
139 | QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enable: enabled, id: m_shortcutId, owner: m_target); |
140 | } |
141 | |
142 | QVariant QQuickActionPrivate::shortcut() const |
143 | { |
144 | return vshortcut; |
145 | } |
146 | |
147 | void QQuickActionPrivate::setShortcut(const QVariant &var) |
148 | { |
149 | Q_Q(QQuickAction); |
150 | if (vshortcut == var) |
151 | return; |
152 | |
153 | defaultShortcutEntry->ungrab(); |
154 | for (QQuickActionPrivate::ShortcutEntry *entry : std::as_const(t&: shortcutEntries)) |
155 | entry->ungrab(); |
156 | |
157 | vshortcut = var; |
158 | keySequence = variantToKeySequence(var); |
159 | |
160 | defaultShortcutEntry->grab(shortcut: keySequence, enabled); |
161 | for (QQuickActionPrivate::ShortcutEntry *entry : std::as_const(t&: shortcutEntries)) |
162 | entry->grab(shortcut: keySequence, enabled); |
163 | |
164 | emit q->shortcutChanged(shortcut: keySequence); |
165 | } |
166 | #endif // QT_CONFIG(shortcut) |
167 | |
168 | void QQuickActionPrivate::setEnabled(bool enable) |
169 | { |
170 | Q_Q(QQuickAction); |
171 | if (enabled == enable) |
172 | return; |
173 | |
174 | enabled = enable; |
175 | |
176 | #if QT_CONFIG(shortcut) |
177 | defaultShortcutEntry->setEnabled(enable); |
178 | for (QQuickActionPrivate::ShortcutEntry *entry : std::as_const(t&: shortcutEntries)) |
179 | entry->setEnabled(enable); |
180 | #endif |
181 | |
182 | emit q->enabledChanged(enabled: enable); |
183 | } |
184 | |
185 | bool QQuickActionPrivate::watchItem(QQuickItem *item) |
186 | { |
187 | Q_Q(QQuickAction); |
188 | if (!item) |
189 | return false; |
190 | |
191 | item->installEventFilter(filterObj: q); |
192 | QQuickItemPrivate::get(item)->addItemChangeListener(listener: this, types: QQuickItemPrivate::Visibility | QQuickItemPrivate::Destroyed); |
193 | return true; |
194 | } |
195 | |
196 | bool QQuickActionPrivate::unwatchItem(QQuickItem *item) |
197 | { |
198 | Q_Q(QQuickAction); |
199 | if (!item) |
200 | return false; |
201 | |
202 | item->removeEventFilter(obj: q); |
203 | QQuickItemPrivate::get(item)->removeItemChangeListener(this, types: QQuickItemPrivate::Visibility | QQuickItemPrivate::Destroyed); |
204 | return true; |
205 | } |
206 | |
207 | void QQuickActionPrivate::registerItem(QQuickItem *item) |
208 | { |
209 | if (!watchItem(item)) |
210 | return; |
211 | |
212 | #if QT_CONFIG(shortcut) |
213 | QQuickActionPrivate::ShortcutEntry *entry = new QQuickActionPrivate::ShortcutEntry(item); |
214 | if (item->isVisible()) |
215 | entry->grab(shortcut: keySequence, enabled); |
216 | shortcutEntries += entry; |
217 | |
218 | updateDefaultShortcutEntry(); |
219 | #endif |
220 | } |
221 | |
222 | void QQuickActionPrivate::unregisterItem(QQuickItem *item) |
223 | { |
224 | #if QT_CONFIG(shortcut) |
225 | QQuickActionPrivate::ShortcutEntry *entry = findShortcutEntry(target: item); |
226 | if (!entry || !unwatchItem(item)) |
227 | return; |
228 | |
229 | shortcutEntries.removeOne(t: entry); |
230 | delete entry; |
231 | |
232 | updateDefaultShortcutEntry(); |
233 | #else |
234 | Q_UNUSED(item); |
235 | #endif |
236 | } |
237 | |
238 | void QQuickActionPrivate::itemVisibilityChanged(QQuickItem *item) |
239 | { |
240 | #if QT_CONFIG(shortcut) |
241 | QQuickActionPrivate::ShortcutEntry *entry = findShortcutEntry(target: item); |
242 | if (!entry) |
243 | return; |
244 | |
245 | if (item->isVisible()) |
246 | entry->grab(shortcut: keySequence, enabled); |
247 | else |
248 | entry->ungrab(); |
249 | |
250 | updateDefaultShortcutEntry(); |
251 | #else |
252 | Q_UNUSED(item); |
253 | #endif |
254 | } |
255 | |
256 | void QQuickActionPrivate::itemDestroyed(QQuickItem *item) |
257 | { |
258 | unregisterItem(item); |
259 | } |
260 | |
261 | #if QT_CONFIG(shortcut) |
262 | bool QQuickActionPrivate::handleShortcutEvent(QObject *object, QShortcutEvent *event) |
263 | { |
264 | Q_Q(QQuickAction); |
265 | if (event->key() != keySequence) |
266 | return false; |
267 | |
268 | QQuickActionPrivate::ShortcutEntry *entry = findShortcutEntry(target: object); |
269 | if (!entry || event->shortcutId() != entry->shortcutId()) |
270 | return false; |
271 | |
272 | q->trigger(source: entry->target()); |
273 | return true; |
274 | } |
275 | |
276 | QQuickActionPrivate::ShortcutEntry *QQuickActionPrivate::findShortcutEntry(QObject *target) const |
277 | { |
278 | Q_Q(const QQuickAction); |
279 | if (target == q) |
280 | return defaultShortcutEntry; |
281 | for (QQuickActionPrivate::ShortcutEntry *entry : shortcutEntries) { |
282 | if (entry->target() == target) |
283 | return entry; |
284 | } |
285 | return nullptr; |
286 | } |
287 | |
288 | void QQuickActionPrivate::updateDefaultShortcutEntry() |
289 | { |
290 | bool hasActiveShortcutEntries = false; |
291 | for (QQuickActionPrivate::ShortcutEntry *entry : std::as_const(t&: shortcutEntries)) { |
292 | if (entry->shortcutId()) { |
293 | hasActiveShortcutEntries = true; |
294 | break; |
295 | } |
296 | } |
297 | |
298 | if (hasActiveShortcutEntries) |
299 | defaultShortcutEntry->ungrab(); |
300 | else if (!defaultShortcutEntry->shortcutId()) |
301 | defaultShortcutEntry->grab(shortcut: keySequence, enabled); |
302 | } |
303 | #endif // QT_CONFIG(shortcut) |
304 | |
305 | QQuickAction::QQuickAction(QObject *parent) |
306 | : QObject(*(new QQuickActionPrivate), parent) |
307 | { |
308 | #if QT_CONFIG(shortcut) |
309 | Q_D(QQuickAction); |
310 | d->defaultShortcutEntry = new QQuickActionPrivate::ShortcutEntry(this); |
311 | #endif |
312 | } |
313 | |
314 | QQuickAction::~QQuickAction() |
315 | { |
316 | Q_D(QQuickAction); |
317 | if (d->group) |
318 | d->group->removeAction(action: this); |
319 | |
320 | #if QT_CONFIG(shortcut) |
321 | for (QQuickActionPrivate::ShortcutEntry *entry : std::as_const(t&: d->shortcutEntries)) |
322 | d->unwatchItem(item: qobject_cast<QQuickItem *>(o: entry->target())); |
323 | |
324 | qDeleteAll(c: d->shortcutEntries); |
325 | delete d->defaultShortcutEntry; |
326 | #endif |
327 | } |
328 | |
329 | /*! |
330 | \qmlproperty string QtQuick.Controls::Action::text |
331 | |
332 | This property holds a textual description of the action. |
333 | */ |
334 | QString QQuickAction::text() const |
335 | { |
336 | Q_D(const QQuickAction); |
337 | return d->text; |
338 | } |
339 | |
340 | void QQuickAction::setText(const QString &text) |
341 | { |
342 | Q_D(QQuickAction); |
343 | if (d->text == text) |
344 | return; |
345 | |
346 | d->text = text; |
347 | emit textChanged(text); |
348 | } |
349 | |
350 | /*! |
351 | \qmlproperty string QtQuick.Controls::Action::icon.name |
352 | \qmlproperty url QtQuick.Controls::Action::icon.source |
353 | \qmlproperty int QtQuick.Controls::Action::icon.width |
354 | \qmlproperty int QtQuick.Controls::Action::icon.height |
355 | \qmlproperty color QtQuick.Controls::Action::icon.color |
356 | \qmlproperty bool QtQuick.Controls::Action::icon.cache |
357 | |
358 | \include qquickicon.qdocinc grouped-properties |
359 | */ |
360 | QQuickIcon QQuickAction::icon() const |
361 | { |
362 | Q_D(const QQuickAction); |
363 | return d->icon; |
364 | } |
365 | |
366 | void QQuickAction::setIcon(const QQuickIcon &icon) |
367 | { |
368 | Q_D(QQuickAction); |
369 | if (d->icon == icon) |
370 | return; |
371 | |
372 | d->icon = icon; |
373 | d->icon.ensureRelativeSourceResolved(owner: this); |
374 | emit iconChanged(icon); |
375 | } |
376 | |
377 | /*! |
378 | \qmlproperty bool QtQuick.Controls::Action::enabled |
379 | |
380 | This property holds whether the action is enabled. The default value is \c true. |
381 | */ |
382 | bool QQuickAction::isEnabled() const |
383 | { |
384 | Q_D(const QQuickAction); |
385 | return d->enabled && (!d->group || d->group->isEnabled()); |
386 | } |
387 | |
388 | void QQuickAction::setEnabled(bool enabled) |
389 | { |
390 | Q_D(QQuickAction); |
391 | d->explicitEnabled = true; |
392 | d->setEnabled(enabled); |
393 | } |
394 | |
395 | void QQuickAction::resetEnabled() |
396 | { |
397 | Q_D(QQuickAction); |
398 | if (!d->explicitEnabled) |
399 | return; |
400 | |
401 | d->explicitEnabled = false; |
402 | d->setEnabled(true); |
403 | } |
404 | |
405 | /*! |
406 | \qmlproperty bool QtQuick.Controls::Action::checked |
407 | |
408 | This property holds whether the action is checked. |
409 | |
410 | \sa checkable |
411 | */ |
412 | bool QQuickAction::isChecked() const |
413 | { |
414 | Q_D(const QQuickAction); |
415 | return d->checked; |
416 | } |
417 | |
418 | void QQuickAction::setChecked(bool checked) |
419 | { |
420 | Q_D(QQuickAction); |
421 | if (d->checked == checked) |
422 | return; |
423 | |
424 | d->checked = checked; |
425 | emit checkedChanged(checked); |
426 | } |
427 | |
428 | /*! |
429 | \qmlproperty bool QtQuick.Controls::Action::checkable |
430 | |
431 | This property holds whether the action is checkable. The default value is \c false. |
432 | |
433 | A checkable action toggles between checked (on) and unchecked (off) when triggered. |
434 | |
435 | \sa checked |
436 | */ |
437 | bool QQuickAction::isCheckable() const |
438 | { |
439 | Q_D(const QQuickAction); |
440 | return d->checkable; |
441 | } |
442 | |
443 | void QQuickAction::setCheckable(bool checkable) |
444 | { |
445 | Q_D(QQuickAction); |
446 | if (d->checkable == checkable) |
447 | return; |
448 | |
449 | d->checkable = checkable; |
450 | emit checkableChanged(checkable); |
451 | } |
452 | |
453 | #if QT_CONFIG(shortcut) |
454 | /*! |
455 | \qmlproperty keysequence QtQuick.Controls::Action::shortcut |
456 | |
457 | This property holds the action's shortcut. The key sequence can be set |
458 | to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts}, |
459 | or it can be described with a string containing a sequence of up to four |
460 | key presses that are needed to trigger the shortcut. |
461 | |
462 | \code |
463 | Action { |
464 | shortcut: "Ctrl+E,Ctrl+W" |
465 | onTriggered: edit.wrapMode = TextEdit.Wrap |
466 | } |
467 | \endcode |
468 | */ |
469 | QKeySequence QQuickAction::shortcut() const |
470 | { |
471 | Q_D(const QQuickAction); |
472 | return d->keySequence; |
473 | } |
474 | |
475 | void QQuickAction::setShortcut(const QKeySequence &shortcut) |
476 | { |
477 | Q_D(QQuickAction); |
478 | d->setShortcut(shortcut.toString()); |
479 | } |
480 | #endif // QT_CONFIG(shortcut) |
481 | |
482 | /*! |
483 | \qmlmethod void QtQuick.Controls::Action::toggle(QtObject source) |
484 | |
485 | Toggles the action and emits \l toggled() if enabled, with an optional \a source object defined. |
486 | */ |
487 | void QQuickAction::toggle(QObject *source) |
488 | { |
489 | Q_D(QQuickAction); |
490 | if (!d->enabled) |
491 | return; |
492 | |
493 | if (d->checkable) |
494 | setChecked(!d->checked); |
495 | |
496 | emit toggled(source); |
497 | } |
498 | |
499 | /*! |
500 | \qmlmethod void QtQuick.Controls::Action::trigger(QtObject source) |
501 | |
502 | Triggers the action and emits \l triggered() if enabled, with an optional \a source object defined. |
503 | */ |
504 | void QQuickAction::trigger(QObject *source) |
505 | { |
506 | Q_D(QQuickAction); |
507 | d->trigger(source, doToggle: true); |
508 | } |
509 | |
510 | void QQuickActionPrivate::trigger(QObject* source, bool doToggle) |
511 | { |
512 | Q_Q(QQuickAction); |
513 | if (!enabled) |
514 | return; |
515 | |
516 | QPointer<QObject> guard = q; |
517 | // the checked action of an exclusive group cannot be unchecked |
518 | if (checkable && (!checked || !group || !group->isExclusive() || group->checkedAction() != q)) { |
519 | if (doToggle) |
520 | q->toggle(source); |
521 | else |
522 | emit q->toggled(source); |
523 | } |
524 | |
525 | if (!guard.isNull()) |
526 | emit q->triggered(source); |
527 | } |
528 | |
529 | bool QQuickAction::event(QEvent *event) |
530 | { |
531 | #if QT_CONFIG(shortcut) |
532 | Q_D(QQuickAction); |
533 | if (event->type() == QEvent::Shortcut) |
534 | return d->handleShortcutEvent(object: this, event: static_cast<QShortcutEvent *>(event)); |
535 | #endif |
536 | return QObject::event(event); |
537 | } |
538 | |
539 | bool QQuickAction::eventFilter(QObject *object, QEvent *event) |
540 | { |
541 | #if QT_CONFIG(shortcut) |
542 | Q_D(QQuickAction); |
543 | if (event->type() == QEvent::Shortcut) |
544 | return d->handleShortcutEvent(object, event: static_cast<QShortcutEvent *>(event)); |
545 | #else |
546 | Q_UNUSED(object); |
547 | Q_UNUSED(event); |
548 | #endif |
549 | return false; |
550 | } |
551 | |
552 | QT_END_NAMESPACE |
553 | |
554 | #include "moc_qquickaction_p.cpp" |
555 | |