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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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