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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | using namespace Qt::StringLiterals; |
17 | |
18 | size_t qHash(QLocale::Language lang, size_t seed) |
19 | { |
20 | return qHash(key: ushort(lang), seed); |
21 | } |
22 | |
23 | namespace QtVirtualKeyboard { |
24 | |
25 | class ShiftHandlerPrivate : public QObjectPrivate |
26 | { |
27 | public: |
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 | |
76 | ShiftHandler::ShiftHandler(QVirtualKeyboardInputContext *parent) : |
77 | QObject(*new ShiftHandlerPrivate(), parent) |
78 | { |
79 | Q_D(ShiftHandler); |
80 | d->inputContext = parent; |
81 | } |
82 | |
83 | void 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 | */ |
100 | ShiftHandler::~ShiftHandler() |
101 | { |
102 | |
103 | } |
104 | |
105 | QString ShiftHandler::sentenceEndingCharacters() const |
106 | { |
107 | Q_D(const ShiftHandler); |
108 | return d->sentenceEndingCharacters; |
109 | } |
110 | |
111 | void 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 | |
121 | bool ShiftHandler::isAutoCapitalizationEnabled() const |
122 | { |
123 | Q_D(const ShiftHandler); |
124 | return d->autoCapitalizationEnabled; |
125 | } |
126 | |
127 | bool ShiftHandler::isToggleShiftEnabled() const |
128 | { |
129 | Q_D(const ShiftHandler); |
130 | return d->toggleShiftEnabled; |
131 | } |
132 | |
133 | bool ShiftHandler::isShiftActive() const |
134 | { |
135 | Q_D(const ShiftHandler); |
136 | return d->shift; |
137 | } |
138 | |
139 | void 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 | |
151 | bool ShiftHandler::isCapsLockActive() const |
152 | { |
153 | Q_D(const ShiftHandler); |
154 | return d->capsLock; |
155 | } |
156 | |
157 | void 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 | |
168 | bool 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 | */ |
197 | void 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 | */ |
231 | void ShiftHandler::clearToggleShiftTimer() |
232 | { |
233 | Q_D(ShiftHandler); |
234 | d->timer.invalidate(); |
235 | } |
236 | |
237 | void 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 | |
271 | void 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 | |
297 | void 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 | |
308 | void ShiftHandler::localeChanged() |
309 | { |
310 | Q_D(ShiftHandler); |
311 | d->locale = QLocale(d->inputContext->locale()); |
312 | restart(); |
313 | } |
314 | |
315 | void 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 | |
328 | void ShiftHandler::setAutoCapitalizationEnabled(bool enabled) |
329 | { |
330 | Q_D(ShiftHandler); |
331 | if (d->autoCapitalizationEnabled != enabled) { |
332 | d->autoCapitalizationEnabled = enabled; |
333 | emit autoCapitalizationEnabledChanged(); |
334 | } |
335 | } |
336 | |
337 | void 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 |
405 | QT_END_NAMESPACE |
406 |
Definitions
- qHash
- ShiftHandlerPrivate
- ShiftHandlerPrivate
- ShiftHandler
- init
- ~ShiftHandler
- sentenceEndingCharacters
- setSentenceEndingCharacters
- isAutoCapitalizationEnabled
- isToggleShiftEnabled
- isShiftActive
- setShiftActive
- isCapsLockActive
- setCapsLockActive
- isUppercase
- toggleShift
- clearToggleShiftTimer
- reset
- autoCapitalize
- restart
- localeChanged
- inputMethodVisibleChanged
- setAutoCapitalizationEnabled
Learn Advanced QML with KDAB
Find out more