1// Copyright (C) 2016 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 "qquickshortcut_p.h"
5
6#include <QtQuick/qquickitem.h>
7#include <QtQuick/qquickwindow.h>
8#include <QtQuick/qquickrendercontrol.h>
9#include <QtQuick/private/qtquickglobal_p.h>
10#include <QtGui/private/qguiapplication_p.h>
11#include <QtQml/qqmlinfo.h>
12
13QT_BEGIN_NAMESPACE
14
15/*!
16 \qmltype Shortcut
17 \nativetype QQuickShortcut
18 \inqmlmodule QtQuick
19 \since 5.5
20 \ingroup qtquick-input
21 \brief Provides keyboard shortcuts.
22
23 The Shortcut type lets you handle keyboard shortcuts. The shortcut can
24 be set to one of the
25 \l{QKeySequence::StandardKey}{standard keyboard shortcuts},
26 or it can be described with a string containing a sequence of up to four key
27 presses that are needed to \l{Shortcut::activated}{activate} the shortcut.
28
29 \qml
30 Item {
31 id: view
32
33 property int currentIndex
34
35 Shortcut {
36 sequences: [StandardKey.NextChild]
37 onActivated: view.currentIndex++
38 }
39 }
40 \endqml
41
42 It is also possible to set multiple shortcut \l sequences, so that the shortcut
43 can be \l activated via several different sequences of key presses.
44
45 \sa Keys, {Keys::}{shortcutOverride()}
46*/
47
48/*! \qmlsignal QtQuick::Shortcut::activated()
49
50 This signal is emitted when the shortcut is activated.
51*/
52
53/*! \qmlsignal QtQuick::Shortcut::activatedAmbiguously()
54
55 This signal is emitted when the shortcut is activated ambigously,
56 meaning that it matches the start of more than one shortcut.
57*/
58
59static bool qQuickShortcutContextMatcher(QObject *obj, Qt::ShortcutContext context)
60{
61 switch (context) {
62 case Qt::ApplicationShortcut:
63 return true;
64 case Qt::WindowShortcut:
65 while (obj && !obj->isWindowType()) {
66 obj = obj->parent();
67 if (QQuickItem *item = qobject_cast<QQuickItem *>(o: obj))
68 obj = item->window();
69 }
70 if (QWindow *renderWindow = QQuickRenderControl::renderWindowFor(win: qobject_cast<QQuickWindow *>(object: obj)))
71 obj = renderWindow;
72 return obj && obj == QGuiApplication::focusWindow();
73 default:
74 return false;
75 }
76}
77
78typedef bool (*ContextMatcher)(QObject *, Qt::ShortcutContext);
79
80Q_GLOBAL_STATIC_WITH_ARGS(ContextMatcher, ctxMatcher, (qQuickShortcutContextMatcher))
81
82Q_QUICK_EXPORT ContextMatcher qt_quick_shortcut_context_matcher()
83{
84 return *ctxMatcher();
85}
86
87Q_QUICK_EXPORT void qt_quick_set_shortcut_context_matcher(ContextMatcher matcher)
88{
89 if (!ctxMatcher.isDestroyed())
90 *ctxMatcher() = matcher;
91}
92
93static QKeySequence valueToKeySequence(const QVariant &value, const QQuickShortcut *const shortcut)
94{
95 if (value.userType() == QMetaType::Int) {
96 const QVector<QKeySequence> s =
97 QKeySequence::keyBindings(key: static_cast<QKeySequence::StandardKey>(value.toInt()));
98 if (s.size() > 1) {
99 const QString templateString = QString::fromUtf16(
100 u"Shortcut: Only binding to one of multiple key bindings associated with %1. "
101 u"Use 'sequences: [ <key> ]' to bind to all of them.");
102 qmlWarning(me: shortcut)
103 << templateString.arg(a: static_cast<QKeySequence::StandardKey>(value.toInt()));
104 }
105 return s.size() > 0 ? s[0] : QKeySequence {};
106 }
107
108 return QKeySequence::fromString(str: value.toString());
109}
110
111static QList<QKeySequence> valueToKeySequences(const QVariant &value)
112{
113 if (value.userType() == QMetaType::Int) {
114 return QKeySequence::keyBindings(key: static_cast<QKeySequence::StandardKey>(value.toInt()));
115 } else {
116 QList<QKeySequence> result;
117 result.push_back(t: QKeySequence::fromString(str: value.toString()));
118 return result;
119 }
120}
121
122QQuickShortcut::QQuickShortcut(QObject *parent) : QObject(parent),
123 m_enabled(true), m_completed(false), m_autorepeat(true), m_context(Qt::WindowShortcut)
124{
125}
126
127QQuickShortcut::~QQuickShortcut()
128{
129 ungrabShortcut(shortcut&: m_shortcut);
130 for (Shortcut &shortcut : m_shortcuts)
131 ungrabShortcut(shortcut);
132}
133
134/*!
135 \qmlproperty keysequence QtQuick::Shortcut::sequence
136
137 This property holds the shortcut's key sequence. The key sequence can be set
138 to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts}, or
139 it can be described with a string containing a sequence of up to four key
140 presses that are needed to \l{Shortcut::activated}{activate} the shortcut.
141
142 The default value is an empty key sequence.
143
144 \qml
145 Shortcut {
146 sequence: "Ctrl+E,Ctrl+W"
147 onActivated: edit.wrapMode = TextEdit.Wrap
148 }
149 \endqml
150
151 \note Given that standard keys can resolve to one shortcut on some
152 platforms, but multiple shortcuts on other platforms, we recommend always
153 using \l{Shortcut::}{sequences} for standard keys.
154
155 \sa sequences
156*/
157QVariant QQuickShortcut::sequence() const
158{
159 return m_shortcut.userValue;
160}
161
162void QQuickShortcut::setSequence(const QVariant &value)
163{
164 if (value == m_shortcut.userValue)
165 return;
166
167 QKeySequence keySequence = valueToKeySequence(value, shortcut: this);
168
169 ungrabShortcut(shortcut&: m_shortcut);
170 m_shortcut.userValue = value;
171 m_shortcut.keySequence = keySequence;
172 grabShortcut(shortcut&: m_shortcut, context: m_context);
173 emit sequenceChanged();
174 if (m_shortcuts.isEmpty()) {
175 // The text only changes if m_shortcuts contained no entry
176 // as we prefer the entry from there otherwise
177 emit nativeTextChanged();
178 emit portableTextChanged();
179 }
180}
181
182/*!
183 \qmlproperty list<keysequence> QtQuick::Shortcut::sequences
184 \since 5.9
185
186 This property holds multiple key sequences for the shortcut. The key sequences
187 can be set to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts},
188 or they can be described with strings containing sequences of up to four key
189 presses that are needed to \l{Shortcut::activated}{activate} the shortcut.
190
191 \qml
192 Shortcut {
193 sequences: [StandardKey.Cut, "Ctrl+X", "Shift+Del"]
194 onActivated: edit.cut()
195 }
196 \endqml
197*/
198QVariantList QQuickShortcut::sequences() const
199{
200 QVariantList values;
201 for (const Shortcut &shortcut : m_shortcuts)
202 values += shortcut.userValue;
203 return values;
204}
205
206void QQuickShortcut::setSequences(const QVariantList &values)
207{
208 // convert QVariantList to QVector<QKeySequence>
209 QVector<Shortcut> requestedShortcuts;
210 for (const QVariant &v : values) {
211 const QVector<QKeySequence> list = valueToKeySequences(value: v);
212 for (const QKeySequence &s : list) {
213 Shortcut sc;
214 sc.userValue = v;
215 sc.keySequence = s;
216 requestedShortcuts.push_back(t: sc);
217 }
218 }
219
220 // if nothing has changed, just return:
221 if (m_shortcuts.size() == requestedShortcuts.size()) {
222 bool changed = false;
223 for (int i = 0; i < requestedShortcuts.size(); ++i) {
224 const Shortcut &requestedShortcut = requestedShortcuts[i];
225 const Shortcut &shortcut = m_shortcuts[i];
226 if (!(requestedShortcut.userValue == shortcut.userValue
227 && requestedShortcut.keySequence == shortcut.keySequence)) {
228 changed = true;
229 break;
230 }
231 }
232 if (!changed) {
233 return;
234 }
235 }
236
237 const Shortcut oldUsedShortcut = m_shortcuts.isEmpty() ? m_shortcut
238 : m_shortcuts.first();
239
240 for (Shortcut &s : m_shortcuts)
241 ungrabShortcut(shortcut&: s);
242 m_shortcuts = requestedShortcuts;
243 for (Shortcut &s : m_shortcuts)
244 grabShortcut(shortcut&: s, context: m_context);
245
246 const Shortcut currentUsedShortcut = m_shortcuts.isEmpty() ? m_shortcut
247 : m_shortcuts.first();
248
249 emit sequencesChanged();
250 if (oldUsedShortcut.keySequence != currentUsedShortcut.keySequence) {
251 emit nativeTextChanged();
252 emit portableTextChanged();
253 }
254}
255
256/*!
257 \qmlproperty string QtQuick::Shortcut::nativeText
258 \since 5.6
259
260 This property provides the shortcut's key sequence as a platform specific
261 string. This means that it will be shown translated, and on \macos it will
262 resemble a key sequence from the menu bar. It is best to display this text
263 to the user (for example, on a tooltip).
264
265 \include qquickshortcut.qdocinc [multishortcut]
266
267 \sa sequence, portableText
268*/
269QString QQuickShortcut::nativeText() const
270{
271 const Shortcut &shortCut = m_shortcuts.isEmpty() ? m_shortcut
272 : m_shortcuts.front();
273 return shortCut.keySequence.toString(format: QKeySequence::NativeText);
274}
275
276/*!
277 \qmlproperty string QtQuick::Shortcut::portableText
278 \since 5.6
279
280 This property provides the shortcut's key sequence as a string in a
281 "portable" format, suitable for reading and writing to a file. In many
282 cases, it will look similar to the native text on Windows and X11.
283
284 \include qquickshortcut.qdocinc [multishortcut]
285
286 \sa sequence, nativeText
287*/
288QString QQuickShortcut::portableText() const
289{
290 const Shortcut &shortCut = m_shortcuts.isEmpty() ? m_shortcut
291 : m_shortcuts.front();
292 return shortCut.keySequence.toString(format: QKeySequence::PortableText);
293}
294
295/*!
296 \qmlproperty bool QtQuick::Shortcut::enabled
297
298 This property holds whether the shortcut is enabled.
299
300 The default value is \c true.
301*/
302bool QQuickShortcut::isEnabled() const
303{
304 return m_enabled;
305}
306
307void QQuickShortcut::setEnabled(bool enabled)
308{
309 if (enabled == m_enabled)
310 return;
311
312 setEnabled(shortcut&: m_shortcut, enabled);
313 for (Shortcut &shortcut : m_shortcuts)
314 setEnabled(shortcut, enabled);
315
316 m_enabled = enabled;
317 emit enabledChanged();
318}
319
320/*!
321 \qmlproperty bool QtQuick::Shortcut::autoRepeat
322
323 This property holds whether the shortcut can auto repeat.
324
325 The default value is \c true.
326*/
327bool QQuickShortcut::autoRepeat() const
328{
329 return m_autorepeat;
330}
331
332void QQuickShortcut::setAutoRepeat(bool repeat)
333{
334 if (repeat == m_autorepeat)
335 return;
336
337 setAutoRepeat(shortcut&: m_shortcut, repeat);
338 for (Shortcut &shortcut : m_shortcuts)
339 setAutoRepeat(shortcut, repeat);
340
341 m_autorepeat = repeat;
342 emit autoRepeatChanged();
343}
344
345/*!
346 \qmlproperty enumeration QtQuick::Shortcut::context
347
348 This property holds the \l{Qt::ShortcutContext}{shortcut context}.
349
350 Supported values are:
351
352 \value Qt.WindowShortcut
353 (default) The shortcut is active when its parent item is in an active top-level window.
354 \value Qt.ApplicationShortcut
355 The shortcut is active when one of the application's windows are active.
356
357 \qml
358 Shortcut {
359 sequence: StandardKey.Quit
360 context: Qt.ApplicationShortcut
361 onActivated: Qt.quit()
362 }
363 \endqml
364*/
365Qt::ShortcutContext QQuickShortcut::context() const
366{
367 return m_context;
368}
369
370void QQuickShortcut::setContext(Qt::ShortcutContext context)
371{
372 if (context == m_context)
373 return;
374
375 ungrabShortcut(shortcut&: m_shortcut);
376 for (auto &s : m_shortcuts)
377 ungrabShortcut(shortcut&: s);
378
379 m_context = context;
380
381 grabShortcut(shortcut&: m_shortcut, context);
382 for (auto &s : m_shortcuts)
383 grabShortcut(shortcut&: s, context);
384
385 emit contextChanged();
386}
387
388void QQuickShortcut::classBegin()
389{
390}
391
392void QQuickShortcut::componentComplete()
393{
394 m_completed = true;
395 grabShortcut(shortcut&: m_shortcut, context: m_context);
396 for (Shortcut &shortcut : m_shortcuts)
397 grabShortcut(shortcut, context: m_context);
398}
399
400bool QQuickShortcut::event(QEvent *event)
401{
402 if (m_enabled && event->type() == QEvent::Shortcut) {
403 QShortcutEvent *se = static_cast<QShortcutEvent *>(event);
404 bool match = m_shortcut.matches(event: se);
405 int i = 0;
406 while (!match && i < m_shortcuts.size())
407 match |= m_shortcuts.at(i: i++).matches(event: se);
408 if (match) {
409 if (se->isAmbiguous())
410 emit activatedAmbiguously();
411 else
412 emit activated();
413 return true;
414 }
415 }
416 return false;
417}
418
419bool QQuickShortcut::Shortcut::matches(QShortcutEvent *event) const
420{
421 return event->shortcutId() == id && event->key() == keySequence;
422}
423
424void QQuickShortcut::setEnabled(QQuickShortcut::Shortcut &shortcut, bool enabled)
425{
426 if (shortcut.id)
427 QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enable: enabled, id: shortcut.id, owner: this);
428}
429
430void QQuickShortcut::setAutoRepeat(QQuickShortcut::Shortcut &shortcut, bool repeat)
431{
432 if (shortcut.id)
433 QGuiApplicationPrivate::instance()->shortcutMap.setShortcutAutoRepeat(on: repeat, id: shortcut.id, owner: this);
434}
435
436void QQuickShortcut::grabShortcut(Shortcut &shortcut, Qt::ShortcutContext context)
437{
438 if (m_completed && !shortcut.keySequence.isEmpty()) {
439 QGuiApplicationPrivate *pApp = QGuiApplicationPrivate::instance();
440 shortcut.id = pApp->shortcutMap.addShortcut(owner: this, key: shortcut.keySequence, context, matcher: *ctxMatcher());
441 if (!m_enabled)
442 pApp->shortcutMap.setShortcutEnabled(enable: false, id: shortcut.id, owner: this);
443 if (!m_autorepeat)
444 pApp->shortcutMap.setShortcutAutoRepeat(on: false, id: shortcut.id, owner: this);
445 }
446}
447
448void QQuickShortcut::ungrabShortcut(Shortcut &shortcut)
449{
450 if (shortcut.id) {
451 QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(id: shortcut.id, owner: this);
452 shortcut.id = 0;
453 }
454}
455
456QT_END_NAMESPACE
457
458#include "moc_qquickshortcut_p.cpp"
459

source code of qtdeclarative/src/quick/util/qquickshortcut.cpp