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

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