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