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 "pinyininputmethod_p.h"
31#include "pinyindecoderservice_p.h"
32#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
33#include <QLoggingCategory>
34
35QT_BEGIN_NAMESPACE
36namespace QtVirtualKeyboard {
37
38Q_LOGGING_CATEGORY(lcPinyin, "qt.virtualkeyboard.pinyin")
39
40class PinyinInputMethodPrivate
41{
42 Q_DECLARE_PUBLIC(PinyinInputMethod)
43
44public:
45 enum State
46 {
47 Idle,
48 Input,
49 Predict
50 };
51
52 PinyinInputMethodPrivate(PinyinInputMethod *q_ptr) :
53 q_ptr(q_ptr),
54 inputMode(QVirtualKeyboardInputEngine::InputMode::Pinyin),
55 pinyinDecoderService(PinyinDecoderService::getInstance()),
56 state(Idle),
57 surface(),
58 totalChoicesNum(0),
59 candidatesList(),
60 fixedLen(0),
61 composingStr(),
62 activeCmpsLen(0),
63 finishSelection(true),
64 posDelSpl(-1),
65 isPosInSpl(false)
66 {
67 }
68
69 void resetToIdleState()
70 {
71 Q_Q(PinyinInputMethod);
72
73 QVirtualKeyboardInputContext *inputContext = q->inputContext();
74
75 // Disable the user dictionary when entering sensitive data
76 if (inputContext && pinyinDecoderService) {
77 bool userDictionaryEnabled = !inputContext->inputMethodHints().testFlag(flag: Qt::ImhSensitiveData);
78 if (userDictionaryEnabled != pinyinDecoderService->isUserDictionaryEnabled())
79 pinyinDecoderService->setUserDictionary(userDictionaryEnabled);
80 }
81
82 if (state == Idle)
83 return;
84
85 state = Idle;
86 surface.clear();
87 fixedLen = 0;
88 finishSelection = true;
89 composingStr.clear();
90 if (inputContext)
91 inputContext->setPreeditText(text: QString());
92 activeCmpsLen = 0;
93 posDelSpl = -1;
94 isPosInSpl = false;
95
96 resetCandidates();
97 }
98
99 bool addSpellingChar(QChar ch, bool reset)
100 {
101 if (reset) {
102 surface.clear();
103 pinyinDecoderService->resetSearch();
104 }
105 if (ch == u'\'') {
106 if (surface.isEmpty())
107 return false;
108 if (surface.endsWith(c: ch))
109 return true;
110 }
111 surface.append(c: ch);
112 return true;
113 }
114
115 bool removeSpellingChar()
116 {
117 if (surface.isEmpty())
118 return false;
119 QVector<int> splStart = pinyinDecoderService->spellingStartPositions();
120 isPosInSpl = (surface.length() <= splStart[fixedLen + 1]);
121 posDelSpl = isPosInSpl ? fixedLen - 1 : surface.length() - 1;
122 return true;
123 }
124
125 void chooseAndUpdate(int candId)
126 {
127 Q_Q(PinyinInputMethod);
128
129 if (state == Predict)
130 choosePredictChoice(choiceId: candId);
131 else
132 chooseDecodingCandidate(candId);
133
134 if (composingStr.length() > 0) {
135 if ((candId >= 0 || finishSelection) && composingStr.length() == fixedLen) {
136 QString resultStr = getComposingStrActivePart();
137 q->inputContext()->commit(text: resultStr);
138 tryPredict();
139 } else if (state == Idle) {
140 state = Input;
141 }
142 } else {
143 tryPredict();
144 }
145 }
146
147 bool chooseAndFinish()
148 {
149 if (state == Predict || !totalChoicesNum)
150 return false;
151
152 chooseAndUpdate(candId: 0);
153 if (state != Predict && totalChoicesNum > 0)
154 chooseAndUpdate(candId: 0);
155
156 return true;
157 }
158
159 int candidatesCount()
160 {
161 return totalChoicesNum;
162 }
163
164 QString candidateAt(int index)
165 {
166 if (index < 0 || index >= totalChoicesNum)
167 return QString();
168 if (index >= candidatesList.size()) {
169 int fetchMore = qMin(a: index + 20, b: totalChoicesNum - candidatesList.size());
170 candidatesList.append(t: pinyinDecoderService->fetchCandidates(index: candidatesList.size(), count: fetchMore, sentFixedLen: fixedLen));
171 if (index == 0 && totalChoicesNum == 1) {
172 int surfaceDecodedLen = pinyinDecoderService->pinyinStringLength(decoded: true);
173 if (surfaceDecodedLen < surface.length())
174 candidatesList[0] = candidatesList[0] + surface.mid(position: surfaceDecodedLen).toLower();
175 }
176 }
177 return index < candidatesList.size() ? candidatesList[index] : QString();
178 }
179
180 void chooseDecodingCandidate(int candId)
181 {
182 Q_Q(PinyinInputMethod);
183 Q_ASSERT(state != Predict);
184
185 int result = 0;
186 if (candId < 0) {
187 if (surface.length() > 0) {
188 if (posDelSpl < 0) {
189 result = pinyinDecoderService->search(spelling: surface);
190 } else {
191 result = pinyinDecoderService->deleteSearch(pos: posDelSpl, isPosInSpellingId: isPosInSpl, clearFixedInThisStep: false);
192 posDelSpl = -1;
193 }
194 }
195 } else {
196 if (totalChoicesNum > 1) {
197 result = pinyinDecoderService->chooceCandidate(index: candId);
198 } else {
199 QString resultStr;
200 if (totalChoicesNum == 1) {
201 QString undecodedStr = candId < candidatesList.length() ? candidatesList.at(i: candId) : QString();
202 resultStr = pinyinDecoderService->candidateAt(index: 0).mid(position: 0, n: fixedLen) + undecodedStr;
203 }
204 resetToIdleState();
205 if (!resultStr.isEmpty())
206 q->inputContext()->commit(text: resultStr);
207 return;
208 }
209 }
210
211 resetCandidates();
212 totalChoicesNum = result;
213
214 surface = pinyinDecoderService->pinyinString(decoded: false);
215 QVector<int> splStart = pinyinDecoderService->spellingStartPositions();
216 QString fullSent = pinyinDecoderService->candidateAt(index: 0);
217 fixedLen = pinyinDecoderService->fixedLength();
218 composingStr = fullSent.mid(position: 0, n: fixedLen) + surface.mid(position: splStart[fixedLen + 1]);
219 activeCmpsLen = composingStr.length();
220
221 // Prepare the display string.
222 QString composingStrDisplay;
223 int surfaceDecodedLen = pinyinDecoderService->pinyinStringLength(decoded: true);
224 if (!surfaceDecodedLen) {
225 composingStrDisplay = composingStr.toLower();
226 if (!totalChoicesNum)
227 totalChoicesNum = 1;
228 } else {
229 activeCmpsLen = activeCmpsLen - (surface.length() - surfaceDecodedLen);
230 composingStrDisplay = fullSent.mid(position: 0, n: fixedLen);
231 for (int pos = fixedLen + 1; pos < splStart.size() - 1; pos++) {
232 composingStrDisplay += surface.mid(position: splStart[pos], n: splStart[pos + 1] - splStart[pos]);
233 if (splStart[pos + 1] < surfaceDecodedLen)
234 composingStrDisplay += QLatin1String(" ");
235 }
236 if (surfaceDecodedLen < surface.length())
237 composingStrDisplay += surface.mid(position: surfaceDecodedLen);
238 }
239 q->inputContext()->setPreeditText(text: composingStrDisplay);
240
241 finishSelection = splStart.size() == (fixedLen + 2);
242 if (!finishSelection)
243 candidateAt(index: 0);
244 }
245
246 void choosePredictChoice(int choiceId)
247 {
248 Q_ASSERT(state == Predict);
249
250 if (choiceId < 0 || choiceId >= totalChoicesNum)
251 return;
252
253 QString tmp = candidatesList.at(i: choiceId);
254
255 resetCandidates();
256
257 candidatesList.append(t: tmp);
258 totalChoicesNum = 1;
259
260 surface.clear();
261 fixedLen = tmp.length();
262 composingStr = tmp;
263 activeCmpsLen = fixedLen;
264
265 finishSelection = true;
266 }
267
268 QString getComposingStrActivePart()
269 {
270 return composingStr.mid(position: 0, n: activeCmpsLen);
271 }
272
273 void resetCandidates()
274 {
275 candidatesList.clear();
276 if (totalChoicesNum) {
277 totalChoicesNum = 0;
278 }
279 }
280
281 void updateCandidateList()
282 {
283 Q_Q(PinyinInputMethod);
284 emit q->selectionListChanged(type: QVirtualKeyboardSelectionListModel::Type::WordCandidateList);
285 emit q->selectionListActiveItemChanged(type: QVirtualKeyboardSelectionListModel::Type::WordCandidateList,
286 index: totalChoicesNum > 0 && state == PinyinInputMethodPrivate::Input ? 0 : -1);
287 }
288
289 bool canDoPrediction()
290 {
291 Q_Q(PinyinInputMethod);
292 QVirtualKeyboardInputContext *inputContext = q->inputContext();
293 return inputMode == QVirtualKeyboardInputEngine::InputMode::Pinyin &&
294 composingStr.length() == fixedLen &&
295 inputContext &&
296 !inputContext->inputMethodHints().testFlag(flag: Qt::ImhNoPredictiveText);
297 }
298
299 void tryPredict()
300 {
301 // Try to get the prediction list.
302 if (canDoPrediction()) {
303 Q_Q(PinyinInputMethod);
304 if (state != Predict)
305 resetToIdleState();
306 QVirtualKeyboardInputContext *inputContext = q->inputContext();
307 int cursorPosition = inputContext->cursorPosition();
308 int historyStart = qMax(a: 0, b: cursorPosition - 3);
309 QString history = inputContext->surroundingText().mid(position: historyStart, n: cursorPosition - historyStart);
310 candidatesList = pinyinDecoderService->predictionList(history);
311 totalChoicesNum = candidatesList.size();
312 finishSelection = false;
313 state = Predict;
314 } else {
315 resetCandidates();
316 }
317
318 if (!candidatesCount())
319 resetToIdleState();
320 }
321
322 PinyinInputMethod *q_ptr;
323 QVirtualKeyboardInputEngine::InputMode inputMode;
324 QPointer<PinyinDecoderService> pinyinDecoderService;
325 State state;
326 QString surface;
327 int totalChoicesNum;
328 QList<QString> candidatesList;
329 int fixedLen;
330 QString composingStr;
331 int activeCmpsLen;
332 bool finishSelection;
333 int posDelSpl;
334 bool isPosInSpl;
335};
336
337class ScopedCandidateListUpdate
338{
339 Q_DISABLE_COPY(ScopedCandidateListUpdate)
340public:
341 inline explicit ScopedCandidateListUpdate(PinyinInputMethodPrivate *d) :
342 d(d),
343 candidatesList(d->candidatesList),
344 totalChoicesNum(d->totalChoicesNum),
345 state(d->state)
346 {
347 }
348
349 inline ~ScopedCandidateListUpdate()
350 {
351 if (totalChoicesNum != d->totalChoicesNum || state != d->state || candidatesList != d->candidatesList)
352 d->updateCandidateList();
353 }
354
355private:
356 PinyinInputMethodPrivate *d;
357 QList<QString> candidatesList;
358 int totalChoicesNum;
359 PinyinInputMethodPrivate::State state;
360};
361
362/*!
363 \class QtVirtualKeyboard::PinyinInputMethod
364 \internal
365*/
366
367PinyinInputMethod::PinyinInputMethod(QObject *parent) :
368 QVirtualKeyboardAbstractInputMethod(parent),
369 d_ptr(new PinyinInputMethodPrivate(this))
370{
371}
372
373PinyinInputMethod::~PinyinInputMethod()
374{
375}
376
377QList<QVirtualKeyboardInputEngine::InputMode> PinyinInputMethod::inputModes(const QString &locale)
378{
379 Q_UNUSED(locale)
380 Q_D(PinyinInputMethod);
381 QList<QVirtualKeyboardInputEngine::InputMode> result;
382 if (d->pinyinDecoderService)
383 result << QVirtualKeyboardInputEngine::InputMode::Pinyin;
384 result << QVirtualKeyboardInputEngine::InputMode::Latin;
385 return result;
386}
387
388bool PinyinInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode)
389{
390 Q_UNUSED(locale)
391 Q_D(PinyinInputMethod);
392 reset();
393 if (inputMode == QVirtualKeyboardInputEngine::InputMode::Pinyin && !d->pinyinDecoderService)
394 return false;
395 d->inputMode = inputMode;
396 return true;
397}
398
399bool PinyinInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase)
400{
401 Q_UNUSED(textCase)
402 return true;
403}
404
405bool PinyinInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers)
406{
407 Q_UNUSED(modifiers)
408 Q_D(PinyinInputMethod);
409 if (d->inputMode == QVirtualKeyboardInputEngine::InputMode::Pinyin) {
410 ScopedCandidateListUpdate scopedCandidateListUpdate(d);
411 Q_UNUSED(scopedCandidateListUpdate)
412 if ((key >= Qt::Key_A && key <= Qt::Key_Z) || (key == Qt::Key_Apostrophe)) {
413 if (d->state == PinyinInputMethodPrivate::Predict)
414 d->resetToIdleState();
415 if (d->addSpellingChar(ch: text.at(i: 0), reset: d->state == PinyinInputMethodPrivate::Idle)) {
416 d->chooseAndUpdate(candId: -1);
417 return true;
418 }
419 } else if (key == Qt::Key_Space) {
420 if (d->state != PinyinInputMethodPrivate::Predict && d->candidatesCount() > 0) {
421 d->chooseAndUpdate(candId: 0);
422 return true;
423 }
424 } else if (key == Qt::Key_Return) {
425 if (d->state != PinyinInputMethodPrivate::Predict && d->candidatesCount() > 0) {
426 QString surface = d->surface;
427 d->resetToIdleState();
428 inputContext()->commit(text: surface);
429 return true;
430 }
431 } else if (key == Qt::Key_Backspace) {
432 if (d->removeSpellingChar()) {
433 d->chooseAndUpdate(candId: -1);
434 return true;
435 }
436 } else if (!text.isEmpty()) {
437 d->chooseAndFinish();
438 }
439 }
440 return false;
441}
442
443QList<QVirtualKeyboardSelectionListModel::Type> PinyinInputMethod::selectionLists()
444{
445 return QList<QVirtualKeyboardSelectionListModel::Type>() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList;
446}
447
448int PinyinInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type)
449{
450 Q_UNUSED(type)
451 Q_D(PinyinInputMethod);
452 return d->candidatesCount();
453}
454
455QVariant PinyinInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role)
456{
457 QVariant result;
458 Q_UNUSED(type)
459 Q_D(PinyinInputMethod);
460 switch (role) {
461 case QVirtualKeyboardSelectionListModel::Role::Display:
462 result = QVariant(d->candidateAt(index));
463 break;
464 case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength:
465 result.setValue(0);
466 break;
467 default:
468 result = QVirtualKeyboardAbstractInputMethod::selectionListData(type, index, role);
469 break;
470 }
471 return result;
472}
473
474void PinyinInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index)
475{
476 Q_UNUSED(type)
477 Q_D(PinyinInputMethod);
478 ScopedCandidateListUpdate scopedCandidateListUpdate(d);
479 Q_UNUSED(scopedCandidateListUpdate)
480 d->chooseAndUpdate(candId: index);
481}
482
483void PinyinInputMethod::reset()
484{
485 Q_D(PinyinInputMethod);
486 ScopedCandidateListUpdate scopedCandidateListUpdate(d);
487 Q_UNUSED(scopedCandidateListUpdate)
488 d->resetToIdleState();
489}
490
491void PinyinInputMethod::update()
492{
493 Q_D(PinyinInputMethod);
494 ScopedCandidateListUpdate scopedCandidateListUpdate(d);
495 Q_UNUSED(scopedCandidateListUpdate)
496 d->chooseAndFinish();
497 d->tryPredict();
498}
499
500} // namespace QtVirtualKeyboard
501QT_END_NAMESPACE
502

source code of qtvirtualkeyboard/src/plugins/pinyin/plugin/pinyininputmethod.cpp