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

source code of qtquickcontrols2/src/quicktemplates2/qquickaction.cpp