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 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | Q_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) |
90 | static 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 | |
97 | QQuickActionPrivate::ShortcutEntry::ShortcutEntry(QObject *target) |
98 | : m_target(target) |
99 | { |
100 | } |
101 | |
102 | QQuickActionPrivate::ShortcutEntry::~ShortcutEntry() |
103 | { |
104 | ungrab(); |
105 | } |
106 | |
107 | QObject *QQuickActionPrivate::ShortcutEntry::target() const |
108 | { |
109 | return m_target; |
110 | } |
111 | |
112 | int QQuickActionPrivate::ShortcutEntry::shortcutId() const |
113 | { |
114 | return m_shortcutId; |
115 | } |
116 | |
117 | void 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 | |
129 | void 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 | |
138 | void 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 | |
146 | QVariant QQuickActionPrivate::shortcut() const |
147 | { |
148 | return vshortcut; |
149 | } |
150 | |
151 | void 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 | |
172 | void 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 | |
189 | bool 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 | |
200 | bool 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 | |
211 | void 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 | |
226 | void 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 | |
242 | void 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 | |
260 | void QQuickActionPrivate::itemDestroyed(QQuickItem *item) |
261 | { |
262 | unregisterItem(item); |
263 | } |
264 | |
265 | #if QT_CONFIG(shortcut) |
266 | bool 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 | |
280 | QQuickActionPrivate::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 | |
292 | void 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 | |
309 | QQuickAction::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 | |
318 | QQuickAction::~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 | */ |
339 | QString QQuickAction::text() const |
340 | { |
341 | Q_D(const QQuickAction); |
342 | return d->text; |
343 | } |
344 | |
345 | void 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 | */ |
365 | QQuickIcon QQuickAction::icon() const |
366 | { |
367 | Q_D(const QQuickAction); |
368 | return d->icon; |
369 | } |
370 | |
371 | void 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 | */ |
387 | bool QQuickAction::isEnabled() const |
388 | { |
389 | Q_D(const QQuickAction); |
390 | return d->enabled && (!d->group || d->group->isEnabled()); |
391 | } |
392 | |
393 | void QQuickAction::setEnabled(bool enabled) |
394 | { |
395 | Q_D(QQuickAction); |
396 | d->explicitEnabled = true; |
397 | d->setEnabled(enabled); |
398 | } |
399 | |
400 | void 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 | */ |
417 | bool QQuickAction::isChecked() const |
418 | { |
419 | Q_D(const QQuickAction); |
420 | return d->checked; |
421 | } |
422 | |
423 | void 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 | */ |
442 | bool QQuickAction::isCheckable() const |
443 | { |
444 | Q_D(const QQuickAction); |
445 | return d->checkable; |
446 | } |
447 | |
448 | void 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 | */ |
474 | QKeySequence QQuickAction::shortcut() const |
475 | { |
476 | Q_D(const QQuickAction); |
477 | return d->keySequence; |
478 | } |
479 | |
480 | void 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 | */ |
492 | void 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 | */ |
509 | void QQuickAction::trigger(QObject *source) |
510 | { |
511 | Q_D(QQuickAction); |
512 | d->trigger(source, doToggle: true); |
513 | } |
514 | |
515 | void 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 | |
534 | bool 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 | |
544 | bool 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 | |
557 | QT_END_NAMESPACE |
558 | |
559 | #include "moc_qquickaction_p.cpp" |
560 |
Definitions
- lcAction
- variantToKeySequence
- ShortcutEntry
- ~ShortcutEntry
- target
- shortcutId
- grab
- ungrab
- setEnabled
- shortcut
- setShortcut
- setEnabled
- watchItem
- unwatchItem
- registerItem
- unregisterItem
- itemVisibilityChanged
- itemDestroyed
- handleShortcutEvent
- findShortcutEntry
- updateDefaultShortcutEntry
- QQuickAction
- ~QQuickAction
- text
- setText
- icon
- setIcon
- isEnabled
- setEnabled
- resetEnabled
- isChecked
- setChecked
- isCheckable
- setCheckable
- shortcut
- setShortcut
- toggle
- trigger
- trigger
- event
Learn Advanced QML with KDAB
Find out more