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
13/*!
14 \qmltype Shortcut
15 \instantiates QQuickShortcut
16 \inqmlmodule QtQuick
17 \since 5.5
18 \ingroup qtquick-input
19 \brief Provides keyboard shortcuts.
20
21 The Shortcut type lets you handle keyboard shortcuts. The shortcut can
22 be set to one of the
23 \l{QKeySequence::StandardKey}{standard keyboard shortcuts},
24 or it can be described with a string containing a sequence of up to four key
25 presses that are needed to \l{Shortcut::activated}{activate} the shortcut.
26
27 \qml
28 Item {
29 id: view
30
31 property int currentIndex
32
33 Shortcut {
34 sequences: [StandardKey.NextChild]
35 onActivated: view.currentIndex++
36 }
37 }
38 \endqml
39
40 It is also possible to set multiple shortcut \l sequences, so that the shortcut
41 can be \l activated via several different sequences of key presses.
42
43 \sa Keys, {Keys::}{shortcutOverride()}
44*/
45
46/*! \qmlsignal QtQuick::Shortcut::activated()
47
48 This signal is emitted when the shortcut is activated.
49*/
50
51/*! \qmlsignal QtQuick::Shortcut::activatedAmbiguously()
52
53 This signal is emitted when the shortcut is activated ambigously,
54 meaning that it matches the start of more than one shortcut.
55*/
56
57static bool qQuickShortcutContextMatcher(QObject *obj, Qt::ShortcutContext context)
58{
59 switch (context) {
60 case Qt::ApplicationShortcut:
61 return true;
62 case Qt::WindowShortcut:
63 while (obj && !obj->isWindowType()) {
64 obj = obj->parent();
65 if (QQuickItem *item = qobject_cast<QQuickItem *>(o: obj))
66 obj = item->window();
67 }
68 if (QWindow *renderWindow = QQuickRenderControl::renderWindowFor(win: qobject_cast<QQuickWindow *>(object: obj)))
69 obj = renderWindow;
70 return obj && obj == QGuiApplication::focusWindow();
71 default:
72 return false;
73 }
74}
75
76typedef bool (*ContextMatcher)(QObject *, Qt::ShortcutContext);
77
78Q_GLOBAL_STATIC_WITH_ARGS(ContextMatcher, ctxMatcher, (qQuickShortcutContextMatcher))
79
80Q_QUICK_PRIVATE_EXPORT ContextMatcher qt_quick_shortcut_context_matcher()
81{
82 return *ctxMatcher();
83}
84
85Q_QUICK_PRIVATE_EXPORT void qt_quick_set_shortcut_context_matcher(ContextMatcher matcher)
86{
87 if (!ctxMatcher.isDestroyed())
88 *ctxMatcher() = matcher;
89}
90
91QT_BEGIN_NAMESPACE
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}
175
176/*!
177 \qmlproperty list<keysequence> QtQuick::Shortcut::sequences
178 \since 5.9
179
180 This property holds multiple key sequences for the shortcut. The key sequences
181 can be set to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts},
182 or they can be described with strings containing sequences of up to four key
183 presses that are needed to \l{Shortcut::activated}{activate} the shortcut.
184
185 \qml
186 Shortcut {
187 sequences: [StandardKey.Cut, "Ctrl+X", "Shift+Del"]
188 onActivated: edit.cut()
189 }
190 \endqml
191*/
192QVariantList QQuickShortcut::sequences() const
193{
194 QVariantList values;
195 for (const Shortcut &shortcut : m_shortcuts)
196 values += shortcut.userValue;
197 return values;
198}
199
200void QQuickShortcut::setSequences(const QVariantList &values)
201{
202 // convert QVariantList to QVector<QKeySequence>
203 QVector<Shortcut> requestedShortcuts;
204 for (const QVariant &v : values) {
205 const QVector<QKeySequence> list = valueToKeySequences(value: v);
206 for (const QKeySequence &s : list) {
207 Shortcut sc;
208 sc.userValue = v;
209 sc.keySequence = s;
210 requestedShortcuts.push_back(t: sc);
211 }
212 }
213
214 // if nothing has changed, just return:
215 if (m_shortcuts.size() == requestedShortcuts.size()) {
216 bool changed = false;
217 for (int i = 0; i < requestedShortcuts.size(); ++i) {
218 const Shortcut &requestedShortcut = requestedShortcuts[i];
219 const Shortcut &shortcut = m_shortcuts[i];
220 if (!(requestedShortcut.userValue == shortcut.userValue
221 && requestedShortcut.keySequence == shortcut.keySequence)) {
222 changed = true;
223 break;
224 }
225 }
226 if (!changed) {
227 return;
228 }
229 }
230
231 for (Shortcut &s : m_shortcuts)
232 ungrabShortcut(shortcut&: s);
233 m_shortcuts = requestedShortcuts;
234 for (Shortcut &s : m_shortcuts)
235 grabShortcut(shortcut&: s, context: m_context);
236
237 emit sequencesChanged();
238}
239
240/*!
241 \qmlproperty string QtQuick::Shortcut::nativeText
242 \since 5.6
243
244 This property provides the shortcut's key sequence as a platform specific
245 string. This means that it will be shown translated, and on \macos it will
246 resemble a key sequence from the menu bar. It is best to display this text
247 to the user (for example, on a tooltip).
248
249 \sa sequence, portableText
250*/
251QString QQuickShortcut::nativeText() const
252{
253 return m_shortcut.keySequence.toString(format: QKeySequence::NativeText);
254}
255
256/*!
257 \qmlproperty string QtQuick::Shortcut::portableText
258 \since 5.6
259
260 This property provides the shortcut's key sequence as a string in a
261 "portable" format, suitable for reading and writing to a file. In many
262 cases, it will look similar to the native text on Windows and X11.
263
264 \sa sequence, nativeText
265*/
266QString QQuickShortcut::portableText() const
267{
268 return m_shortcut.keySequence.toString(format: QKeySequence::PortableText);
269}
270
271/*!
272 \qmlproperty bool QtQuick::Shortcut::enabled
273
274 This property holds whether the shortcut is enabled.
275
276 The default value is \c true.
277*/
278bool QQuickShortcut::isEnabled() const
279{
280 return m_enabled;
281}
282
283void QQuickShortcut::setEnabled(bool enabled)
284{
285 if (enabled == m_enabled)
286 return;
287
288 setEnabled(shortcut&: m_shortcut, enabled);
289 for (Shortcut &shortcut : m_shortcuts)
290 setEnabled(shortcut, enabled);
291
292 m_enabled = enabled;
293 emit enabledChanged();
294}
295
296/*!
297 \qmlproperty bool QtQuick::Shortcut::autoRepeat
298
299 This property holds whether the shortcut can auto repeat.
300
301 The default value is \c true.
302*/
303bool QQuickShortcut::autoRepeat() const
304{
305 return m_autorepeat;
306}
307
308void QQuickShortcut::setAutoRepeat(bool repeat)
309{
310 if (repeat == m_autorepeat)
311 return;
312
313 setAutoRepeat(shortcut&: m_shortcut, repeat);
314 for (Shortcut &shortcut : m_shortcuts)
315 setAutoRepeat(shortcut, repeat);
316
317 m_autorepeat = repeat;
318 emit autoRepeatChanged();
319}
320
321/*!
322 \qmlproperty enumeration QtQuick::Shortcut::context
323
324 This property holds the \l{Qt::ShortcutContext}{shortcut context}.
325
326 Supported values are:
327
328 \value Qt.WindowShortcut
329 (default) The shortcut is active when its parent item is in an active top-level window.
330 \value Qt.ApplicationShortcut
331 The shortcut is active when one of the application's windows are active.
332
333 \qml
334 Shortcut {
335 sequence: StandardKey.Quit
336 context: Qt.ApplicationShortcut
337 onActivated: Qt.quit()
338 }
339 \endqml
340*/
341Qt::ShortcutContext QQuickShortcut::context() const
342{
343 return m_context;
344}
345
346void QQuickShortcut::setContext(Qt::ShortcutContext context)
347{
348 if (context == m_context)
349 return;
350
351 ungrabShortcut(shortcut&: m_shortcut);
352 for (auto &s : m_shortcuts)
353 ungrabShortcut(shortcut&: s);
354
355 m_context = context;
356
357 grabShortcut(shortcut&: m_shortcut, context);
358 for (auto &s : m_shortcuts)
359 grabShortcut(shortcut&: s, context);
360
361 emit contextChanged();
362}
363
364void QQuickShortcut::classBegin()
365{
366}
367
368void QQuickShortcut::componentComplete()
369{
370 m_completed = true;
371 grabShortcut(shortcut&: m_shortcut, context: m_context);
372 for (Shortcut &shortcut : m_shortcuts)
373 grabShortcut(shortcut, context: m_context);
374}
375
376bool QQuickShortcut::event(QEvent *event)
377{
378 if (m_enabled && event->type() == QEvent::Shortcut) {
379 QShortcutEvent *se = static_cast<QShortcutEvent *>(event);
380 bool match = m_shortcut.matches(event: se);
381 int i = 0;
382 while (!match && i < m_shortcuts.size())
383 match |= m_shortcuts.at(i: i++).matches(event: se);
384 if (match) {
385 if (se->isAmbiguous())
386 emit activatedAmbiguously();
387 else
388 emit activated();
389 return true;
390 }
391 }
392 return false;
393}
394
395bool QQuickShortcut::Shortcut::matches(QShortcutEvent *event) const
396{
397 return event->shortcutId() == id && event->key() == keySequence;
398}
399
400void QQuickShortcut::setEnabled(QQuickShortcut::Shortcut &shortcut, bool enabled)
401{
402 if (shortcut.id)
403 QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enable: enabled, id: shortcut.id, owner: this);
404}
405
406void QQuickShortcut::setAutoRepeat(QQuickShortcut::Shortcut &shortcut, bool repeat)
407{
408 if (shortcut.id)
409 QGuiApplicationPrivate::instance()->shortcutMap.setShortcutAutoRepeat(on: repeat, id: shortcut.id, owner: this);
410}
411
412void QQuickShortcut::grabShortcut(Shortcut &shortcut, Qt::ShortcutContext context)
413{
414 if (m_completed && !shortcut.keySequence.isEmpty()) {
415 QGuiApplicationPrivate *pApp = QGuiApplicationPrivate::instance();
416 shortcut.id = pApp->shortcutMap.addShortcut(owner: this, key: shortcut.keySequence, context, matcher: *ctxMatcher());
417 if (!m_enabled)
418 pApp->shortcutMap.setShortcutEnabled(enable: false, id: shortcut.id, owner: this);
419 if (!m_autorepeat)
420 pApp->shortcutMap.setShortcutAutoRepeat(on: false, id: shortcut.id, owner: this);
421 }
422}
423
424void QQuickShortcut::ungrabShortcut(Shortcut &shortcut)
425{
426 if (shortcut.id) {
427 QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(id: shortcut.id, owner: this);
428 shortcut.id = 0;
429 }
430}
431
432QT_END_NAMESPACE
433
434#include "moc_qquickshortcut_p.cpp"
435

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