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
16QT_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)
86static 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
93QQuickActionPrivate::ShortcutEntry::ShortcutEntry(QObject *target)
94 : m_target(target)
95{
96}
97
98QQuickActionPrivate::ShortcutEntry::~ShortcutEntry()
99{
100 ungrab();
101}
102
103QObject *QQuickActionPrivate::ShortcutEntry::target() const
104{
105 return m_target;
106}
107
108int QQuickActionPrivate::ShortcutEntry::shortcutId() const
109{
110 return m_shortcutId;
111}
112
113void 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
125void 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
134void 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
142QVariant QQuickActionPrivate::shortcut() const
143{
144 return vshortcut;
145}
146
147void 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
168void 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
185bool 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
196bool 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
207void 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
222void 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
238void 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
256void QQuickActionPrivate::itemDestroyed(QQuickItem *item)
257{
258 unregisterItem(item);
259}
260
261#if QT_CONFIG(shortcut)
262bool 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
276QQuickActionPrivate::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
288void 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
305QQuickAction::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
314QQuickAction::~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*/
334QString QQuickAction::text() const
335{
336 Q_D(const QQuickAction);
337 return d->text;
338}
339
340void 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*/
360QQuickIcon QQuickAction::icon() const
361{
362 Q_D(const QQuickAction);
363 return d->icon;
364}
365
366void 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*/
382bool QQuickAction::isEnabled() const
383{
384 Q_D(const QQuickAction);
385 return d->enabled && (!d->group || d->group->isEnabled());
386}
387
388void QQuickAction::setEnabled(bool enabled)
389{
390 Q_D(QQuickAction);
391 d->explicitEnabled = true;
392 d->setEnabled(enabled);
393}
394
395void 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*/
412bool QQuickAction::isChecked() const
413{
414 Q_D(const QQuickAction);
415 return d->checked;
416}
417
418void 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*/
437bool QQuickAction::isCheckable() const
438{
439 Q_D(const QQuickAction);
440 return d->checkable;
441}
442
443void 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*/
469QKeySequence QQuickAction::shortcut() const
470{
471 Q_D(const QQuickAction);
472 return d->keySequence;
473}
474
475void 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*/
487void 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*/
504void QQuickAction::trigger(QObject *source)
505{
506 Q_D(QQuickAction);
507 d->trigger(source, doToggle: true);
508}
509
510void 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
529bool 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
539bool 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
552QT_END_NAMESPACE
553
554#include "moc_qquickaction_p.cpp"
555

source code of qtdeclarative/src/quicktemplates/qquickaction.cpp