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 else if (var.metaType().id() == QMetaType::QKeySequence)
98 return var.value<QKeySequence>();
99 return QKeySequence::fromString(var.toString());
100}
101
102QQuickActionPrivate::ShortcutEntry::ShortcutEntry(QObject *target)
103 : m_target(target)
104{
105}
106
107QQuickActionPrivate::ShortcutEntry::~ShortcutEntry()
108{
109 ungrab();
110}
111
112QObject *QQuickActionPrivate::ShortcutEntry::target() const
113{
114 return m_target;
115}
116
117int QQuickActionPrivate::ShortcutEntry::shortcutId() const
118{
119 return m_shortcutId;
120}
121
122void QQuickActionPrivate::ShortcutEntry::grab(const QKeySequence &shortcut, bool enabled)
123{
124 qCDebug(lcShortcutEntry) << "- grab called with" << shortcut << "enabled" << enabled
125 << "for" << m_target;
126 if (shortcut.isEmpty() || m_shortcutId)
127 return;
128
129 Qt::ShortcutContext context = Qt::WindowShortcut; // TODO
130 m_shortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(m_target, shortcut, context, QQuickShortcutContext::matcher);
131
132 if (!enabled)
133 QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(false, m_shortcutId, m_target);
134}
135
136void QQuickActionPrivate::ShortcutEntry::ungrab()
137{
138 qCDebug(lcShortcutEntry).nospace() << "- ungrab called for " << m_target << ", m_shortcutId is "
139 << m_shortcutId;
140 if (!m_shortcutId)
141 return;
142
143 QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(m_shortcutId, m_target);
144 m_shortcutId = 0;
145}
146
147void QQuickActionPrivate::ShortcutEntry::setEnabled(bool enabled)
148{
149 if (!m_shortcutId)
150 return;
151
152 QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enabled, m_shortcutId, m_target);
153}
154
155QVariant QQuickActionPrivate::shortcut() const
156{
157 return vshortcut;
158}
159
160void QQuickActionPrivate::setShortcut(const QVariant &var)
161{
162 Q_Q(QQuickAction);
163 if (vshortcut == var)
164 return;
165
166 qCDebug(lcAction) << q << "setShortcut called with" << var;
167 defaultShortcutEntry->ungrab();
168 for (QQuickActionPrivate::ShortcutEntry *entry : std::as_const(shortcutEntries))
169 entry->ungrab();
170
171 vshortcut = var;
172 keySequence = variantToKeySequence(var);
173
174 defaultShortcutEntry->grab(keySequence, enabled);
175 for (QQuickActionPrivate::ShortcutEntry *entry : std::as_const(shortcutEntries))
176 entry->grab(keySequence, enabled);
177
178 emit q->shortcutChanged(keySequence);
179}
180#endif // QT_CONFIG(shortcut)
181
182void QQuickActionPrivate::setEnabled(bool enable)
183{
184 Q_Q(QQuickAction);
185 if (enabled == enable)
186 return;
187
188 enabled = enable;
189
190#if QT_CONFIG(shortcut)
191 defaultShortcutEntry->setEnabled(enable);
192 for (QQuickActionPrivate::ShortcutEntry *entry : std::as_const(shortcutEntries))
193 entry->setEnabled(enable);
194#endif
195
196 emit q->enabledChanged(enable);
197}
198
199bool QQuickActionPrivate::watchItem(QQuickItem *item)
200{
201 Q_Q(QQuickAction);
202 if (!item)
203 return false;
204
205 item->installEventFilter(q);
206 QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Visibility | QQuickItemPrivate::Destroyed);
207 return true;
208}
209
210bool QQuickActionPrivate::unwatchItem(QQuickItem *item)
211{
212 Q_Q(QQuickAction);
213 if (!item)
214 return false;
215
216 item->removeEventFilter(q);
217 QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Visibility | QQuickItemPrivate::Destroyed);
218 return true;
219}
220
221void QQuickActionPrivate::registerItem(QQuickItem *item)
222{
223 if (!watchItem(item))
224 return;
225
226#if QT_CONFIG(shortcut)
227 qCDebug(lcAction) << q_func() << "registerItem called with" << item;
228 QQuickActionPrivate::ShortcutEntry *entry = new QQuickActionPrivate::ShortcutEntry(item);
229 if (item->isVisible())
230 entry->grab(keySequence, enabled);
231 shortcutEntries += entry;
232
233 updateDefaultShortcutEntry();
234#endif
235}
236
237void QQuickActionPrivate::unregisterItem(QQuickItem *item)
238{
239#if QT_CONFIG(shortcut)
240 QQuickActionPrivate::ShortcutEntry *entry = findShortcutEntry(item);
241 if (!entry || !unwatchItem(item))
242 return;
243
244 qCDebug(lcAction) << q_func() << "unregisterItem called with" << item;
245 shortcutEntries.removeOne(entry);
246 delete entry;
247
248 updateDefaultShortcutEntry();
249#else
250 Q_UNUSED(item);
251#endif
252}
253
255{
256#if QT_CONFIG(shortcut)
257 QQuickActionPrivate::ShortcutEntry *entry = findShortcutEntry(item);
258 if (!entry)
259 return;
260
261 qCDebug(lcAction) << q_func() << "visibility of" << item << "changed to" << item->isVisible()
262 << "- grabbing/ungrabbing shortcut";
263 if (item->isVisible())
264 entry->grab(keySequence, enabled);
265 else
266 entry->ungrab();
267
268 updateDefaultShortcutEntry();
269#else
270 Q_UNUSED(item);
271#endif
272}
273
274void QQuickActionPrivate::itemDestroyed(QQuickItem *item)
275{
276 unregisterItem(item);
277}
278
279#if QT_CONFIG(shortcut)
280bool QQuickActionPrivate::handleShortcutEvent(QObject *object, QShortcutEvent *event)
281{
282 Q_Q(QQuickAction);
283 if (event->key() != keySequence)
284 return false;
285
286 QQuickActionPrivate::ShortcutEntry *entry = findShortcutEntry(object);
287 if (!entry || event->shortcutId() != entry->shortcutId())
288 return false;
289
290 q->trigger(entry->target());
291 return true;
292}
293
294QQuickActionPrivate::ShortcutEntry *QQuickActionPrivate::findShortcutEntry(QObject *target) const
295{
296 Q_Q(const QQuickAction);
297 if (target == q)
298 return defaultShortcutEntry;
299 for (QQuickActionPrivate::ShortcutEntry *entry : shortcutEntries) {
300 if (entry->target() == target)
301 return entry;
302 }
303 return nullptr;
304}
305
306void QQuickActionPrivate::updateDefaultShortcutEntry()
307{
308 bool hasActiveShortcutEntries = false;
309 for (QQuickActionPrivate::ShortcutEntry *entry : std::as_const(shortcutEntries)) {
310 if (entry->shortcutId()) {
311 hasActiveShortcutEntries = true;
312 break;
313 }
314 }
315
316 if (hasActiveShortcutEntries) {
317 // There is an item that is using us as its action; let it have the shortcut.
318 defaultShortcutEntry->ungrab();
319 } else if (!defaultShortcutEntry->shortcutId()) {
320 // There are no items that are using us as their action (or they aren't visible) and we
321 // haven't already grabbed the shortcut; grab it.
322 defaultShortcutEntry->grab(keySequence, enabled);
323 }
324}
325#endif // QT_CONFIG(shortcut)
326
327QQuickAction::QQuickAction(QObject *parent)
328 : QObject(*(new QQuickActionPrivate), parent)
329{
330#if QT_CONFIG(shortcut)
331 Q_D(QQuickAction);
332 d->defaultShortcutEntry = new QQuickActionPrivate::ShortcutEntry(this);
333#endif
334}
335
336QQuickAction::~QQuickAction()
337{
338 Q_D(QQuickAction);
339 qCDebug(lcAction) << "destroying" << this << d->text;
340 if (d->group)
341 d->group->removeAction(this);
342
343#if QT_CONFIG(shortcut)
344 for (QQuickActionPrivate::ShortcutEntry *entry : std::as_const(d->shortcutEntries))
345 d->unwatchItem(qobject_cast<QQuickItem *>(entry->target()));
346
347 qDeleteAll(d->shortcutEntries);
348 delete d->defaultShortcutEntry;
349#endif
350}
351
352/*!
353 \qmlproperty string QtQuick.Controls::Action::text
354
355 This property holds a textual description of the action.
356*/
357QString QQuickAction::text() const
358{
359 Q_D(const QQuickAction);
360 return d->text;
361}
362
363void QQuickAction::setText(const QString &text)
364{
365 Q_D(QQuickAction);
366 if (d->text == text)
367 return;
368
369 d->text = text;
370 emit textChanged(text);
371}
372
373/*!
374 \qmlproperty string QtQuick.Controls::Action::icon.name
375 \qmlproperty url QtQuick.Controls::Action::icon.source
376 \qmlproperty int QtQuick.Controls::Action::icon.width
377 \qmlproperty int QtQuick.Controls::Action::icon.height
378 \qmlproperty color QtQuick.Controls::Action::icon.color
379 \qmlproperty bool QtQuick.Controls::Action::icon.cache
380
381 \include qquickicon.qdocinc grouped-properties
382*/
383QQuickIcon QQuickAction::icon() const
384{
385 Q_D(const QQuickAction);
386 return d->icon;
387}
388
389void QQuickAction::setIcon(const QQuickIcon &icon)
390{
391 Q_D(QQuickAction);
392 // Similar to QQuickAbstractButtonPrivate::updateEffectiveIcon, we don't want to rely
393 // purely on QQuickIcon::operator==, because it doesn't account for the color being resolved.
394 // If we didn't check the resolve mask and the user set the color to transparent (the default),
395 // the resolveMask of d->icon wouldn't indicate that the color was resolved and iconChanged
396 // wouldn't be emitted, leading to the user's request being ignored.
397 const bool oldColorResolved = QQuickIconPrivate::isResolved(d->icon, QQuickIconPrivate::ColorResolved);
398 const bool newColorResolved = QQuickIconPrivate::isResolved(icon, QQuickIconPrivate::ColorResolved);
399 const bool unchanged = d->icon == icon && oldColorResolved && !newColorResolved;
400
401 d->icon = icon;
402
403 if (unchanged)
404 return;
405
406 d->icon.ensureRelativeSourceResolved(this);
407 emit iconChanged(icon);
408}
409
410/*!
411 \qmlproperty bool QtQuick.Controls::Action::enabled
412
413 This property holds whether the action is enabled. The default value is \c true.
414*/
415bool QQuickAction::isEnabled() const
416{
417 Q_D(const QQuickAction);
418 return d->enabled && (!d->group || d->group->isEnabled());
419}
420
421void QQuickAction::setEnabled(bool enabled)
422{
423 Q_D(QQuickAction);
424 d->explicitEnabled = true;
425 d->setEnabled(enabled);
426}
427
428void QQuickAction::resetEnabled()
429{
430 Q_D(QQuickAction);
431 if (!d->explicitEnabled)
432 return;
433
434 d->explicitEnabled = false;
435 d->setEnabled(true);
436}
437
438/*!
439 \qmlproperty bool QtQuick.Controls::Action::checked
440
441 This property holds whether the action is checked.
442
443 \sa checkable
444*/
445bool QQuickAction::isChecked() const
446{
447 Q_D(const QQuickAction);
448 return d->checked;
449}
450
451void QQuickAction::setChecked(bool checked)
452{
453 Q_D(QQuickAction);
454 if (d->checked == checked)
455 return;
456
457 d->checked = checked;
458 emit checkedChanged(checked);
459}
460
461/*!
462 \qmlproperty bool QtQuick.Controls::Action::checkable
463
464 This property holds whether the action is checkable. The default value is \c false.
465
466 A checkable action toggles between checked (on) and unchecked (off) when triggered.
467
468 \sa checked
469*/
470bool QQuickAction::isCheckable() const
471{
472 Q_D(const QQuickAction);
473 return d->checkable;
474}
475
476void QQuickAction::setCheckable(bool checkable)
477{
478 Q_D(QQuickAction);
479 if (d->checkable == checkable)
480 return;
481
482 d->checkable = checkable;
483 emit checkableChanged(checkable);
484}
485
486#if QT_CONFIG(shortcut)
487/*!
488 \qmlproperty keysequence QtQuick.Controls::Action::shortcut
489
490 This property holds the action's shortcut. The key sequence can be set to
491 one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts}, (for
492 example, \c StandardKey.Copy), or it can be described with a string
493 containing a sequence of up to four key presses that are needed to trigger
494 the shortcut.
495
496 \code
497 Action {
498 shortcut: "Ctrl+E,Ctrl+W"
499 onTriggered: edit.wrapMode = TextEdit.Wrap
500 }
501 \endcode
502*/
503QKeySequence QQuickAction::shortcut() const
504{
505 Q_D(const QQuickAction);
506 return d->keySequence;
507}
508
509void QQuickAction::setShortcut(const QKeySequence &shortcut)
510{
511 Q_D(QQuickAction);
512 d->setShortcut(shortcut.toString());
513}
514#endif // QT_CONFIG(shortcut)
515
516/*!
517 \qmlmethod void QtQuick.Controls::Action::toggle(QtObject source)
518
519 Toggles the action and emits \l toggled() if enabled, with an optional \a source object defined.
520*/
521void QQuickAction::toggle(QObject *source)
522{
523 Q_D(QQuickAction);
524 if (!d->enabled)
525 return;
526
527 if (d->checkable)
528 setChecked(!d->checked);
529
530 emit toggled(source);
531}
532
533/*!
534 \qmlmethod void QtQuick.Controls::Action::trigger(QtObject source)
535
536 Triggers the action and emits \l triggered() if enabled, with an optional \a source object defined.
537*/
538void QQuickAction::trigger(QObject *source)
539{
540 Q_D(QQuickAction);
541 d->trigger(source, true);
542}
543
544/*!
545 \internal
546
547 Returns \c true if \l {Action::}{triggered()} was emitted.
548*/
549bool QQuickActionPrivate::trigger(QObject* source, bool doToggle)
550{
551 Q_Q(QQuickAction);
552 if (!enabled)
553 return false;
554
555 QPointer<QObject> guard = q;
556 // the checked action of an exclusive group cannot be unchecked
557 if (checkable && (!checked || !group || !group->isExclusive() || group->checkedAction() != q)) {
558 if (doToggle)
559 q->toggle(source);
560 else
561 emit q->toggled(source);
562 }
563
564 if (guard.isNull())
565 return false;
566
567 emit q->triggered(source);
568 return true;
569}
570
571bool QQuickAction::event(QEvent *event)
572{
573#if QT_CONFIG(shortcut)
574 Q_D(QQuickAction);
575 if (event->type() == QEvent::Shortcut)
576 return d->handleShortcutEvent(this, static_cast<QShortcutEvent *>(event));
577#endif
578 return QObject::event(event);
579}
580
581bool QQuickAction::eventFilter(QObject *object, QEvent *event)
582{
583#if QT_CONFIG(shortcut)
584 Q_D(QQuickAction);
585 if (event->type() == QEvent::Shortcut)
586 return d->handleShortcutEvent(object, static_cast<QShortcutEvent *>(event));
587#else
588 Q_UNUSED(object);
589 Q_UNUSED(event);
590#endif
591 return false;
592}
593
594QT_END_NAMESPACE
595
596#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.