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 | |
57 | static 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 | |
76 | typedef bool (*ContextMatcher)(QObject *, Qt::ShortcutContext); |
77 | |
78 | Q_GLOBAL_STATIC_WITH_ARGS(ContextMatcher, ctxMatcher, (qQuickShortcutContextMatcher)) |
79 | |
80 | Q_QUICK_PRIVATE_EXPORT ContextMatcher qt_quick_shortcut_context_matcher() |
81 | { |
82 | return *ctxMatcher(); |
83 | } |
84 | |
85 | Q_QUICK_PRIVATE_EXPORT void qt_quick_set_shortcut_context_matcher(ContextMatcher matcher) |
86 | { |
87 | if (!ctxMatcher.isDestroyed()) |
88 | *ctxMatcher() = matcher; |
89 | } |
90 | |
91 | QT_BEGIN_NAMESPACE |
92 | |
93 | static 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 | |
111 | static 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 | |
122 | QQuickShortcut::QQuickShortcut(QObject *parent) : QObject(parent), |
123 | m_enabled(true), m_completed(false), m_autorepeat(true), m_context(Qt::WindowShortcut) |
124 | { |
125 | } |
126 | |
127 | QQuickShortcut::~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 | */ |
157 | QVariant QQuickShortcut::sequence() const |
158 | { |
159 | return m_shortcut.userValue; |
160 | } |
161 | |
162 | void 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 | */ |
192 | QVariantList QQuickShortcut::sequences() const |
193 | { |
194 | QVariantList values; |
195 | for (const Shortcut &shortcut : m_shortcuts) |
196 | values += shortcut.userValue; |
197 | return values; |
198 | } |
199 | |
200 | void 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 | */ |
251 | QString 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 | */ |
266 | QString 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 | */ |
278 | bool QQuickShortcut::isEnabled() const |
279 | { |
280 | return m_enabled; |
281 | } |
282 | |
283 | void 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 | */ |
303 | bool QQuickShortcut::autoRepeat() const |
304 | { |
305 | return m_autorepeat; |
306 | } |
307 | |
308 | void 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 | */ |
341 | Qt::ShortcutContext QQuickShortcut::context() const |
342 | { |
343 | return m_context; |
344 | } |
345 | |
346 | void 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 | |
364 | void QQuickShortcut::classBegin() |
365 | { |
366 | } |
367 | |
368 | void 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 | |
376 | bool 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 | |
395 | bool QQuickShortcut::Shortcut::matches(QShortcutEvent *event) const |
396 | { |
397 | return event->shortcutId() == id && event->key() == keySequence; |
398 | } |
399 | |
400 | void 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 | |
406 | void 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 | |
412 | void 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 | |
424 | void 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 | |
432 | QT_END_NAMESPACE |
433 | |
434 | #include "moc_qquickshortcut_p.cpp" |
435 | |