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 
49 QT_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)
variantToKeySequence(const QVariant & var)119 static 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(var.toString());
124 }
125 
ShortcutEntry(QObject * target)126 QQuickActionPrivate::ShortcutEntry::ShortcutEntry(QObject *target)
127     : m_target(target)
128 {
129 }
130 
~ShortcutEntry()131 QQuickActionPrivate::ShortcutEntry::~ShortcutEntry()
132 {
133     ungrab();
134 }
135 
target() const136 QObject *QQuickActionPrivate::ShortcutEntry::target() const
137 {
138     return m_target;
139 }
140 
shortcutId() const141 int QQuickActionPrivate::ShortcutEntry::shortcutId() const
142 {
143     return m_shortcutId;
144 }
145 
grab(const QKeySequence & shortcut,bool enabled)146 void QQuickActionPrivate::ShortcutEntry::grab(const QKeySequence &shortcut, bool enabled)
147 {
148     if (shortcut.isEmpty())
149         return;
150 
151     Qt::ShortcutContext context = Qt::WindowShortcut; // TODO
152     m_shortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(m_target, shortcut, context, QQuickShortcutContext::matcher);
153 
154     if (!enabled)
155         QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(false, m_shortcutId, m_target);
156 }
157 
ungrab()158 void QQuickActionPrivate::ShortcutEntry::ungrab()
159 {
160     if (!m_shortcutId)
161         return;
162 
163     QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(m_shortcutId, m_target);
164     m_shortcutId = 0;
165 }
166 
setEnabled(bool enabled)167 void QQuickActionPrivate::ShortcutEntry::setEnabled(bool enabled)
168 {
169     if (!m_shortcutId)
170         return;
171 
172     QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enabled, m_shortcutId, m_target);
173 }
174 
shortcut() const175 QVariant QQuickActionPrivate::shortcut() const
176 {
177     return vshortcut;
178 }
179 
setShortcut(const QVariant & var)180 void 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(shortcutEntries))
188         entry->ungrab();
189 
190     vshortcut = var;
191     keySequence = variantToKeySequence(var);
192 
193     defaultShortcutEntry->grab(keySequence, enabled);
194     for (QQuickActionPrivate::ShortcutEntry *entry : qAsConst(shortcutEntries))
195         entry->grab(keySequence, enabled);
196 
197     emit q->shortcutChanged(keySequence);
198 }
199 #endif // QT_CONFIG(shortcut)
200 
setEnabled(bool enable)201 void 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(shortcutEntries))
212         entry->setEnabled(enable);
213 #endif
214 
215     emit q->enabledChanged(enable);
216 }
217 
watchItem(QQuickItem * item)218 bool QQuickActionPrivate::watchItem(QQuickItem *item)
219 {
220     Q_Q(QQuickAction);
221     if (!item)
222         return false;
223 
224     item->installEventFilter(q);
225     QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Visibility | QQuickItemPrivate::Destroyed);
226     return true;
227 }
228 
unwatchItem(QQuickItem * item)229 bool QQuickActionPrivate::unwatchItem(QQuickItem *item)
230 {
231     Q_Q(QQuickAction);
232     if (!item)
233         return false;
234 
235     item->removeEventFilter(q);
236     QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Visibility | QQuickItemPrivate::Destroyed);
237     return true;
238 }
239 
registerItem(QQuickItem * item)240 void 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(keySequence, enabled);
249     shortcutEntries += entry;
250 
251     updateDefaultShortcutEntry();
252 #endif
253 }
254 
unregisterItem(QQuickItem * item)255 void QQuickActionPrivate::unregisterItem(QQuickItem *item)
256 {
257 #if QT_CONFIG(shortcut)
258     QQuickActionPrivate::ShortcutEntry *entry = findShortcutEntry(item);
259     if (!entry || !unwatchItem(item))
260         return;
261 
262     shortcutEntries.removeOne(entry);
263     delete entry;
264 
265     updateDefaultShortcutEntry();
266 #else
267     Q_UNUSED(item);
268 #endif
269 }
270 
itemVisibilityChanged(QQuickItem * item)271 void QQuickActionPrivate::itemVisibilityChanged(QQuickItem *item)
272 {
273 #if QT_CONFIG(shortcut)
274     QQuickActionPrivate::ShortcutEntry *entry = findShortcutEntry(item);
275     if (!entry)
276         return;
277 
278     if (item->isVisible())
279         entry->grab(keySequence, enabled);
280     else
281         entry->ungrab();
282 
283     updateDefaultShortcutEntry();
284 #else
285     Q_UNUSED(item);
286 #endif
287 }
288 
itemDestroyed(QQuickItem * item)289 void QQuickActionPrivate::itemDestroyed(QQuickItem *item)
290 {
291     unregisterItem(item);
292 }
293 
294 #if QT_CONFIG(shortcut)
handleShortcutEvent(QObject * object,QShortcutEvent * event)295 bool 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(object);
302     if (!entry || event->shortcutId() != entry->shortcutId())
303         return false;
304 
305     q->trigger(entry->target());
306     return true;
307 }
308 
findShortcutEntry(QObject * target) const309 QQuickActionPrivate::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 
updateDefaultShortcutEntry()321 void QQuickActionPrivate::updateDefaultShortcutEntry()
322 {
323     bool hasActiveShortcutEntries = false;
324     for (QQuickActionPrivate::ShortcutEntry *entry : qAsConst(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(keySequence, enabled);
335 }
336 #endif // QT_CONFIG(shortcut)
337 
QQuickAction(QObject * parent)338 QQuickAction::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 
~QQuickAction()347 QQuickAction::~QQuickAction()
348 {
349     Q_D(QQuickAction);
350     if (d->group)
351         d->group->removeAction(this);
352 
353 #if QT_CONFIG(shortcut)
354     for (QQuickActionPrivate::ShortcutEntry *entry : qAsConst(d->shortcutEntries))
355         d->unwatchItem(qobject_cast<QQuickItem *>(entry->target()));
356 
357     qDeleteAll(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 */
text() const367 QString QQuickAction::text() const
368 {
369     Q_D(const QQuickAction);
370     return d->text;
371 }
372 
setText(const QString & text)373 void 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 */
icon() const393 QQuickIcon QQuickAction::icon() const
394 {
395     Q_D(const QQuickAction);
396     return d->icon;
397 }
398 
setIcon(const QQuickIcon & icon)399 void 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 */
isEnabled() const414 bool QQuickAction::isEnabled() const
415 {
416     Q_D(const QQuickAction);
417     return d->enabled && (!d->group || d->group->isEnabled());
418 }
419 
setEnabled(bool enabled)420 void QQuickAction::setEnabled(bool enabled)
421 {
422     Q_D(QQuickAction);
423     d->explicitEnabled = true;
424     d->setEnabled(enabled);
425 }
426 
resetEnabled()427 void 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 */
isChecked() const444 bool QQuickAction::isChecked() const
445 {
446     Q_D(const QQuickAction);
447     return d->checked;
448 }
449 
setChecked(bool checked)450 void 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 */
isCheckable() const469 bool QQuickAction::isCheckable() const
470 {
471     Q_D(const QQuickAction);
472     return d->checkable;
473 }
474 
setCheckable(bool checkable)475 void 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 */
shortcut() const501 QKeySequence QQuickAction::shortcut() const
502 {
503     Q_D(const QQuickAction);
504     return d->keySequence;
505 }
506 
setShortcut(const QKeySequence & shortcut)507 void 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 */
toggle(QObject * source)519 void 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 */
trigger(QObject * source)536 void QQuickAction::trigger(QObject *source)
537 {
538     Q_D(QQuickAction);
539     d->trigger(source, true);
540 }
541 
trigger(QObject * source,bool doToggle)542 void 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 
event(QEvent * event)561 bool 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 
eventFilter(QObject * object,QEvent * event)571 bool 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 
584 QT_END_NAMESPACE
585 
586 #include "moc_qquickaction_p.cpp"
587