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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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