/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include QT_BEGIN_NAMESPACE namespace QtVirtualKeyboard { Q_LOGGING_CATEGORY(lcHunspell, "qt.virtualkeyboard.hunspell") /*! \class QtVirtualKeyboard::HunspellInputMethod \internal */ HunspellInputMethod::HunspellInputMethod(HunspellInputMethodPrivate *d_ptr, QObject *parent) : QVirtualKeyboardAbstractInputMethod(parent), d_ptr(d_ptr) { } HunspellInputMethod::HunspellInputMethod(QObject *parent) : QVirtualKeyboardAbstractInputMethod(parent), d_ptr(new HunspellInputMethodPrivate(this)) { } HunspellInputMethod::~HunspellInputMethod() { } QList HunspellInputMethod::inputModes(const QString &locale) { QList result; switch (QLocale(locale).script()) { case QLocale::GreekScript: result.append(QVirtualKeyboardInputEngine::InputMode::Greek); break; case QLocale::CyrillicScript: result.append(QVirtualKeyboardInputEngine::InputMode::Cyrillic); break; case QLocale::ArabicScript: result.append(QVirtualKeyboardInputEngine::InputMode::Arabic); break; case QLocale::HebrewScript: result.append(QVirtualKeyboardInputEngine::InputMode::Hebrew); break; default: break; } result.append(QVirtualKeyboardInputEngine::InputMode::Latin); result.append(QVirtualKeyboardInputEngine::InputMode::Numeric); return result; } bool HunspellInputMethod::setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode) { Q_UNUSED(inputMode) Q_D(HunspellInputMethod); return d->createHunspell(locale); } bool HunspellInputMethod::setTextCase(QVirtualKeyboardInputEngine::TextCase textCase) { Q_UNUSED(textCase) return true; } bool HunspellInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers) { Q_D(HunspellInputMethod); QVirtualKeyboardInputContext *ic = inputContext(); Qt::InputMethodHints inputMethodHints = ic->inputMethodHints(); bool accept = false; switch (key) { case Qt::Key_Enter: case Qt::Key_Return: case Qt::Key_Tab: case Qt::Key_Space: update(); break; case Qt::Key_Backspace: { QString word = d->wordCandidates.wordAt(0); if (!word.isEmpty()) { word.remove(word.length() - 1, 1); ic->setPreeditText(word); if (!word.isEmpty()) { d->wordCandidates.updateWord(0, word); if (d->updateSuggestions()) { emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList); emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->wordCandidates.index()); } } else { d->reset(); } accept = true; } break; } default: if (inputMethodHints.testFlag(Qt::ImhNoPredictiveText)) break; if (d->dictionaryState == HunspellInputMethodPrivate::DictionaryNotLoaded) { update(); break; } if (text.length() > 0) { QChar c = text.at(0); QString word = d->wordCandidates.wordAt(0); bool addToWord = d->isValidInputChar(c) && (!word.isEmpty() || !d->isJoiner(c)); if (addToWord) { QString newText = text; /* Automatic space insertion. */ if (word.isEmpty()) { QString surroundingText = ic->surroundingText(); int cursorPosition = ic->cursorPosition(); /* Rules for automatic space insertion: - Surrounding text is not empty - Cursor is at the end of the line - No space before the cursor - No spefic characters before the cursor; minus and apostrophe */ if (!surroundingText.isEmpty() && cursorPosition == surroundingText.length()) { QChar lastChar = surroundingText.at(cursorPosition - 1); if (!lastChar.isSpace() && lastChar != Qt::Key_Minus && d->isAutoSpaceAllowed()) { // auto-insertion of space might trigger auto-capitalization bool wasShiftActive = ic->isShiftActive(); ic->commit(QLatin1String(" ")); if (ic->isShiftActive() && !wasShiftActive) newText = newText.toUpper(); } } } /* Ignore possible call to update() function when sending initial pre-edit text. The update is triggered if the text editor has a selection which the pre-edit text will replace. */ d->ignoreUpdate = word.isEmpty(); word.append(newText); d->wordCandidates.updateWord(0, word); ic->setPreeditText(word); d->ignoreUpdate = false; if (d->updateSuggestions()) { emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList); emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->wordCandidates.index()); } accept = true; } else if (text.length() > 1) { bool addSpace = !word.isEmpty() || d->autoSpaceAllowed; update(); d->autoSpaceAllowed = true; if (addSpace && d->isAutoSpaceAllowed()) ic->commit(QLatin1String(" ")); ic->commit(text); d->autoSpaceAllowed = addSpace; accept = true; } else { update(); inputContext()->sendKeyClick(key, text, modifiers); d->autoSpaceAllowed = true; accept = true; } } break; } return accept; } QList HunspellInputMethod::selectionLists() { Q_D(const HunspellInputMethod); QVirtualKeyboardInputContext *ic = inputContext(); if (!ic) return QList(); Qt::InputMethodHints inputMethodHints = ic->inputMethodHints(); if (d->dictionaryState == HunspellInputMethodPrivate::DictionaryNotLoaded || inputMethodHints.testFlag(Qt::ImhNoPredictiveText) || inputMethodHints.testFlag(Qt::ImhHiddenText)) return QList(); return QList() << QVirtualKeyboardSelectionListModel::Type::WordCandidateList; } int HunspellInputMethod::selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type) { Q_UNUSED(type) Q_D(HunspellInputMethod); return d->wordCandidates.size(); } QVariant HunspellInputMethod::selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role) { QVariant result; Q_D(HunspellInputMethod); switch (role) { case QVirtualKeyboardSelectionListModel::Role::Display: result = QVariant(d->wordCandidates.wordAt(index)); break; case QVirtualKeyboardSelectionListModel::Role::WordCompletionLength: { const QString wordCandidate(d->wordCandidates.wordAt(index)); const QString word(d->wordCandidates.wordAt(0)); int wordCompletionLength = wordCandidate.length() - word.length(); result.setValue((wordCompletionLength > 0 && wordCandidate.startsWith(word)) ? wordCompletionLength : 0); break; } case QVirtualKeyboardSelectionListModel::Role::Dictionary: { const QString wordCandidate(d->wordCandidates.wordAt(index)); QVirtualKeyboardSelectionListModel::DictionaryType dictionaryType = d->userDictionaryWords && d->userDictionaryWords->contains(wordCandidate) ? QVirtualKeyboardSelectionListModel::DictionaryType::User : QVirtualKeyboardSelectionListModel::DictionaryType::Default; result = QVariant(static_cast(dictionaryType)); break; } case QVirtualKeyboardSelectionListModel::Role::CanRemoveSuggestion: result.setValue(index > 0 && d->wordCandidates.wordFlagsAt(index).testFlag(HunspellWordList::SpellCheckOk)); break; default: result = QVirtualKeyboardAbstractInputMethod::selectionListData(type, index, role); break; } return result; } void HunspellInputMethod::selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index) { Q_UNUSED(type) Q_D(HunspellInputMethod); d->wordCandidates.setIndex(index); d->addToDictionary(); QString finalWord = d->wordCandidates.wordAt(index); reset(); inputContext()->commit(finalWord); d->autoSpaceAllowed = true; } bool HunspellInputMethod::selectionListRemoveItem(QVirtualKeyboardSelectionListModel::Type type, int index) { Q_D(HunspellInputMethod); Q_UNUSED(type) if (index <= 0 || index >= d->wordCandidates.size()) return false; QString word = d->wordCandidates.wordAt(index); d->removeFromDictionary(word); return true; } bool HunspellInputMethod::reselect(int cursorPosition, const QVirtualKeyboardInputEngine::ReselectFlags &reselectFlags) { Q_D(HunspellInputMethod); QString word(d->wordCandidates.wordAt(0)); Q_ASSERT(word.isEmpty()); if (d->dictionaryState == HunspellInputMethodPrivate::DictionaryNotLoaded) return false; QVirtualKeyboardInputContext *ic = inputContext(); if (!ic) return false; const QString surroundingText = ic->surroundingText(); int replaceFrom = 0; if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordBeforeCursor)) { for (int i = cursorPosition - 1; i >= 0; --i) { QChar c = surroundingText.at(i); if (!d->isValidInputChar(c)) break; word.insert(0, c); --replaceFrom; } while (replaceFrom < 0 && d->isJoiner(word.at(0))) { word.remove(0, 1); ++replaceFrom; } } if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordAtCursor) && replaceFrom == 0) return false; if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordAfterCursor)) { for (int i = cursorPosition; i < surroundingText.length(); ++i) { QChar c = surroundingText.at(i); if (!d->isValidInputChar(c)) break; word.append(c); } while (replaceFrom > -word.length()) { int lastPos = word.length() - 1; if (!d->isJoiner(word.at(lastPos))) break; word.remove(lastPos, 1); } } if (word.isEmpty()) return false; if (reselectFlags.testFlag(QVirtualKeyboardInputEngine::ReselectFlag::WordAtCursor) && replaceFrom == -word.length()) return false; if (d->isJoiner(word.at(0))) return false; if (d->isJoiner(word.at(word.length() - 1))) return false; d->wordCandidates.updateWord(0, word); ic->setPreeditText(word, QList(), replaceFrom, word.length()); d->autoSpaceAllowed = false; if (d->updateSuggestions()) { emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList); emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->wordCandidates.index()); } return true; } void HunspellInputMethod::reset() { Q_D(HunspellInputMethod); d->reset(); } void HunspellInputMethod::update() { Q_D(HunspellInputMethod); if (d->ignoreUpdate) return; QString finalWord; if (!d->wordCandidates.isEmpty()) { d->addToDictionary(); finalWord = d->wordCandidates.wordAt(d->wordCandidates.index()); } d->reset(); inputContext()->commit(finalWord); d->autoSpaceAllowed = false; } void HunspellInputMethod::updateSuggestions(const QSharedPointer &wordList, int tag) { Q_D(HunspellInputMethod); if (d->dictionaryState == HunspellInputMethodPrivate::DictionaryNotLoaded) { qCDebug(lcHunspell) << "updateSuggestions: skip (dictionary not loaded)"; update(); return; } if (d->wordCandidatesUpdateTag != tag) { qCDebug(lcHunspell) << "updateSuggestions: skip tag" << tag << "current" << d->wordCandidatesUpdateTag; return; } QString word(d->wordCandidates.wordAt(0)); d->wordCandidates = *wordList; if (d->wordCandidates.wordAt(0).compare(word) != 0) d->wordCandidates.updateWord(0, word); emit selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList); emit selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, d->wordCandidates.index()); } void HunspellInputMethod::dictionaryLoadCompleted(bool success) { Q_D(HunspellInputMethod); QVirtualKeyboardInputContext *ic = inputContext(); if (!ic) return; QList oldSelectionLists = selectionLists(); d->dictionaryState = success ? HunspellInputMethodPrivate::DictionaryReady : HunspellInputMethodPrivate::DictionaryNotLoaded; QList newSelectionLists = selectionLists(); if (oldSelectionLists != newSelectionLists) emit selectionListsChanged(); } } // namespace QtVirtualKeyboard QT_END_NAMESPACE