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
48/*!
49 \qmltype Shortcut
50 \instantiates QQuickShortcut
51 \inqmlmodule QtQuick
52 \since 5.5
53 \ingroup qtquick-input
54 \brief Provides keyboard shortcuts.
55
56 The Shortcut type provides a way of handling keyboard shortcuts. The shortcut can
57 be set to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts},
58 or it can be described with a string containing a sequence of up to four key
59 presses that are needed to \l{Shortcut::activated}{activate} the shortcut.
60
61 \qml
62 Item {
63 id: view
64
65 property int currentIndex
66
67 Shortcut {
68 sequence: StandardKey.NextChild
69 onActivated: view.currentIndex++
70 }
71 }
72 \endqml
73
74 It is also possible to set multiple shortcut \l sequences, so that the shortcut
75 can be \l activated via several different sequences of key presses.
76
77 \sa Keys, {Keys::}{shortcutOverride()}
78*/
79
80/*! \qmlsignal QtQuick::Shortcut::activated()
81
82 This signal is emitted when the shortcut is activated.
83
84 The corresponding handler is \c onActivated.
85*/
86
87/*! \qmlsignal QtQuick::Shortcut::activatedAmbiguously()
88
89 This signal is emitted when the shortcut is activated ambigously,
90 meaning that it matches the start of more than one shortcut.
91
92 The corresponding handler is \c onActivatedAmbiguously.
93*/
94
95static bool qQuickShortcutContextMatcher(QObject *obj, Qt::ShortcutContext context)
96{
97 switch (context) {
98 case Qt::ApplicationShortcut:
99 return true;
100 case Qt::WindowShortcut:
101 while (obj && !obj->isWindowType()) {
102 obj = obj->parent();
103 if (QQuickItem *item = qobject_cast<QQuickItem *>(obj))
104 obj = item->window();
105 }
106 if (QWindow *renderWindow = QQuickRenderControl::renderWindowFor(qobject_cast<QQuickWindow *>(obj)))
107 obj = renderWindow;
108 return obj && obj == QGuiApplication::focusWindow();
109 default:
110 return false;
111 }
112}
113
114typedef bool (*ContextMatcher)(QObject *, Qt::ShortcutContext);
115
116Q_GLOBAL_STATIC_WITH_ARGS(ContextMatcher, ctxMatcher, (qQuickShortcutContextMatcher))
117
118Q_QUICK_PRIVATE_EXPORT ContextMatcher qt_quick_shortcut_context_matcher()
119{
120 return *ctxMatcher();
121}
122
123Q_QUICK_PRIVATE_EXPORT void qt_quick_set_shortcut_context_matcher(ContextMatcher matcher)
124{
125 if (!ctxMatcher.isDestroyed())
126 *ctxMatcher() = matcher;
127}
128
129QT_BEGIN_NAMESPACE
130
131static QKeySequence valueToKeySequence(const QVariant &value)
132{
133 if (value.type() == QVariant::Int)
134 return QKeySequence(static_cast<QKeySequence::StandardKey>(value.toInt()));
135 return QKeySequence::fromString(value.toString());
136}
137
138QQuickShortcut::QQuickShortcut(QObject *parent) : QObject(parent),
139 m_enabled(true), m_completed(false), m_autorepeat(true), m_context(Qt::WindowShortcut)
140{
141}
142
143QQuickShortcut::~QQuickShortcut()
144{
145 ungrabShortcut(m_shortcut);
146 for (Shortcut &shortcut : m_shortcuts)
147 ungrabShortcut(shortcut);
148}
149
150/*!
151 \qmlproperty keysequence QtQuick::Shortcut::sequence
152
153 This property holds the shortcut's key sequence. The key sequence can be set
154 to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts}, or
155 it can be described with a string containing a sequence of up to four key
156 presses that are needed to \l{Shortcut::activated}{activate} the shortcut.
157
158 The default value is an empty key sequence.
159
160 \qml
161 Shortcut {
162 sequence: "Ctrl+E,Ctrl+W"
163 onActivated: edit.wrapMode = TextEdit.Wrap
164 }
165 \endqml
166
167 \sa sequences
168*/
169QVariant QQuickShortcut::sequence() const
170{
171 return m_shortcut.userValue;
172}
173
174void QQuickShortcut::setSequence(const QVariant &value)
175{
176 if (value == m_shortcut.userValue)
177 return;
178
179 QKeySequence keySequence = valueToKeySequence(value);
180
181 ungrabShortcut(m_shortcut);
182 m_shortcut.userValue = value;
183 m_shortcut.keySequence = keySequence;
184 grabShortcut(m_shortcut, m_context);
185 emit sequenceChanged();
186}
187
188/*!
189 \qmlproperty list<keysequence> QtQuick::Shortcut::sequences
190 \since 5.9
191
192 This property holds multiple key sequences for the shortcut. The key sequences
193 can be set to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts},
194 or they can be described with strings containing sequences of up to four key
195 presses that are needed to \l{Shortcut::activated}{activate} the shortcut.
196
197 \qml
198 Shortcut {
199 sequences: [StandardKey.Cut, "Ctrl+X", "Shift+Del"]
200 onActivated: edit.cut()
201 }
202 \endqml
203*/
204QVariantList QQuickShortcut::sequences() const
205{
206 QVariantList values;
207 for (const Shortcut &shortcut : m_shortcuts)
208 values += shortcut.userValue;
209 return values;
210}
211
212void QQuickShortcut::setSequences(const QVariantList &values)
213{
214 QVector<Shortcut> remainder = m_shortcuts.mid(values.count());
215 m_shortcuts.resize(values.count());
216
217 bool changed = !remainder.isEmpty();
218 for (int i = 0; i < values.count(); ++i) {
219 QVariant value = values.at(i);
220 Shortcut& shortcut = m_shortcuts[i];
221 if (value == shortcut.userValue)
222 continue;
223
224 QKeySequence keySequence = valueToKeySequence(value);
225
226 ungrabShortcut(shortcut);
227 shortcut.userValue = value;
228 shortcut.keySequence = keySequence;
229 grabShortcut(shortcut, m_context);
230
231 changed = true;
232 }
233
234 if (changed)
235 emit sequencesChanged();
236}
237
238/*!
239 \qmlproperty string QtQuick::Shortcut::nativeText
240 \since 5.6
241
242 This property provides the shortcut's key sequence as a platform specific
243 string. This means that it will be shown translated, and on \macos it will
244 resemble a key sequence from the menu bar. It is best to display this text
245 to the user (for example, on a tooltip).
246
247 \sa sequence, portableText
248*/
249QString QQuickShortcut::nativeText() const
250{
251 return m_shortcut.keySequence.toString(QKeySequence::NativeText);
252}
253
254/*!
255 \qmlproperty string QtQuick::Shortcut::portableText
256 \since 5.6
257
258 This property provides the shortcut's key sequence as a string in a
259 "portable" format, suitable for reading and writing to a file. In many
260 cases, it will look similar to the native text on Windows and X11.
261
262 \sa sequence, nativeText
263*/
264QString QQuickShortcut::portableText() const
265{
266 return m_shortcut.keySequence.toString(QKeySequence::PortableText);
267}
268
269/*!
270 \qmlproperty bool QtQuick::Shortcut::enabled
271
272 This property holds whether the shortcut is enabled.
273
274 The default value is \c true.
275*/
276bool QQuickShortcut::isEnabled() const
277{
278 return m_enabled;
279}
280
281void QQuickShortcut::setEnabled(bool enabled)
282{
283 if (enabled == m_enabled)
284 return;
285
286 setEnabled(m_shortcut, enabled);
287 for (Shortcut &shortcut : m_shortcuts)
288 setEnabled(shortcut, enabled);
289
290 m_enabled = enabled;
291 emit enabledChanged();
292}
293
294/*!
295 \qmlproperty bool QtQuick::Shortcut::autoRepeat
296
297 This property holds whether the shortcut can auto repeat.
298
299 The default value is \c true.
300*/
301bool QQuickShortcut::autoRepeat() const
302{
303 return m_autorepeat;
304}
305
306void QQuickShortcut::setAutoRepeat(bool repeat)
307{
308 if (repeat == m_autorepeat)
309 return;
310
311 setAutoRepeat(m_shortcut, repeat);
312 for (Shortcut &shortcut : m_shortcuts)
313 setAutoRepeat(shortcut, repeat);
314
315 m_autorepeat = repeat;
316 emit autoRepeatChanged();
317}
318
319/*!
320 \qmlproperty enumeration QtQuick::Shortcut::context
321
322 This property holds the \l{Qt::ShortcutContext}{shortcut context}.
323
324 Supported values are:
325 \list
326 \li \c Qt.WindowShortcut (default) - The shortcut is active when its parent item is in an active top-level window.
327 \li \c Qt.ApplicationShortcut - The shortcut is active when one of the application's windows are active.
328 \endlist
329
330 \qml
331 Shortcut {
332 sequence: StandardKey.Quit
333 context: Qt.ApplicationShortcut
334 onActivated: Qt.quit()
335 }
336 \endqml
337*/
338Qt::ShortcutContext QQuickShortcut::context() const
339{
340 return m_context;
341}
342
343void QQuickShortcut::setContext(Qt::ShortcutContext context)
344{
345 if (context == m_context)
346 return;
347
348 ungrabShortcut(m_shortcut);
349 m_context = context;
350 grabShortcut(m_shortcut, context);
351 emit contextChanged();
352}
353
354void QQuickShortcut::classBegin()
355{
356}
357
358void QQuickShortcut::componentComplete()
359{
360 m_completed = true;
361 grabShortcut(m_shortcut, m_context);
362 for (Shortcut &shortcut : m_shortcuts)
363 grabShortcut(shortcut, m_context);
364}
365
366bool QQuickShortcut::event(QEvent *event)
367{
368 if (m_enabled && event->type() == QEvent::Shortcut) {
369 QShortcutEvent *se = static_cast<QShortcutEvent *>(event);
370 bool match = m_shortcut.matches(se);
371 int i = 0;
372 while (!match && i < m_shortcuts.count())
373 match |= m_shortcuts.at(i++).matches(se);
374 if (match) {
375 if (se->isAmbiguous())
376 emit activatedAmbiguously();
377 else
378 emit activated();
379 return true;
380 }
381 }
382 return false;
383}
384
385bool QQuickShortcut::Shortcut::matches(QShortcutEvent *event) const
386{
387 return event->shortcutId() == id && event->key() == keySequence;
388}
389
390void QQuickShortcut::setEnabled(QQuickShortcut::Shortcut &shortcut, bool enabled)
391{
392 if (shortcut.id)
393 QGuiApplicationPrivate::instance()->shortcutMap.setShortcutEnabled(enabled, shortcut.id, this);
394}
395
396void QQuickShortcut::setAutoRepeat(QQuickShortcut::Shortcut &shortcut, bool repeat)
397{
398 if (shortcut.id)
399 QGuiApplicationPrivate::instance()->shortcutMap.setShortcutAutoRepeat(repeat, shortcut.id, this);
400}
401
402void QQuickShortcut::grabShortcut(Shortcut &shortcut, Qt::ShortcutContext context)
403{
404 if (m_completed && !shortcut.keySequence.isEmpty()) {
405 QGuiApplicationPrivate *pApp = QGuiApplicationPrivate::instance();
406 shortcut.id = pApp->shortcutMap.addShortcut(this, shortcut.keySequence, context, *ctxMatcher());
407 if (!m_enabled)
408 pApp->shortcutMap.setShortcutEnabled(false, shortcut.id, this);
409 if (!m_autorepeat)
410 pApp->shortcutMap.setShortcutAutoRepeat(false, shortcut.id, this);
411 }
412}
413
414void QQuickShortcut::ungrabShortcut(Shortcut &shortcut)
415{
416 if (shortcut.id) {
417 QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(shortcut.id, this);
418 shortcut.id = 0;
419 }
420}
421
422QT_END_NAMESPACE
423
424#include "moc_qquickshortcut_p.cpp"
425