1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtVirtualKeyboard/private/shifthandler_p.h>
5#include <QtVirtualKeyboard/private/qvirtualkeyboardinputcontext_p.h>
6#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
7#include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h>
8#include <QtCore/private/qobject_p.h>
9#include <QSet>
10#include <QGuiApplication>
11#include <QElapsedTimer>
12#include <QStyleHints>
13
14QT_BEGIN_NAMESPACE
15
16size_t qHash(QLocale::Language lang, size_t seed)
17{
18 return qHash(key: ushort(lang), seed);
19}
20
21namespace QtVirtualKeyboard {
22
23class ShiftHandlerPrivate : public QObjectPrivate
24{
25public:
26 ShiftHandlerPrivate() :
27 QObjectPrivate(),
28 inputContext(nullptr),
29 sentenceEndingCharacters(QLatin1String(".!?") + QChar(Qt::Key_exclamdown) + QChar(Qt::Key_questiondown)),
30 autoCapitalizationEnabled(false),
31 toggleShiftEnabled(false),
32 shift(false),
33 shiftChanged(false),
34 capsLock(false),
35 resetWhenVisible(false),
36 manualShiftLanguageFilter(QSet<QLocale::Language>() << QLocale::Arabic << QLocale::Persian << QLocale::Hindi << QLocale::Korean << QLocale::Thai),
37 manualCapsInputModeFilter(QSet<QVirtualKeyboardInputEngine::InputMode>() << QVirtualKeyboardInputEngine::InputMode::Cangjie << QVirtualKeyboardInputEngine::InputMode::Zhuyin << QVirtualKeyboardInputEngine::InputMode::Hebrew),
38 noAutoUppercaseInputModeFilter(QSet<QVirtualKeyboardInputEngine::InputMode>() << QVirtualKeyboardInputEngine::InputMode::FullwidthLatin << QVirtualKeyboardInputEngine::InputMode::Pinyin << QVirtualKeyboardInputEngine::InputMode::Cangjie << QVirtualKeyboardInputEngine::InputMode::Zhuyin << QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting << QVirtualKeyboardInputEngine::InputMode::JapaneseHandwriting << QVirtualKeyboardInputEngine::InputMode::KoreanHandwriting << QVirtualKeyboardInputEngine::InputMode::Romaji),
39 allCapsInputModeFilter(QSet<QVirtualKeyboardInputEngine::InputMode>() << QVirtualKeyboardInputEngine::InputMode::Hiragana << QVirtualKeyboardInputEngine::InputMode::Katakana)
40 {
41 }
42
43 QVirtualKeyboardInputContext *inputContext;
44 QString sentenceEndingCharacters;
45 bool autoCapitalizationEnabled;
46 bool toggleShiftEnabled;
47 bool shift;
48 bool shiftChanged;
49 bool capsLock;
50 bool resetWhenVisible;
51 QLocale locale;
52 QElapsedTimer timer;
53 const QSet<QLocale::Language> manualShiftLanguageFilter;
54 const QSet<QVirtualKeyboardInputEngine::InputMode> manualCapsInputModeFilter;
55 const QSet<QVirtualKeyboardInputEngine::InputMode> noAutoUppercaseInputModeFilter;
56 const QSet<QVirtualKeyboardInputEngine::InputMode> allCapsInputModeFilter;
57};
58
59/*!
60 \qmltype ShiftHandler
61 \inqmlmodule QtQuick.VirtualKeyboard
62 \ingroup qtvirtualkeyboard-internal-qml
63 \instantiates QtVirtualKeyboard::ShiftHandler
64 \brief Manages the shift state.
65*/
66
67/*!
68 \class QtVirtualKeyboard::ShiftHandler
69 \internal
70 \inmodule QtVirtualKeyboard
71 \brief Manages the shift state.
72*/
73
74ShiftHandler::ShiftHandler(QVirtualKeyboardInputContext *parent) :
75 QObject(*new ShiftHandlerPrivate(), parent)
76{
77 Q_D(ShiftHandler);
78 d->inputContext = parent;
79}
80
81void ShiftHandler::init()
82{
83 Q_D(ShiftHandler);
84 connect(asender: d->inputContext, SIGNAL(inputMethodHintsChanged()), SLOT(restart()));
85 connect(asender: d->inputContext->priv(), SIGNAL(inputItemChanged()), SLOT(restart()));
86 connect(asender: d->inputContext->inputEngine(), SIGNAL(inputModeChanged()), SLOT(restart()));
87 connect(asender: d->inputContext, SIGNAL(preeditTextChanged()), SLOT(autoCapitalize()));
88 connect(asender: d->inputContext, SIGNAL(surroundingTextChanged()), SLOT(autoCapitalize()));
89 connect(asender: d->inputContext, SIGNAL(cursorPositionChanged()), SLOT(autoCapitalize()));
90 connect(asender: d->inputContext, SIGNAL(localeChanged()), SLOT(localeChanged()));
91 connect(qGuiApp->inputMethod(), SIGNAL(visibleChanged()), SLOT(inputMethodVisibleChanged()));
92 d->locale = QLocale(d->inputContext->locale());
93}
94
95/*!
96 \internal
97*/
98ShiftHandler::~ShiftHandler()
99{
100
101}
102
103QString ShiftHandler::sentenceEndingCharacters() const
104{
105 Q_D(const ShiftHandler);
106 return d->sentenceEndingCharacters;
107}
108
109void ShiftHandler::setSentenceEndingCharacters(const QString &value)
110{
111 Q_D(ShiftHandler);
112 if (d->sentenceEndingCharacters != value) {
113 d->sentenceEndingCharacters = value;
114 autoCapitalize();
115 emit sentenceEndingCharactersChanged();
116 }
117}
118
119bool ShiftHandler::isAutoCapitalizationEnabled() const
120{
121 Q_D(const ShiftHandler);
122 return d->autoCapitalizationEnabled;
123}
124
125bool ShiftHandler::isToggleShiftEnabled() const
126{
127 Q_D(const ShiftHandler);
128 return d->toggleShiftEnabled;
129}
130
131bool ShiftHandler::isShiftActive() const
132{
133 Q_D(const ShiftHandler);
134 return d->shift;
135}
136
137void ShiftHandler::setShiftActive(bool active)
138{
139 Q_D(ShiftHandler);
140 if (d->shift != active) {
141 d->shift = active;
142 d->shiftChanged = true;
143 emit shiftActiveChanged();
144 if (!d->capsLock)
145 emit uppercaseChanged();
146 }
147}
148
149bool ShiftHandler::isCapsLockActive() const
150{
151 Q_D(const ShiftHandler);
152 return d->capsLock;
153}
154
155void ShiftHandler::setCapsLockActive(bool active)
156{
157 Q_D(ShiftHandler);
158 if (d->capsLock != active) {
159 d->capsLock = active;
160 emit capsLockActiveChanged();
161 if (!d->shift)
162 emit uppercaseChanged();
163 }
164}
165
166bool ShiftHandler::isUppercase() const
167{
168 Q_D(const ShiftHandler);
169 return d->shift || d->capsLock;
170}
171
172/*!
173 \since 1.2
174
175 \qmlmethod void ShiftHandler::toggleShift()
176
177 Toggles the current shift state.
178
179 This method provides the functionality of the shift key.
180
181 \sa toggleShiftEnabled
182*/
183/*!
184 \since 1.2
185 \internal
186
187 \fn void QtVirtualKeyboard::ShiftHandler::toggleShift()
188
189 Toggles the current shift state.
190
191 This method provides the functionality of the shift key.
192
193 \sa toggleShiftEnabled
194*/
195void ShiftHandler::toggleShift()
196{
197 Q_D(ShiftHandler);
198 if (!d->toggleShiftEnabled)
199 return;
200 if (d->manualShiftLanguageFilter.contains(value: d->locale.language())) {
201 setCapsLockActive(false);
202 setShiftActive(!d->shift);
203 } else if (d->manualCapsInputModeFilter.contains(value: d->inputContext->inputEngine()->inputMode())) {
204 bool capsLock = d->capsLock;
205 setCapsLockActive(!capsLock);
206 setShiftActive(!capsLock);
207 } else {
208 if (d->capsLock) {
209 setCapsLockActive(!d->capsLock && d->shift && !d->shiftChanged);
210 }
211
212 QStyleHints *style = QGuiApplication::styleHints();
213
214 if (!d->timer.isValid() || d->timer.elapsed() > style->mouseDoubleClickInterval()) {
215 d->timer.restart();
216 } else if (d->timer.elapsed() < style->mouseDoubleClickInterval() && !d->capsLock) {
217 setCapsLockActive(!d->capsLock && d->shift && !d->shiftChanged);
218 }
219
220 setShiftActive(d->capsLock || !d->shift);
221 d->shiftChanged = false;
222 }
223}
224
225/*! Clears the toggle shift timer.
226 \internal
227
228*/
229void ShiftHandler::clearToggleShiftTimer()
230{
231 Q_D(ShiftHandler);
232 d->timer.invalidate();
233}
234
235void ShiftHandler::reset()
236{
237 Q_D(ShiftHandler);
238 if (d->inputContext->priv()->inputItem() || QT_VIRTUALKEYBOARD_FORCE_EVENTS_WITHOUT_FOCUS) {
239 Qt::InputMethodHints inputMethodHints = d->inputContext->inputMethodHints();
240 QVirtualKeyboardInputEngine::InputMode inputMode = d->inputContext->inputEngine()->inputMode();
241 bool preferUpperCase = (inputMethodHints & (Qt::ImhPreferUppercase | Qt::ImhUppercaseOnly));
242 bool autoCapitalizationEnabled = !(d->inputContext->inputMethodHints() & (Qt::ImhNoAutoUppercase |
243 Qt::ImhUppercaseOnly | Qt::ImhLowercaseOnly | Qt::ImhEmailCharactersOnly |
244 Qt::ImhUrlCharactersOnly | Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly |
245 Qt::ImhDigitsOnly)) && !d->noAutoUppercaseInputModeFilter.contains(value: inputMode);
246 bool toggleShiftEnabled = !(inputMethodHints & (Qt::ImhUppercaseOnly | Qt::ImhLowercaseOnly));
247 // For filtered languages reset the initial shift status to lower case
248 // and allow manual shift change
249 if (d->manualShiftLanguageFilter.contains(value: d->locale.language()) ||
250 d->manualCapsInputModeFilter.contains(value: inputMode)) {
251 preferUpperCase = false;
252 autoCapitalizationEnabled = false;
253 toggleShiftEnabled = true;
254 } else if (d->allCapsInputModeFilter.contains(value: inputMode)) {
255 preferUpperCase = true;
256 autoCapitalizationEnabled = false;
257 toggleShiftEnabled = false;
258 }
259 setToggleShiftEnabled(toggleShiftEnabled);
260 setAutoCapitalizationEnabled(autoCapitalizationEnabled);
261 setCapsLockActive(preferUpperCase);
262 if (preferUpperCase)
263 setShiftActive(preferUpperCase);
264 else
265 autoCapitalize();
266 }
267}
268
269void ShiftHandler::autoCapitalize()
270{
271 Q_D(ShiftHandler);
272 if (d->capsLock)
273 return;
274 if (!d->autoCapitalizationEnabled || !d->inputContext->preeditText().isEmpty()) {
275 setShiftActive(false);
276 } else {
277 int cursorPosition = d->inputContext->cursorPosition();
278 bool preferLowerCase = d->inputContext->inputMethodHints() & Qt::ImhPreferLowercase;
279 if (cursorPosition == 0) {
280 setShiftActive(!preferLowerCase);
281 } else { // space after sentence-ending character triggers auto-capitalization
282 QString text = d->inputContext->surroundingText();
283 text.truncate(pos: cursorPosition);
284 if (text.trimmed().size() == 0)
285 setShiftActive(!preferLowerCase);
286 else if (text.endsWith(c: QLatin1Char(' ')))
287 setShiftActive(d->sentenceEndingCharacters.contains(c: QStringView{text}.right(n: 2)[0])
288 && !preferLowerCase);
289 else
290 setShiftActive(false);
291 }
292 }
293}
294
295void ShiftHandler::restart()
296{
297 Q_D(ShiftHandler);
298 const QGuiApplication *app = qGuiApp;
299 if (!app || !app->inputMethod()->isVisible()) {
300 d->resetWhenVisible = true;
301 return;
302 }
303 reset();
304}
305
306void ShiftHandler::localeChanged()
307{
308 Q_D(ShiftHandler);
309 d->locale = QLocale(d->inputContext->locale());
310 restart();
311}
312
313void ShiftHandler::inputMethodVisibleChanged()
314{
315 Q_D(ShiftHandler);
316 if (!d->resetWhenVisible)
317 return;
318
319 const QGuiApplication *app = qGuiApp;
320 if (app && app->inputMethod()->isVisible()) {
321 d->resetWhenVisible = false;
322 reset();
323 }
324}
325
326void ShiftHandler::setAutoCapitalizationEnabled(bool enabled)
327{
328 Q_D(ShiftHandler);
329 if (d->autoCapitalizationEnabled != enabled) {
330 d->autoCapitalizationEnabled = enabled;
331 emit autoCapitalizationEnabledChanged();
332 }
333}
334
335void ShiftHandler::setToggleShiftEnabled(bool enabled)
336{
337 Q_D(ShiftHandler);
338 if (d->toggleShiftEnabled != enabled) {
339 d->toggleShiftEnabled = enabled;
340 emit toggleShiftEnabledChanged();
341 }
342}
343
344/*!
345 \property QtVirtualKeyboard::ShiftHandler::sentenceEndingCharacters
346
347 This property specifies the sentence ending characters which
348 will cause shift state change.
349
350 By default, the property is initialized to sentence
351 ending characters found in the ASCII range (i.e. ".!?").
352*/
353
354/*!
355 \qmlproperty string ShiftHandler::sentenceEndingCharacters
356
357 This property specifies the sentence ending characters which
358 will cause shift state change.
359
360 By default, the property is initialized to sentence
361 ending characters found in the ASCII range (i.e. ".!?").
362*/
363
364/*!
365 \since 1.2
366
367 \property QtVirtualKeyboard::ShiftHandler::autoCapitalizationEnabled
368
369 This property provides the current state of the automatic
370 capitalization feature.
371*/
372
373/*!
374 \since 1.2
375
376 \qmlproperty bool ShiftHandler::autoCapitalizationEnabled
377
378 This property provides the current state of the automatic
379 capitalization feature.
380*/
381
382/*!
383 \since 1.2
384
385 \property QtVirtualKeyboard::ShiftHandler::toggleShiftEnabled
386
387 This property provides the current state of the toggleShift()
388 method. When true, the current shift state can be changed by
389 calling the toggleShift() method.
390*/
391
392/*!
393 \since 1.2
394
395 \qmlproperty bool ShiftHandler::toggleShiftEnabled
396
397 This property provides the current state of the toggleShift()
398 method. When true, the current shift state can be changed by
399 calling the toggleShift() method.
400*/
401
402} // namespace QtVirtualKeyboard
403QT_END_NAMESPACE
404

source code of qtvirtualkeyboard/src/virtualkeyboard/shifthandler.cpp