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