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

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