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
40QT_BEGIN_NAMESPACE
41namespace QtVirtualKeyboard {
42
43class ShiftHandlerPrivate : public QObjectPrivate
44{
45public:
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
94ShiftHandler::ShiftHandler(QVirtualKeyboardInputContext *parent) :
95 QObject(*new ShiftHandlerPrivate(), parent)
96{
97 Q_D(ShiftHandler);
98 d->inputContext = parent;
99}
100
101void 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*/
118ShiftHandler::~ShiftHandler()
119{
120
121}
122
123QString ShiftHandler::sentenceEndingCharacters() const
124{
125 Q_D(const ShiftHandler);
126 return d->sentenceEndingCharacters;
127}
128
129void 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
139bool ShiftHandler::isAutoCapitalizationEnabled() const
140{
141 Q_D(const ShiftHandler);
142 return d->autoCapitalizationEnabled;
143}
144
145bool ShiftHandler::isToggleShiftEnabled() const
146{
147 Q_D(const ShiftHandler);
148 return d->toggleShiftEnabled;
149}
150
151bool ShiftHandler::isShiftActive() const
152{
153 Q_D(const ShiftHandler);
154 return d->shift;
155}
156
157void 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
169bool ShiftHandler::isCapsLockActive() const
170{
171 Q_D(const ShiftHandler);
172 return d->capsLock;
173}
174
175void 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
186bool 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*/
215void 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*/
249void ShiftHandler::clearToggleShiftTimer()
250{
251 Q_D(ShiftHandler);
252 d->timer.invalidate();
253}
254
255void 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
289void 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
315void 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
326void ShiftHandler::localeChanged()
327{
328 Q_D(ShiftHandler);
329 d->locale = QLocale(d->inputContext->locale());
330 restart();
331}
332
333void 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
346void ShiftHandler::setAutoCapitalizationEnabled(bool enabled)
347{
348 Q_D(ShiftHandler);
349 if (d->autoCapitalizationEnabled != enabled) {
350 d->autoCapitalizationEnabled = enabled;
351 emit autoCapitalizationEnabledChanged();
352 }
353}
354
355void 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
423QT_END_NAMESPACE
424

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