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 "openwnninputmethod_p.h" |
31 | #include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h> |
32 | #include <QLoggingCategory> |
33 | #include <openwnnenginejajp.h> |
34 | #include <composingtext.h> |
35 | #include <romkan.h> |
36 | #include <romkanfullkatakana.h> |
37 | #include <romkanhalfkatakana.h> |
38 | #include <QTextFormat> |
39 | |
40 | QT_BEGIN_NAMESPACE |
41 | namespace QtVirtualKeyboard { |
42 | |
43 | Q_LOGGING_CATEGORY(lcOpenWnn, "qt.virtualkeyboard.openwnn" ) |
44 | |
45 | class OpenWnnInputMethodPrivate |
46 | { |
47 | Q_DECLARE_PUBLIC(OpenWnnInputMethod) |
48 | public: |
49 | enum EngineMode { |
50 | ENGINE_MODE_DEFAULT, |
51 | ENGINE_MODE_DIRECT, |
52 | ENGINE_MODE_NO_LV2_CONV, |
53 | ENGINE_MODE_FULL_KATAKANA, |
54 | ENGINE_MODE_HALF_KATAKANA, |
55 | }; |
56 | |
57 | enum ConvertType { |
58 | CONVERT_TYPE_NONE = 0, |
59 | CONVERT_TYPE_RENBUN = 1, |
60 | }; |
61 | |
62 | enum { |
63 | MAX_COMPOSING_TEXT = 30 |
64 | }; |
65 | |
66 | OpenWnnInputMethodPrivate(OpenWnnInputMethod *q_ptr) : |
67 | q_ptr(q_ptr), |
68 | inputMode(QVirtualKeyboardInputEngine::InputMode::Latin), |
69 | exactMatchMode(false), |
70 | converter(nullptr), |
71 | converterJAJP(), |
72 | activeConvertType(CONVERT_TYPE_NONE), |
73 | preConverter(nullptr), |
74 | enableLearning(true), |
75 | enablePrediction(true), |
76 | enableConverter(true), |
77 | disableUpdate(false), |
78 | commitCount(0), |
79 | targetLayer(ComposingText::LAYER1), |
80 | activeWordIndex(-1) |
81 | { |
82 | } |
83 | |
84 | void changeEngineMode(EngineMode mode) |
85 | { |
86 | switch (mode) { |
87 | case ENGINE_MODE_DIRECT: |
88 | /* Full/Half-width number or Full-width alphabet */ |
89 | converter = nullptr; |
90 | preConverter.reset(); |
91 | break; |
92 | |
93 | case ENGINE_MODE_NO_LV2_CONV: |
94 | converter = nullptr; |
95 | preConverter.reset(other: new Romkan()); |
96 | break; |
97 | |
98 | case ENGINE_MODE_FULL_KATAKANA: |
99 | converter = nullptr; |
100 | preConverter.reset(other: new RomkanFullKatakana()); |
101 | break; |
102 | |
103 | case ENGINE_MODE_HALF_KATAKANA: |
104 | converter = nullptr; |
105 | preConverter.reset(other: new RomkanHalfKatakana()); |
106 | break; |
107 | |
108 | default: |
109 | /* HIRAGANA input mode */ |
110 | setDictionary(OpenWnnEngineJAJP::DIC_LANG_JP); |
111 | converter = &converterJAJP; |
112 | preConverter.reset(other: new Romkan()); |
113 | break; |
114 | } |
115 | } |
116 | |
117 | void setDictionary(OpenWnnEngineJAJP::DictionaryType mode) |
118 | { |
119 | converterJAJP.setDictionary(mode); |
120 | } |
121 | |
122 | void breakSequence() |
123 | { |
124 | converterJAJP.breakSequence(); |
125 | } |
126 | |
127 | bool isEnableL2Converter() |
128 | { |
129 | return converter != nullptr && enableConverter; |
130 | } |
131 | |
132 | void startConvert(ConvertType convertType) |
133 | { |
134 | if (!isEnableL2Converter()) |
135 | return; |
136 | |
137 | if (activeConvertType != convertType) { |
138 | if (!exactMatchMode) { |
139 | if (convertType == CONVERT_TYPE_RENBUN) { |
140 | /* not specify */ |
141 | composingText.setCursor(layer: ComposingText::LAYER1, pos: 0); |
142 | } else { |
143 | if (activeConvertType == CONVERT_TYPE_RENBUN) { |
144 | exactMatchMode = true; |
145 | } else { |
146 | /* specify all range */ |
147 | composingText.setCursor(layer: ComposingText::LAYER1, |
148 | pos: composingText.size(layer: ComposingText::LAYER1)); |
149 | } |
150 | } |
151 | } |
152 | |
153 | if (convertType == CONVERT_TYPE_RENBUN) |
154 | /* clears variables for the prediction */ |
155 | exactMatchMode = false; |
156 | |
157 | /* clears variables for the convert */ |
158 | commitCount = 0; |
159 | |
160 | activeConvertType = convertType; |
161 | |
162 | updateViewStatus(layer: ComposingText::LAYER2, updateCandidates: true, updateEmptyText: true); |
163 | |
164 | focusNextCandidate(); |
165 | } |
166 | } |
167 | |
168 | void changeL2Segment(const QSharedPointer<WnnWord> &word) |
169 | { |
170 | if (word.isNull()) |
171 | return; |
172 | QList<StrSegment> ss; |
173 | ss.append(t: composingText.getStrSegment(layer: ComposingText::LAYER2, pos: 0)); |
174 | if (!ss[0].clause.isNull()) |
175 | ss[0].clause->candidate = word->candidate; |
176 | ss[0].string = word->candidate; |
177 | composingText.replaceStrSegment(layer: ComposingText::LAYER2, str: ss); |
178 | if (lcOpenWnn().isDebugEnabled()) |
179 | composingText.debugout(); |
180 | updateViewStatus(layer: ComposingText::LAYER2, updateCandidates: false, updateEmptyText: false); |
181 | } |
182 | |
183 | void initializeScreen() |
184 | { |
185 | if (composingText.size(layer: ComposingText::LAYER0) != 0) { |
186 | Q_Q(OpenWnnInputMethod); |
187 | q->inputContext()->commit(text: QString()); |
188 | } |
189 | composingText.clear(); |
190 | exactMatchMode = false; |
191 | activeConvertType = CONVERT_TYPE_NONE; |
192 | clearCandidates(); |
193 | } |
194 | |
195 | void updateViewStatusForPrediction(bool updateCandidates, bool updateEmptyText) |
196 | { |
197 | activeConvertType = CONVERT_TYPE_NONE; |
198 | |
199 | updateViewStatus(layer: ComposingText::LAYER1, updateCandidates, updateEmptyText); |
200 | } |
201 | |
202 | void updateViewStatus(ComposingText::TextLayer layer, bool updateCandidates, bool updateEmptyText) |
203 | { |
204 | targetLayer = layer; |
205 | |
206 | if (updateCandidates) |
207 | updateCandidateView(); |
208 | |
209 | /* set the text for displaying as the composing text */ |
210 | displayText.clear(); |
211 | displayText.insert(i: 0, s: composingText.toString(layer)); |
212 | |
213 | /* add decoration to the text */ |
214 | if (!displayText.isEmpty() || updateEmptyText) { |
215 | |
216 | QList<QInputMethodEvent::Attribute> attributes; |
217 | |
218 | int cursor = composingText.getCursor(layer); |
219 | if (cursor != 0) { |
220 | int highlightEnd = 0; |
221 | |
222 | if (exactMatchMode) { |
223 | |
224 | QTextCharFormat textFormat; |
225 | textFormat.setBackground(QBrush(QColor(0x66, 0xCD, 0xAA))); |
226 | textFormat.setForeground(QBrush(Qt::black)); |
227 | attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, cursor, textFormat)); |
228 | highlightEnd = cursor; |
229 | |
230 | } else if (layer == ComposingText::LAYER2) { |
231 | |
232 | highlightEnd = composingText.toString(layer, from: 0, to: 0).length(); |
233 | |
234 | /* highlights the first segment */ |
235 | QTextCharFormat textFormat; |
236 | textFormat.setBackground(QBrush(QColor(0x88, 0x88, 0xFF))); |
237 | textFormat.setForeground(QBrush(Qt::black)); |
238 | attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, highlightEnd, textFormat)); |
239 | } |
240 | |
241 | if (highlightEnd != 0 && highlightEnd < displayText.length()) { |
242 | /* highlights remaining text */ |
243 | QTextCharFormat textFormat; |
244 | textFormat.setBackground(QBrush(QColor(0xF0, 0xFF, 0xFF))); |
245 | textFormat.setForeground(QBrush(Qt::black)); |
246 | attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, highlightEnd, displayText.length() - highlightEnd, textFormat)); |
247 | } |
248 | } |
249 | |
250 | QTextCharFormat textFormat; |
251 | textFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); |
252 | attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, displayText.length(), textFormat)); |
253 | |
254 | int displayCursor = composingText.toString(layer, from: 0, to: cursor - 1).length(); |
255 | attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, displayCursor, 1, QVariant())); |
256 | |
257 | Q_Q(OpenWnnInputMethod); |
258 | q->inputContext()->setPreeditText(text: displayText, attributes); |
259 | } |
260 | } |
261 | |
262 | void updateCandidateView() |
263 | { |
264 | switch (targetLayer) { |
265 | case ComposingText::LAYER0: |
266 | case ComposingText::LAYER1: /* prediction */ |
267 | if (enablePrediction) |
268 | /* update the candidates view */ |
269 | updatePrediction(); |
270 | break; |
271 | case ComposingText::LAYER2: /* convert */ |
272 | if (commitCount == 0) |
273 | converter->convert(text&: composingText); |
274 | |
275 | if (converter->makeCandidateListOf(clausePosition: commitCount) != 0) { |
276 | composingText.setCursor(layer: ComposingText::LAYER2, pos: 1); |
277 | displayCandidates(); |
278 | } else { |
279 | composingText.setCursor(layer: ComposingText::LAYER1, |
280 | pos: composingText.toString(layer: ComposingText::LAYER1).length()); |
281 | clearCandidates(); |
282 | } |
283 | break; |
284 | default: |
285 | break; |
286 | } |
287 | } |
288 | |
289 | void updatePrediction() |
290 | { |
291 | int candidates = 0; |
292 | int cursor = composingText.getCursor(layer: ComposingText::LAYER1); |
293 | if (isEnableL2Converter()) { |
294 | if (exactMatchMode) |
295 | /* exact matching */ |
296 | candidates = converter->predict(text: composingText, minLen: 0, maxLen: cursor); |
297 | else |
298 | /* normal prediction */ |
299 | candidates = converter->predict(text: composingText, minLen: 0, maxLen: -1); |
300 | } |
301 | |
302 | /* update the candidates view */ |
303 | if (candidates > 0) |
304 | displayCandidates(); |
305 | else |
306 | clearCandidates(); |
307 | } |
308 | |
309 | void displayCandidates() |
310 | { |
311 | int previousActiveWordIndex = activeWordIndex; |
312 | bool wasEmpty = candidateList.isEmpty(); |
313 | clearCandidates(deferUpdate: true); |
314 | |
315 | QSharedPointer<WnnWord> result; |
316 | while ((result = converter->getNextCandidate())) |
317 | candidateList.append(t: result); |
318 | |
319 | Q_Q(OpenWnnInputMethod); |
320 | if (!candidateList.isEmpty() || !wasEmpty) |
321 | emit q->selectionListChanged(type: QVirtualKeyboardSelectionListModel::Type::WordCandidateList); |
322 | if (previousActiveWordIndex != activeWordIndex) |
323 | emit q->selectionListActiveItemChanged(type: QVirtualKeyboardSelectionListModel::Type::WordCandidateList, index: activeWordIndex); |
324 | } |
325 | |
326 | void clearCandidates(bool deferUpdate = false) |
327 | { |
328 | if (!candidateList.isEmpty()) { |
329 | candidateList.clear(); |
330 | if (!deferUpdate) { |
331 | Q_Q(OpenWnnInputMethod); |
332 | emit q->selectionListChanged(type: QVirtualKeyboardSelectionListModel::Type::WordCandidateList); |
333 | } |
334 | clearFocusCandidate(deferUpdate); |
335 | } |
336 | } |
337 | |
338 | QSharedPointer<WnnWord> focusNextCandidate() |
339 | { |
340 | Q_Q(OpenWnnInputMethod); |
341 | if (candidateList.isEmpty()) |
342 | return QSharedPointer<WnnWord>(); |
343 | activeWordIndex++; |
344 | if (activeWordIndex >= candidateList.size()) |
345 | activeWordIndex = 0; |
346 | emit q->selectionListActiveItemChanged(type: QVirtualKeyboardSelectionListModel::Type::WordCandidateList, index: activeWordIndex); |
347 | return candidateList.at(i: activeWordIndex); |
348 | } |
349 | |
350 | void clearFocusCandidate(bool deferUpdate = false) |
351 | { |
352 | Q_Q(OpenWnnInputMethod); |
353 | if (activeWordIndex != -1) { |
354 | activeWordIndex = -1; |
355 | if (!deferUpdate) |
356 | emit q->selectionListActiveItemChanged(type: QVirtualKeyboardSelectionListModel::Type::WordCandidateList, index: activeWordIndex); |
357 | } |
358 | } |
359 | |
360 | void fitInputType() |
361 | { |
362 | Q_Q(OpenWnnInputMethod); |
363 | enableConverter = true; |
364 | |
365 | Qt::InputMethodHints inputMethodHints = q->inputContext()->inputMethodHints(); |
366 | if (inputMethodHints.testFlag(flag: Qt::ImhDigitsOnly) || |
367 | inputMethodHints.testFlag(flag: Qt::ImhFormattedNumbersOnly) || |
368 | inputMethodHints.testFlag(flag: Qt::ImhDialableCharactersOnly)) { |
369 | enableConverter = false; |
370 | } |
371 | |
372 | if (inputMethodHints.testFlag(flag: Qt::ImhLatinOnly)) { |
373 | enableConverter = false; |
374 | } |
375 | |
376 | if (inputMode != QVirtualKeyboardInputEngine::InputMode::Hiragana || |
377 | inputMethodHints.testFlag(flag: Qt::ImhHiddenText) || |
378 | inputMethodHints.testFlag(flag: Qt::ImhSensitiveData) || |
379 | inputMethodHints.testFlag(flag: Qt::ImhNoPredictiveText)) { |
380 | if (enablePrediction) { |
381 | enablePrediction = false; |
382 | emit q->selectionListsChanged(); |
383 | } |
384 | } else if (inputMode == QVirtualKeyboardInputEngine::InputMode::Hiragana && !enablePrediction) { |
385 | enablePrediction = true; |
386 | emit q->selectionListsChanged(); |
387 | } |
388 | |
389 | activeConvertType = CONVERT_TYPE_NONE; |
390 | } |
391 | |
392 | void learnWord(WnnWord &word) |
393 | { |
394 | if (enableLearning) |
395 | converter->learn(word); |
396 | } |
397 | |
398 | void learnWord(int index) |
399 | { |
400 | if (enableLearning && index < composingText.size(layer: ComposingText::LAYER2)) { |
401 | StrSegment seg = composingText.getStrSegment(layer: ComposingText::LAYER2, pos: index); |
402 | if (!seg.clause.isNull()) { |
403 | converter->learn(word&: *seg.clause); |
404 | } else { |
405 | QString stroke = composingText.toString(layer: ComposingText::LAYER1, from: seg.from, to: seg.to); |
406 | WnnWord word(seg.string, stroke); |
407 | converter->learn(word); |
408 | } |
409 | } |
410 | } |
411 | |
412 | void commitAll() |
413 | { |
414 | if (activeConvertType != CONVERT_TYPE_NONE) { |
415 | commitConvertingText(); |
416 | } else { |
417 | composingText.setCursor(layer: ComposingText::LAYER1, |
418 | pos: composingText.size(layer: ComposingText::LAYER1)); |
419 | commitText(learn: true); |
420 | } |
421 | } |
422 | |
423 | void commitConvertingText() |
424 | { |
425 | if (activeConvertType != CONVERT_TYPE_NONE) { |
426 | Q_Q(OpenWnnInputMethod); |
427 | int size = composingText.size(layer: ComposingText::LAYER2); |
428 | for (int i = 0; i < size; i++) { |
429 | learnWord(index: i); |
430 | } |
431 | |
432 | QString text = composingText.toString(layer: ComposingText::LAYER2); |
433 | disableUpdate = true; |
434 | q->inputContext()->commit(text); |
435 | disableUpdate = false; |
436 | |
437 | initializeScreen(); |
438 | } |
439 | } |
440 | |
441 | bool commitText(bool learn = false) |
442 | { |
443 | ComposingText::TextLayer layer = targetLayer; |
444 | int cursor = composingText.getCursor(layer); |
445 | if (cursor == 0) { |
446 | return false; |
447 | } |
448 | QString tmp = composingText.toString(layer, from: 0, to: cursor - 1); |
449 | |
450 | if (converter != nullptr) { |
451 | if (learn) { |
452 | if (activeConvertType == CONVERT_TYPE_RENBUN) { |
453 | learnWord(index: 0); /* select the top of the clauses */ |
454 | } else { |
455 | if (composingText.size(layer: ComposingText::LAYER1) != 0) { |
456 | QString stroke = composingText.toString(layer: ComposingText::LAYER1, from: 0, to: composingText.getCursor(layer) - 1); |
457 | WnnWord word(tmp, stroke); |
458 | learnWord(word); |
459 | } |
460 | } |
461 | } else { |
462 | breakSequence(); |
463 | } |
464 | } |
465 | return commitText(string: tmp); |
466 | } |
467 | |
468 | bool commitText(const WnnWord &word) |
469 | { |
470 | return commitText(string: word.candidate); |
471 | } |
472 | |
473 | bool commitText(const QString &string) |
474 | { |
475 | Q_Q(OpenWnnInputMethod); |
476 | ComposingText::TextLayer layer = targetLayer; |
477 | |
478 | disableUpdate = true; |
479 | q->inputContext()->commit(text: string); |
480 | disableUpdate = false; |
481 | |
482 | int cursor = composingText.getCursor(layer); |
483 | if (cursor > 0) { |
484 | composingText.deleteStrSegment(layer, from: 0, to: composingText.getCursor(layer) - 1); |
485 | composingText.setCursor(layer, pos: composingText.size(layer)); |
486 | } |
487 | exactMatchMode = false; |
488 | commitCount++; |
489 | |
490 | if ((layer == ComposingText::LAYER2) && (composingText.size(layer) == 0)) |
491 | layer = ComposingText::LAYER1; /* for connected prediction */ |
492 | |
493 | if (layer == ComposingText::LAYER2) { |
494 | activeConvertType = CONVERT_TYPE_RENBUN; |
495 | updateViewStatus(layer, updateCandidates: true, updateEmptyText: false); |
496 | focusNextCandidate(); |
497 | } else { |
498 | updateViewStatusForPrediction(updateCandidates: true, updateEmptyText: false); |
499 | } |
500 | |
501 | return composingText.size(layer: ComposingText::LAYER0) > 0; |
502 | } |
503 | |
504 | bool isAlphabetLast(const QString &str) |
505 | { |
506 | if (str.isEmpty()) |
507 | return false; |
508 | ushort ch = str.at(i: str.length() - 1).unicode(); |
509 | return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); |
510 | } |
511 | |
512 | void commitTextWithoutLastAlphabet() |
513 | { |
514 | QString last = composingText.getStrSegment(layer: targetLayer, pos: -1).string; |
515 | |
516 | if (isAlphabetLast(str: last)) { |
517 | composingText.moveCursor(layer: ComposingText::LAYER1, diff: -1); |
518 | commitText(learn: false); |
519 | composingText.moveCursor(layer: ComposingText::LAYER1, diff: 1); |
520 | } else { |
521 | commitText(learn: false); |
522 | } |
523 | } |
524 | |
525 | bool processLeftKeyEvent() |
526 | { |
527 | if (composingText.size(layer: ComposingText::LAYER1) == 0) |
528 | return false; |
529 | |
530 | if (activeConvertType != CONVERT_TYPE_NONE) { |
531 | if (composingText.getCursor(layer: ComposingText::LAYER1) > 1) { |
532 | composingText.moveCursor(layer: ComposingText::LAYER1, diff: -1); |
533 | } |
534 | } else if (exactMatchMode) { |
535 | composingText.moveCursor(layer: ComposingText::LAYER1, diff: -1); |
536 | } else { |
537 | exactMatchMode = true; |
538 | } |
539 | |
540 | if (lcOpenWnn().isDebugEnabled()) |
541 | composingText.debugout(); |
542 | |
543 | commitCount = 0; /* retry consecutive clause conversion if necessary. */ |
544 | updateViewStatus(layer: targetLayer, updateCandidates: true, updateEmptyText: true); |
545 | |
546 | if (activeConvertType != CONVERT_TYPE_NONE) |
547 | focusNextCandidate(); |
548 | |
549 | return true; |
550 | } |
551 | |
552 | bool processRightKeyEvent() |
553 | { |
554 | if (composingText.size(layer: ComposingText::LAYER1) == 0) |
555 | return false; |
556 | |
557 | ComposingText::TextLayer layer = targetLayer; |
558 | if (exactMatchMode || activeConvertType != CONVERT_TYPE_NONE) { |
559 | int textSize = composingText.size(layer: ComposingText::LAYER1); |
560 | if (composingText.getCursor(layer: ComposingText::LAYER1) == textSize) { |
561 | exactMatchMode = false; |
562 | layer = ComposingText::LAYER1; /* convert -> prediction */ |
563 | activeConvertType = CONVERT_TYPE_NONE; |
564 | } else { |
565 | composingText.moveCursor(layer: ComposingText::LAYER1, diff: 1); |
566 | } |
567 | } else { |
568 | if (composingText.getCursor(layer: ComposingText::LAYER1) < composingText.size(layer: ComposingText::LAYER1)) { |
569 | composingText.moveCursor(layer: ComposingText::LAYER1, diff: 1); |
570 | } |
571 | } |
572 | |
573 | if (lcOpenWnn().isDebugEnabled()) |
574 | composingText.debugout(); |
575 | |
576 | commitCount = 0; /* retry consecutive clause conversion if necessary. */ |
577 | |
578 | updateViewStatus(layer, updateCandidates: true, updateEmptyText: true); |
579 | |
580 | if (activeConvertType != CONVERT_TYPE_NONE) |
581 | focusNextCandidate(); |
582 | |
583 | return true; |
584 | } |
585 | |
586 | OpenWnnInputMethod *q_ptr; |
587 | QVirtualKeyboardInputEngine::InputMode inputMode; |
588 | bool exactMatchMode; |
589 | QString displayText; |
590 | OpenWnnEngineJAJP *converter; |
591 | OpenWnnEngineJAJP converterJAJP; |
592 | ConvertType activeConvertType; |
593 | ComposingText composingText; |
594 | QScopedPointer<LetterConverter> preConverter; |
595 | bool enableLearning; |
596 | bool enablePrediction; |
597 | bool enableConverter; |
598 | bool disableUpdate; |
599 | int commitCount; |
600 | ComposingText::TextLayer targetLayer; |
601 | QList<QSharedPointer<WnnWord> > candidateList; |
602 | int activeWordIndex; |
603 | }; |
604 | |
605 | /*! |
606 | \class QtVirtualKeyboard::OpenWnnInputMethod |
607 | \internal |
608 | */ |
609 | |
610 | OpenWnnInputMethod::OpenWnnInputMethod(QObject *parent) : |
611 | QVirtualKeyboardAbstractInputMethod(parent), |
612 | d_ptr(new OpenWnnInputMethodPrivate(this)) |
613 | { |
614 | } |
615 | |
616 | OpenWnnInputMethod::~OpenWnnInputMethod() |
617 | { |
618 | } |
619 | |
620 | QList<QVirtualKeyboardInputEngine::InputMode> OpenWnnInputMethod::inputModes(const QString &locale) |
621 | { |
622 | Q_UNUSED(locale) |
623 | return QList<QVirtualKeyboardInputEngine::InputMode>() |
624 | << QVirtualKeyboardInputEngine::InputMode::Hiragana |
625 | << QVirtualKeyboardInputEngine::InputMode::Katakana |
626 | << QVirtualKeyboardInputEngine::InputMode::FullwidthLatin |
627 | << QVirtualKeyboardInputEngine::InputMode::Latin; |
628 | } |
629 | |
630 | bool OpenWnnInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode) |
631 | { |
632 | Q_UNUSED(locale) |
633 | Q_D(OpenWnnInputMethod); |
634 | if (d->inputMode == inputMode) |
635 | return true; |
636 | update(); |
637 | switch (inputMode) { |
638 | case QVirtualKeyboardInputEngine::InputMode::Hiragana: |
639 | d->changeEngineMode(mode: OpenWnnInputMethodPrivate::ENGINE_MODE_DEFAULT); |
640 | break; |
641 | |
642 | case QVirtualKeyboardInputEngine::InputMode::Katakana: |
643 | d->changeEngineMode(mode: OpenWnnInputMethodPrivate::ENGINE_MODE_FULL_KATAKANA); |
644 | break; |
645 | |
646 | default: |
647 | d->changeEngineMode(mode: OpenWnnInputMethodPrivate::ENGINE_MODE_DIRECT); |
648 | break; |
649 | } |
650 | d->inputMode = inputMode; |
651 | d->fitInputType(); |
652 | return true; |
653 | } |
654 | |
655 | bool OpenWnnInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase) |
656 | { |
657 | Q_UNUSED(textCase) |
658 | return true; |
659 | } |
660 | |
661 | bool OpenWnnInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers) |
662 | { |
663 | Q_UNUSED(key) |
664 | Q_UNUSED(text) |
665 | Q_UNUSED(modifiers) |
666 | Q_D(OpenWnnInputMethod); |
667 | |
668 | if (d->preConverter == nullptr && !d->isEnableL2Converter()) |
669 | return false; |
670 | |
671 | switch (key) { |
672 | case Qt::Key_Left: |
673 | if (d->isEnableL2Converter() && d->composingText.size(layer: ComposingText::LAYER1) > 0) |
674 | return d->processLeftKeyEvent(); |
675 | else |
676 | return d->commitText(learn: false); |
677 | break; |
678 | |
679 | case Qt::Key_Right: |
680 | if (d->isEnableL2Converter() && d->composingText.size(layer: ComposingText::LAYER1) > 0) |
681 | return d->processRightKeyEvent(); |
682 | else |
683 | return d->commitText(learn: false); |
684 | break; |
685 | |
686 | case Qt::Key_Backspace: |
687 | if (d->composingText.size(layer: ComposingText::LAYER1) > 0) { |
688 | if (d->activeConvertType == OpenWnnInputMethodPrivate::CONVERT_TYPE_RENBUN) { |
689 | d->composingText.setCursor(layer: ComposingText::LAYER1, |
690 | pos: d->composingText.toString(layer: ComposingText::LAYER1).length()); |
691 | d->exactMatchMode = false; |
692 | d->clearFocusCandidate(); |
693 | } else { |
694 | if ((d->composingText.size(layer: ComposingText::LAYER1) == 1) && |
695 | d->composingText.getCursor(layer: ComposingText::LAYER1) != 0) { |
696 | d->initializeScreen(); |
697 | return true; |
698 | } else { |
699 | d->composingText.deleteAt(layer: ComposingText::LAYER1, rightside: false); |
700 | } |
701 | } |
702 | if (lcOpenWnn().isDebugEnabled()) |
703 | d->composingText.debugout(); |
704 | d->updateViewStatusForPrediction(updateCandidates: true, updateEmptyText: true); |
705 | return true; |
706 | } |
707 | break; |
708 | |
709 | case Qt::Key_Space: |
710 | if (d->composingText.size(layer: ComposingText::LAYER0) == 0) { |
711 | d->clearCandidates(); |
712 | d->breakSequence(); |
713 | } else { |
714 | if (d->targetLayer == ComposingText::LAYER2) |
715 | d->changeL2Segment(word: d->focusNextCandidate()); |
716 | else if (d->isEnableL2Converter()) |
717 | d->startConvert(convertType: OpenWnnInputMethodPrivate::CONVERT_TYPE_RENBUN); |
718 | else |
719 | return d->commitText(learn: false); |
720 | return true; |
721 | } |
722 | break; |
723 | |
724 | case Qt::Key_Return: |
725 | case Qt::Key_Enter: |
726 | if (d->composingText.size(layer: ComposingText::LAYER0) > 0) { |
727 | d->commitText(learn: true); |
728 | return true; |
729 | } |
730 | break; |
731 | |
732 | default: |
733 | if (key < Qt::Key_Escape && !text.isEmpty() && text.at(i: 0).isPrint()) { |
734 | if (d->composingText.size(layer: ComposingText::LAYER1) + text.size() > OpenWnnInputMethodPrivate::MAX_COMPOSING_TEXT) |
735 | return true; |
736 | const int last = text.size() - 1; |
737 | for (int i = 0; i <= last; ++i) { |
738 | if (d->isEnableL2Converter()) { |
739 | d->commitConvertingText(); |
740 | d->composingText.insertStrSegment(layer1: ComposingText::LAYER0, layer2: ComposingText::LAYER1, str: text.mid(position: i, n: 1)); |
741 | if (d->preConverter != nullptr) |
742 | d->preConverter->convert(text&: d->composingText); |
743 | if (i == last) |
744 | d->updateViewStatusForPrediction(updateCandidates: true, updateEmptyText: true); |
745 | } else { |
746 | d->composingText.insertStrSegment(layer1: ComposingText::LAYER0, layer2: ComposingText::LAYER1, str: text.mid(position: i, n: 1)); |
747 | QString layer1 = d->composingText.toString(layer: ComposingText::LAYER1); |
748 | if (!d->isAlphabetLast(str: layer1)) { |
749 | d->commitText(learn: false); |
750 | } else { |
751 | bool completed = d->preConverter->convert(text&: d->composingText); |
752 | if (completed) { |
753 | d->commitTextWithoutLastAlphabet(); |
754 | } else { |
755 | if (i == last) |
756 | d->updateViewStatusForPrediction(updateCandidates: true, updateEmptyText: true); |
757 | } |
758 | } |
759 | } |
760 | } |
761 | if (lcOpenWnn().isDebugEnabled()) |
762 | d->composingText.debugout(); |
763 | return true; |
764 | } |
765 | break; |
766 | } |
767 | |
768 | return false; |
769 | } |
770 | |
771 | QList<QVirtualKeyboardSelectionListModel::Type> OpenWnnInputMethod::selectionLists() |
772 | { |
773 | Q_D(OpenWnnInputMethod); |
774 | if (!d->enablePrediction) |
775 | return QList<QVirtualKeyboardSelectionListModel::Type>(); |
776 | return QList<QVirtualKeyboardSelectionListModel::Type>() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList; |
777 | } |
778 | |
779 | int OpenWnnInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type) |
780 | { |
781 | Q_UNUSED(type) |
782 | Q_D(OpenWnnInputMethod); |
783 | return d->candidateList.size(); |
784 | } |
785 | |
786 | QVariant OpenWnnInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role) |
787 | { |
788 | QVariant result; |
789 | Q_D(OpenWnnInputMethod); |
790 | switch (role) { |
791 | case QVirtualKeyboardSelectionListModel::Role::Display: |
792 | result = QVariant(d->candidateList.at(i: index)->candidate); |
793 | break; |
794 | case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength: |
795 | result.setValue(0); |
796 | break; |
797 | default: |
798 | result = QVirtualKeyboardAbstractInputMethod::selectionListData(type, index, role); |
799 | break; |
800 | } |
801 | return result; |
802 | } |
803 | |
804 | void OpenWnnInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index) |
805 | { |
806 | Q_UNUSED(type) |
807 | Q_D(OpenWnnInputMethod); |
808 | d->activeWordIndex = index; |
809 | // Set selected text as preeditText to place cursor at the end of selected text |
810 | inputContext()->setPreeditText(text: d->candidateList.at(i: index)->candidate); |
811 | d->commitText(word: *d->candidateList.at(i: index)); |
812 | } |
813 | |
814 | void OpenWnnInputMethod::reset() |
815 | { |
816 | Q_D(OpenWnnInputMethod); |
817 | d->composingText.clear(); |
818 | d->initializeScreen(); |
819 | d->fitInputType(); |
820 | } |
821 | |
822 | void OpenWnnInputMethod::update() |
823 | { |
824 | Q_D(OpenWnnInputMethod); |
825 | if (!d->disableUpdate) { |
826 | d->commitAll(); |
827 | reset(); |
828 | } |
829 | } |
830 | |
831 | } // namespace QtVirtualKeyboard |
832 | QT_END_NAMESPACE |
833 | |