diff options
Diffstat (limited to 'src/plugins/hunspell')
14 files changed, 2287 insertions, 0 deletions
diff --git a/src/plugins/hunspell/3rdparty/hunspell/hunspell.pro b/src/plugins/hunspell/3rdparty/hunspell/hunspell.pro new file mode 100644 index 00000000..bd5eccb3 --- /dev/null +++ b/src/plugins/hunspell/3rdparty/hunspell/hunspell.pro @@ -0,0 +1,52 @@ +TARGET = qthunspell + +CONFIG += static +CONFIG += precompile_header warn_off +CONFIG(debug, debug|release) { + DEFINES += HUNSPELL_WARNING_ON +} + +config_file = "/* Version number of package */" "$${LITERAL_HASH}define VERSION \"$$VERSION\"" +write_file($$PWD/config.h, config_file) + +MODULE_DEFINES += HUNSPELL_STATIC +MODULE_INCLUDEPATH = $$PWD/src + +SOURCES += \ + src/hunspell/affentry.cxx \ + src/hunspell/affixmgr.cxx \ + src/hunspell/csutil.cxx \ + src/hunspell/filemgr.cxx \ + src/hunspell/hashmgr.cxx \ + src/hunspell/hunspell.cxx \ + src/hunspell/hunzip.cxx \ + src/hunspell/phonet.cxx \ + src/hunspell/replist.cxx \ + src/hunspell/suggestmgr.cxx + +HEADERS += \ + config.h \ + src/hunspell/affentry.hxx \ + src/hunspell/affixmgr.hxx \ + src/hunspell/atypes.hxx \ + src/hunspell/baseaffix.hxx \ + src/hunspell/csutil.hxx \ + src/hunspell/filemgr.hxx \ + src/hunspell/hashmgr.hxx \ + src/hunspell/htypes.hxx \ + src/hunspell/hunspell.h \ + src/hunspell/hunspell.hxx \ + src/hunspell/hunvisapi.h \ + src/hunspell/hunzip.hxx \ + src/hunspell/langnum.hxx \ + src/hunspell/phonet.hxx \ + src/hunspell/replist.hxx \ + src/hunspell/suggestmgr.hxx \ + src/hunspell/w_char.hxx + +OTHER_FILES +=\ + src/hunspell/license.hunspell \ + src/hunspell/license.myspell \ + src/hunspell/utf_info.cxx + +load(qt_helper_lib) diff --git a/src/plugins/hunspell/hunspell.pro b/src/plugins/hunspell/hunspell.pro new file mode 100644 index 00000000..fbe25a0a --- /dev/null +++ b/src/plugins/hunspell/hunspell.pro @@ -0,0 +1,13 @@ +TEMPLATE = subdirs + +include(../../config.pri) + +SUBDIRS += \ + hunspellinputmethod \ + plugin +hunspell-library { + SUBDIRS += 3rdparty/hunspell + hunspellinputmethod.depends += 3rdparty/hunspell +} + +plugin.depends += hunspellinputmethod diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.cpp b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.cpp new file mode 100644 index 00000000..884ec9df --- /dev/null +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.cpp @@ -0,0 +1,407 @@ +/**************************************************************************** +** +** 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 <QtHunspellInputMethod/private/hunspellinputmethod_p_p.h> +#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h> +#include <QLoggingCategory> + +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<QVirtualKeyboardInputEngine::InputMode> HunspellInputMethod::inputModes(const QString &locale) +{ + QList<QVirtualKeyboardInputEngine::InputMode> 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) { + /* 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()) { + ic->commit(QLatin1String(" ")); + } + } + } + /* 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(text); + 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<QVirtualKeyboardSelectionListModel::Type> HunspellInputMethod::selectionLists() +{ + Q_D(const HunspellInputMethod); + QVirtualKeyboardInputContext *ic = inputContext(); + if (!ic) + return QList<QVirtualKeyboardSelectionListModel::Type>(); + Qt::InputMethodHints inputMethodHints = ic->inputMethodHints(); + if (d->dictionaryState == HunspellInputMethodPrivate::DictionaryNotLoaded || inputMethodHints.testFlag(Qt::ImhNoPredictiveText) || inputMethodHints.testFlag(Qt::ImhHiddenText)) + return QList<QVirtualKeyboardSelectionListModel::Type>(); + return QList<QVirtualKeyboardSelectionListModel::Type>() << 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<int>(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<QInputMethodEvent::Attribute>(), 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<HunspellWordList> &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<QVirtualKeyboardSelectionListModel::Type> oldSelectionLists = selectionLists(); + d->dictionaryState = success ? HunspellInputMethodPrivate::DictionaryReady : + HunspellInputMethodPrivate::DictionaryNotLoaded; + QList<QVirtualKeyboardSelectionListModel::Type> newSelectionLists = selectionLists(); + if (oldSelectionLists != newSelectionLists) + emit selectionListsChanged(); +} + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.pro b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.pro new file mode 100644 index 00000000..9dc2e654 --- /dev/null +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.pro @@ -0,0 +1,30 @@ +TARGET = QtHunspellInputMethod +MODULE = hunspellinputmethod + +CONFIG += internal_module +QT += virtualkeyboard-private + +DEFINES += QHUNSPELLINPUTMETHOD_LIBRARY + +SOURCES += \ + hunspellinputmethod.cpp \ + hunspellinputmethod_p.cpp \ + hunspellworker.cpp +HEADERS += \ + hunspellinputmethod_p.h \ + hunspellinputmethod_p_p.h \ + hunspellworker_p.h \ + qhunspellinputmethod_global.h + +DEFINES += \ + QT_NO_CAST_TO_ASCII \ + QT_ASCII_CAST_WARNINGS \ + QT_NO_CAST_FROM_ASCII \ + QT_NO_CAST_FROM_BYTEARRAY + +include(../../../config.pri) + +hunspell-library: QMAKE_USE += hunspell +else:hunspell-package: PKGCONFIG += hunspell + +load(qt_module) diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.cpp b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.cpp new file mode 100644 index 00000000..4651ac1f --- /dev/null +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.cpp @@ -0,0 +1,338 @@ +/**************************************************************************** +** +** 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 <QtHunspellInputMethod/private/hunspellinputmethod_p_p.h> +#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h> +#include <hunspell/hunspell.h> +#include <QStringList> +#include <QDir> +#include <QTextCodec> +#include <QtCore/QLibraryInfo> +#include <QStandardPaths> + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +const int HunspellInputMethodPrivate::userDictionaryMaxSize = 100; + +/*! + \class QtVirtualKeyboard::HunspellInputMethodPrivate + \internal +*/ + +HunspellInputMethodPrivate::HunspellInputMethodPrivate(HunspellInputMethod *q_ptr) : + q_ptr(q_ptr), + hunspellWorker(new HunspellWorker()), + locale(), + wordCompletionPoint(2), + ignoreUpdate(false), + autoSpaceAllowed(false), + dictionaryState(DictionaryNotLoaded), + userDictionaryWords(new HunspellWordList(userDictionaryMaxSize)), + blacklistedWords(new HunspellWordList(userDictionaryMaxSize)), + wordCandidatesUpdateTag(0) +{ + if (hunspellWorker) + hunspellWorker->start(); +} + +HunspellInputMethodPrivate::~HunspellInputMethodPrivate() +{ +} + +bool HunspellInputMethodPrivate::createHunspell(const QString &locale) +{ + Q_Q(HunspellInputMethod); + if (!hunspellWorker) + return false; + if (this->locale != locale) { + clearSuggestionsRelatedTasks(); + hunspellWorker->waitForAllTasks(); + QString hunspellDataPath(qEnvironmentVariable("QT_VIRTUALKEYBOARD_HUNSPELL_DATA_PATH")); + const QString pathListSep( +#if defined(Q_OS_WIN32) + QStringLiteral(";") +#else + QStringLiteral(":") +#endif + ); + QStringList searchPaths(hunspellDataPath.split(pathListSep, QString::SkipEmptyParts)); + const QStringList defaultPaths = QStringList() + << QDir(QLibraryInfo::location(QLibraryInfo::DataPath) + QStringLiteral("/qtvirtualkeyboard/hunspell")).absolutePath() +#if !defined(Q_OS_WIN32) + << QStringLiteral("/usr/share/hunspell") + << QStringLiteral("/usr/share/myspell/dicts") +#endif + ; + for (const QString &defaultPath : defaultPaths) { + if (!searchPaths.contains(defaultPath)) + searchPaths.append(defaultPath); + } + QSharedPointer<HunspellLoadDictionaryTask> loadDictionaryTask(new HunspellLoadDictionaryTask(locale, searchPaths)); + QObject::connect(loadDictionaryTask.data(), &HunspellLoadDictionaryTask::completed, q, &HunspellInputMethod::dictionaryLoadCompleted); + dictionaryState = HunspellInputMethodPrivate::DictionaryLoading; + emit q->selectionListsChanged(); + hunspellWorker->addTask(loadDictionaryTask); + this->locale = locale; + + loadCustomDictionary(userDictionaryWords, QLatin1String("userdictionary")); + addToHunspell(userDictionaryWords); + loadCustomDictionary(blacklistedWords, QLatin1String("blacklist")); + removeFromHunspell(blacklistedWords); + } + return true; +} + +void HunspellInputMethodPrivate::reset() +{ + if (clearSuggestions(true)) { + Q_Q(HunspellInputMethod); + emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList); + emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, wordCandidates.index()); + } + autoSpaceAllowed = false; +} + +bool HunspellInputMethodPrivate::updateSuggestions() +{ + bool wordCandidateListChanged = false; + QString word = wordCandidates.wordAt(0); + if (!word.isEmpty() && dictionaryState != HunspellInputMethodPrivate::DictionaryNotLoaded) { + wordCandidateListChanged = true; + if (word.length() >= wordCompletionPoint) { + if (hunspellWorker) { + QSharedPointer<HunspellWordList> wordList(new HunspellWordList(wordCandidates)); + + // Clear obsolete tasks from the worker queue + clearSuggestionsRelatedTasks(); + + // Build suggestions + QSharedPointer<HunspellBuildSuggestionsTask> buildSuggestionsTask(new HunspellBuildSuggestionsTask()); + buildSuggestionsTask->wordList = wordList; + buildSuggestionsTask->autoCorrect = false; + hunspellWorker->addTask(buildSuggestionsTask); + + // Filter out blacklisted word (sometimes Hunspell suggests, + // e.g. with different text case) + QSharedPointer<HunspellFilterWordTask> filterWordTask(new HunspellFilterWordTask()); + filterWordTask->wordList = wordList; + filterWordTask->filterList = blacklistedWords; + hunspellWorker->addTask(filterWordTask); + + // Boost words from user dictionary + QSharedPointer<HunspellBoostWordTask> boostWordTask(new HunspellBoostWordTask()); + boostWordTask->wordList = wordList; + boostWordTask->boostList = userDictionaryWords; + hunspellWorker->addTask(boostWordTask); + + // Update word candidate list + QSharedPointer<HunspellUpdateSuggestionsTask> updateSuggestionsTask(new HunspellUpdateSuggestionsTask()); + updateSuggestionsTask->wordList = wordList; + updateSuggestionsTask->tag = ++wordCandidatesUpdateTag; + Q_Q(HunspellInputMethod); + QObject::connect(updateSuggestionsTask.data(), &HunspellUpdateSuggestionsTask::updateSuggestions, q, &HunspellInputMethod::updateSuggestions); + hunspellWorker->addTask(updateSuggestionsTask); + } + } + } else { + wordCandidateListChanged = clearSuggestions(); + } + return wordCandidateListChanged; +} + +bool HunspellInputMethodPrivate::clearSuggestions(bool clearInputWord) +{ + clearSuggestionsRelatedTasks(); + return clearInputWord ? wordCandidates.clear() : wordCandidates.clearSuggestions(); +} + +void HunspellInputMethodPrivate::clearSuggestionsRelatedTasks() +{ + if (hunspellWorker) { + hunspellWorker->removeAllTasksOfType<HunspellBuildSuggestionsTask>(); + hunspellWorker->removeAllTasksOfType<HunspellFilterWordTask>(); + hunspellWorker->removeAllTasksOfType<HunspellBoostWordTask>(); + hunspellWorker->removeAllTasksOfType<HunspellUpdateSuggestionsTask>(); + } +} + +bool HunspellInputMethodPrivate::isAutoSpaceAllowed() const +{ + Q_Q(const HunspellInputMethod); + if (!autoSpaceAllowed) + return false; + if (q->inputEngine()->inputMode() == QVirtualKeyboardInputEngine::InputMode::Numeric) + return false; + QVirtualKeyboardInputContext *ic = q->inputContext(); + if (!ic) + return false; + Qt::InputMethodHints inputMethodHints = ic->inputMethodHints(); + return !inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly) && + !inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly); +} + +bool HunspellInputMethodPrivate::isValidInputChar(const QChar &c) const +{ + if (c.isLetterOrNumber()) + return true; + if (isJoiner(c)) + return true; + if (c.isMark()) + return true; + return false; +} + +bool HunspellInputMethodPrivate::isJoiner(const QChar &c) const +{ + if (c.isPunct() || c.isSymbol()) { + Q_Q(const HunspellInputMethod); + QVirtualKeyboardInputContext *ic = q->inputContext(); + if (ic) { + Qt::InputMethodHints inputMethodHints = ic->inputMethodHints(); + if (inputMethodHints.testFlag(Qt::ImhUrlCharactersOnly) || inputMethodHints.testFlag(Qt::ImhEmailCharactersOnly)) + return QString(QStringLiteral(":/?#[]@!$&'()*+,;=-_.%")).contains(c); + } + ushort unicode = c.unicode(); + if (unicode == Qt::Key_Apostrophe || unicode == Qt::Key_Minus) + return true; + } + return false; +} + +QString HunspellInputMethodPrivate::customDictionaryLocation(const QString &dictionaryType) const +{ + if (dictionaryType.isEmpty() || locale.isEmpty()) + return QString(); + + QString location = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); + if (location.isEmpty()) + return QString(); + + return QStringLiteral("%1/qtvirtualkeyboard/hunspell/%2-%3.txt") + .arg(location) + .arg(dictionaryType) + .arg(locale); +} + +void HunspellInputMethodPrivate::loadCustomDictionary(const QSharedPointer<HunspellWordList> &wordList, + const QString &dictionaryType) const +{ + QSharedPointer<HunspellLoadWordListTask> loadWordsTask(new HunspellLoadWordListTask()); + loadWordsTask->filePath = customDictionaryLocation(dictionaryType); + loadWordsTask->wordList = wordList; + hunspellWorker->addTask(loadWordsTask); +} + +void HunspellInputMethodPrivate::saveCustomDictionary(const QSharedPointer<HunspellWordList> &wordList, + const QString &dictionaryType) const +{ + QSharedPointer<HunspellSaveWordListTask> saveWordsTask(new HunspellSaveWordListTask()); + saveWordsTask->filePath = customDictionaryLocation(dictionaryType); + saveWordsTask->wordList = wordList; + hunspellWorker->addTask(saveWordsTask); +} + +void HunspellInputMethodPrivate::addToHunspell(const QSharedPointer<HunspellWordList> &wordList) const +{ + QSharedPointer<HunspellAddWordTask> addWordTask(new HunspellAddWordTask()); + addWordTask->wordList = wordList; + hunspellWorker->addTask(addWordTask); +} + +void HunspellInputMethodPrivate::removeFromHunspell(const QSharedPointer<HunspellWordList> &wordList) const +{ + QSharedPointer<HunspellRemoveWordTask> removeWordTask(new HunspellRemoveWordTask()); + removeWordTask->wordList = wordList; + hunspellWorker->addTask(removeWordTask); +} + +void HunspellInputMethodPrivate::removeFromDictionary(const QString &word) +{ + if (userDictionaryWords->removeWord(word) > 0) { + saveCustomDictionary(userDictionaryWords, QLatin1String("userdictionary")); + } else if (!blacklistedWords->contains(word)) { + blacklistedWords->appendWord(word); + saveCustomDictionary(blacklistedWords, QLatin1String("blacklist")); + } + + QSharedPointer<HunspellWordList> wordList(new HunspellWordList()); + wordList->appendWord(word); + removeFromHunspell(wordList); + + updateSuggestions(); +} + +void HunspellInputMethodPrivate::addToDictionary() +{ + Q_Q(HunspellInputMethod); + // This feature is not allowed when dealing with sensitive information + const Qt::InputMethodHints inputMethodHints(q->inputContext()->inputMethodHints()); + const bool userDictionaryEnabled = + !inputMethodHints.testFlag(Qt::ImhHiddenText) && + !inputMethodHints.testFlag(Qt::ImhSensitiveData); + if (!userDictionaryEnabled) + return; + + if (wordCandidates.isEmpty()) + return; + + QString word; + HunspellWordList::Flags wordFlags; + const int activeWordIndex = wordCandidates.index(); + wordCandidates.wordAt(activeWordIndex, word, wordFlags); + if (activeWordIndex == 0) { + if (blacklistedWords->removeWord(word) > 0) { + saveCustomDictionary(blacklistedWords, QLatin1String("blacklist")); + } else if (word.length() > 1 && !wordFlags.testFlag(HunspellWordList::SpellCheckOk) && !userDictionaryWords->contains(word)) { + userDictionaryWords->appendWord(word); + saveCustomDictionary(userDictionaryWords, QLatin1String("userdictionary")); + } else { + // Avoid adding words to Hunspell which are too short or passed spell check + return; + } + + QSharedPointer<HunspellWordList> wordList(new HunspellWordList()); + wordList->appendWord(word); + addToHunspell(wordList); + } else { + // Check if found in the user dictionary and move as last in the list. + // This way the list is always ordered by use. + // If userDictionaryMaxSize is greater than zero the number of words in the + // list will be limited to that amount. By pushing last used items to end of + // list we can avoid (to certain extent) removing frequently used words. + int userDictionaryIndex = userDictionaryWords->indexOfWord(word); + if (userDictionaryIndex != -1) { + userDictionaryWords->moveWord(userDictionaryIndex, userDictionaryWords->size() - 1); + saveCustomDictionary(userDictionaryWords, QLatin1String("userdictionary")); + } + } +} + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.h b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.h new file mode 100644 index 00000000..4c19063b --- /dev/null +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef HUNSPELLINPUTMETHOD_P_H +#define HUNSPELLINPUTMETHOD_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtVirtualKeyboard/qvirtualkeyboardabstractinputmethod.h> +#include <QtHunspellInputMethod/qhunspellinputmethod_global.h> + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +class HunspellInputMethodPrivate; +class HunspellWordList; + +class QHUNSPELLINPUTMETHOD_EXPORT HunspellInputMethod : public QVirtualKeyboardAbstractInputMethod +{ + Q_OBJECT + Q_DECLARE_PRIVATE(HunspellInputMethod) +protected: + HunspellInputMethod(HunspellInputMethodPrivate *d_ptr, QObject *parent); +public: + explicit HunspellInputMethod(QObject *parent = nullptr); + ~HunspellInputMethod(); + + QList<QVirtualKeyboardInputEngine::InputMode> inputModes(const QString &locale); + bool setInputMode(const QString &locale, QVirtualKeyboardInputEngine::InputMode inputMode); + bool setTextCase(QVirtualKeyboardInputEngine::TextCase textCase); + + bool keyEvent(Qt::Key key, const QString &text, Qt::KeyboardModifiers modifiers); + + QList<QVirtualKeyboardSelectionListModel::Type> selectionLists(); + int selectionListItemCount(QVirtualKeyboardSelectionListModel::Type type); + QVariant selectionListData(QVirtualKeyboardSelectionListModel::Type type, int index, QVirtualKeyboardSelectionListModel::Role role); + void selectionListItemSelected(QVirtualKeyboardSelectionListModel::Type type, int index); + bool selectionListRemoveItem(QVirtualKeyboardSelectionListModel::Type type, int index); + + bool reselect(int cursorPosition, const QVirtualKeyboardInputEngine::ReselectFlags &reselectFlags); + + void reset(); + void update(); + +protected Q_SLOTS: + void updateSuggestions(const QSharedPointer<HunspellWordList> &wordList, int tag); + void dictionaryLoadCompleted(bool success); + +protected: + QScopedPointer<HunspellInputMethodPrivate> d_ptr; +}; + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE + +#endif // HUNSPELLINPUTMETHOD_P_H diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p_p.h b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p_p.h new file mode 100644 index 00000000..2e604350 --- /dev/null +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef HUNSPELLINPUTMETHOD_P_P_H +#define HUNSPELLINPUTMETHOD_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtHunspellInputMethod/private/hunspellinputmethod_p.h> +#include <QtHunspellInputMethod/private/hunspellworker_p.h> + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +class QHUNSPELLINPUTMETHOD_EXPORT HunspellInputMethodPrivate +{ + Q_DECLARE_PUBLIC(HunspellInputMethod) + +public: + HunspellInputMethodPrivate(HunspellInputMethod *q_ptr); + ~HunspellInputMethodPrivate(); + + enum DictionaryState { + DictionaryNotLoaded, + DictionaryLoading, + DictionaryReady + }; + + bool createHunspell(const QString &locale); + void reset(); + bool updateSuggestions(); + bool clearSuggestions(bool clearInputWord = false); + void clearSuggestionsRelatedTasks(); + bool isAutoSpaceAllowed() const; + bool isValidInputChar(const QChar &c) const; + bool isJoiner(const QChar &c) const; + QString customDictionaryLocation(const QString &dictionaryType) const; + void loadCustomDictionary(const QSharedPointer<HunspellWordList> &wordList, const QString &dictionaryType) const; + void saveCustomDictionary(const QSharedPointer<HunspellWordList> &wordList, const QString &dictionaryType) const; + void addToHunspell(const QSharedPointer<HunspellWordList> &wordList) const; + void removeFromHunspell(const QSharedPointer<HunspellWordList> &wordList) const; + void removeFromDictionary(const QString &word); + void addToDictionary(); + + HunspellInputMethod *q_ptr; + QScopedPointer<HunspellWorker> hunspellWorker; + QString locale; + HunspellWordList wordCandidates; + int wordCompletionPoint; + bool ignoreUpdate; + bool autoSpaceAllowed; + DictionaryState dictionaryState; + QSharedPointer<HunspellWordList> userDictionaryWords; + QSharedPointer<HunspellWordList> blacklistedWords; + int wordCandidatesUpdateTag; + static const int userDictionaryMaxSize; +}; + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE + +#endif // HUNSPELLINPUTMETHOD_P_P_H diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellworker.cpp b/src/plugins/hunspell/hunspellinputmethod/hunspellworker.cpp new file mode 100644 index 00000000..15af0841 --- /dev/null +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellworker.cpp @@ -0,0 +1,776 @@ +/**************************************************************************** +** +** 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 <QtHunspellInputMethod/private/hunspellworker_p.h> +#include <QVector> +#include <QTextCodec> +#include <QFileInfo> +#include <QRegularExpression> +#include <QTime> +#include <QFile> +#include <QDir> +#include <QtAlgorithms> + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +HunspellWordList::HunspellWordList(int limit) : + _index(0), + _limit(limit) +{ +} + +HunspellWordList::HunspellWordList(HunspellWordList &other) +{ + *this = other; +} + +HunspellWordList &HunspellWordList::operator=(HunspellWordList &other) +{ + if (this != &other) { + QMutexLocker guard(&_lock); + QMutexLocker otherGuard(&other._lock); + _list = other._list; + _flags = other._flags; + _index = other._index; + _limit = other._limit; + _searchIndex = other._searchIndex; + } + return *this; +} + +int HunspellWordList::index() const +{ + return _index < _list.size() ? _index : -1; +} + +void HunspellWordList::setIndex(int index) +{ + QMutexLocker guard(&_lock); + _index = index; +} + +bool HunspellWordList::clear() +{ + QMutexLocker guard(&_lock); + bool result = !_list.isEmpty(); + _list.clear(); + _flags.clear(); + _index = 0; + _searchIndex.clear(); + return result; +} + +bool HunspellWordList::clearSuggestions() +{ + QMutexLocker guard(&_lock); + if (_list.isEmpty()) + return false; + + _searchIndex.clear(); + if (_list.size() > 1) { + QString word = _list.at(0); + Flags flags = _flags.at(0); + _list.clear(); + _flags.clear(); + if (!word.isEmpty()) { + _index = 0; + _list.append(word); + _flags.append(flags); + } + return true; + } else if (_list.at(0).isEmpty()) { + _list.clear(); + _flags.clear(); + _index = 0; + return true; + } + return false; +} + +bool HunspellWordList::hasSuggestions() const +{ + return _list.size() > 1; +} + +int HunspellWordList::size() const +{ + return _list.size(); +} + +int HunspellWordList::isEmpty() const +{ + return _list.isEmpty() || _list.at(0).isEmpty(); +} + +bool HunspellWordList::contains(const QString &word) +{ + QMutexLocker guard(&_lock); + + // Use index search when the search index is available. + // This provides a lot faster search than QList::contains(). + // Search index is available when it has been rebuilt using + // rebuildSearchIndex() method. Search index is automatically + // cleared when the word list is modified. + if (!_searchIndex.isEmpty()) { + Q_ASSERT(_searchIndex.size() == _list.size()); + + SearchContext searchContext(word, _list); + return std::binary_search(_searchIndex.begin(), _searchIndex.end(), -1, [searchContext](const int &a, const int &b) { + const QString &wordA = (a == -1) ? searchContext.word : searchContext.list[a]; + const QString &wordB = (b == -1) ? searchContext.word : searchContext.list[b]; + return wordA.compare(wordB, Qt::CaseInsensitive) < 0; + }); + } + + return _list.contains(word, Qt::CaseInsensitive); +} + +QString HunspellWordList::findWordCompletion(const QString &word) +{ + QMutexLocker guard(&_lock); + + if (!_searchIndex.isEmpty()) { + Q_ASSERT(_searchIndex.size() == _list.size()); + + SearchContext searchContext(word, _list); + auto match = std::lower_bound(_searchIndex.begin(), _searchIndex.end(), -1, [searchContext](const int &a, const int &b) { + const QString &wordA = (a == -1) ? searchContext.word : searchContext.list[a]; + const QString &wordB = (b == -1) ? searchContext.word : searchContext.list[b]; + return wordA.compare(wordB, Qt::CaseInsensitive) < 0; + }); + + if (match == _searchIndex.end()) + return QString(); + + if (!word.compare(_list[*match], Qt::CaseInsensitive)) { + match++; + if (match == _searchIndex.end()) + return QString(); + } + + return _list[*match].startsWith(word, Qt::CaseInsensitive) ? _list[*match] : QString(); + } + + QString bestMatch; + for (int i = 0, count = _list.size(); i < count; ++i) { + const QString &wordB(_list[i]); + if (wordB.length() > bestMatch.length() && + word.length() < wordB.length() && + wordB.startsWith(word, Qt::CaseInsensitive)) + bestMatch = wordB; + } + + return bestMatch; +} + +int HunspellWordList::indexOfWord(const QString &word) +{ + QMutexLocker guard(&_lock); + + if (!_searchIndex.isEmpty()) { + Q_ASSERT(_searchIndex.size() == _list.size()); + + SearchContext searchContext(word, _list); + auto match = std::lower_bound(_searchIndex.begin(), _searchIndex.end(), -1, [searchContext](int a, int b) { + const QString &wordA = (a == -1) ? searchContext.word : searchContext.list[a]; + const QString &wordB = (b == -1) ? searchContext.word : searchContext.list[b]; + return wordA.compare(wordB, Qt::CaseInsensitive) < 0; + }); + return (match != _searchIndex.end()) ? *match : -1; + } + + return _list.indexOf(word); +} + +QString HunspellWordList::wordAt(int index) +{ + QMutexLocker guard(&_lock); + + return index >= 0 && index < _list.size() ? _list.at(index) : QString(); +} + +void HunspellWordList::wordAt(int index, QString &word, Flags &flags) +{ + QMutexLocker guard(&_lock); + Q_ASSERT(index >= 0 && index < _list.size()); + + word = _list.at(index); + flags = _flags.at(index); +} + +const HunspellWordList::Flags &HunspellWordList::wordFlagsAt(int index) +{ + QMutexLocker guard(&_lock); + + return _flags[index]; +} + +void HunspellWordList::appendWord(const QString &word, const Flags &flags) +{ + QMutexLocker guard(&_lock); + + _searchIndex.clear(); + if (_limit > 0) { + while (_list.size() >= _limit) { + _list.removeAt(0); + _flags.removeAt(0); + } + } + _list.append(word); + _flags.append(flags); +} + +void HunspellWordList::insertWord(int index, const QString &word, const Flags &flags) +{ + QMutexLocker guard(&_lock); + Q_ASSERT(_limit == 0); + + _searchIndex.clear(); + _list.insert(index, word); + _flags.insert(index, flags); +} + +void HunspellWordList::updateWord(int index, const QString &word, const Flags &flags) +{ + Q_ASSERT(index >= 0); + QMutexLocker guard(&_lock); + + if (index < _list.size()) { + if (word != _list[index]) + _searchIndex.clear(); + _list[index] = word; + _flags[index] = flags; + } else { + _searchIndex.clear(); + _list.append(word); + _flags.append(flags); + } +} + +void HunspellWordList::moveWord(int from, int to) +{ + QMutexLocker guard(&_lock); + + if (from < 0 || from >= _list.size()) + return; + if (to < 0 || to >= _list.size()) + return; + if (from == to) + return; + + _searchIndex.clear(); + _list.move(from, to); + _flags.move(from, to); +} + +int HunspellWordList::removeWord(const QString &word) +{ + QMutexLocker guard(&_lock); + int removeCount = 0; + for (int i = 0, count = _list.size(); i < count;) { + if (!_list[i].compare(word, Qt::CaseInsensitive)) { + _list.removeAt(i); + _flags.removeAt(i); + --count; + ++removeCount; + } else { + ++i; + } + } + if (removeCount > 0) + _searchIndex.clear(); + return removeCount; +} + +void HunspellWordList::removeWordAt(int index) +{ + QMutexLocker guard(&_lock); + + _list.removeAt(index); +} + +void HunspellWordList::rebuildSearchIndex() +{ + QMutexLocker guard(&_lock); + _searchIndex.clear(); + + if (_list.isEmpty()) + return; + + _searchIndex.resize(_list.size()); + std::iota(_searchIndex.begin(), _searchIndex.end(), 0); + + const QStringList list(_list); + std::sort(_searchIndex.begin(), _searchIndex.end(), [list](int a, int b) { return list[a].compare(list[b], Qt::CaseInsensitive) < 0; }); +} + +/*! + \class QtVirtualKeyboard::HunspellTask + \internal +*/ + +/*! + \class QtVirtualKeyboard::HunspellWordList + \internal +*/ + +/*! + \class QtVirtualKeyboard::HunspellLoadDictionaryTask + \internal +*/ + +HunspellLoadDictionaryTask::HunspellLoadDictionaryTask(const QString &locale, const QStringList &searchPaths) : + HunspellTask(), + hunspellPtr(nullptr), + locale(locale), + searchPaths(searchPaths) +{ +} + +void HunspellLoadDictionaryTask::run() +{ + Q_ASSERT(hunspellPtr != nullptr); + + qCDebug(lcHunspell) << "HunspellLoadDictionaryTask::run(): locale:" << locale; + + if (*hunspellPtr) { + Hunspell_destroy(*hunspellPtr); + *hunspellPtr = nullptr; + } + + QString affPath; + QString dicPath; + for (const QString &searchPath : searchPaths) { + affPath = QStringLiteral("%1/%2.aff").arg(searchPath, locale); + if (QFileInfo::exists(affPath)) { + dicPath = QStringLiteral("%1/%2.dic").arg(searchPath, locale); + if (QFileInfo::exists(dicPath)) + break; + dicPath.clear(); + } + affPath.clear(); + } + + if (!affPath.isEmpty() && !dicPath.isEmpty()) { + *hunspellPtr = Hunspell_create(affPath.toUtf8().constData(), dicPath.toUtf8().constData()); + if (*hunspellPtr) { + /* Make sure the encoding used by the dictionary is supported + by the QTextCodec. + */ + if (!QTextCodec::codecForName(Hunspell_get_dic_encoding(*hunspellPtr))) { + qCWarning(lcHunspell) << "The Hunspell dictionary" << dicPath << "cannot be used because it uses an unknown text codec" << QLatin1String(Hunspell_get_dic_encoding(*hunspellPtr)); + Hunspell_destroy(*hunspellPtr); + *hunspellPtr = nullptr; + } + } + } else { + qCWarning(lcHunspell) << "Hunspell dictionary is missing for" << locale << ". Search paths" << searchPaths; + } + + emit completed(*hunspellPtr != nullptr); +} + +/*! + \class QtVirtualKeyboard::HunspellBuildSuggestionsTask + \internal +*/ + +void HunspellBuildSuggestionsTask::run() +{ + if (wordList->isEmpty()) + return; + + wordList->clearSuggestions(); + QString word = wordList->wordAt(0); + + /* Select text codec based on the dictionary encoding. + Hunspell_get_dic_encoding() should always return at least + "ISO8859-1", but you can never be too sure. + */ + textCodec = QTextCodec::codecForName(Hunspell_get_dic_encoding(hunspell)); + if (!textCodec) + return; + + char **slst = nullptr; + int n = Hunspell_suggest(hunspell, &slst, textCodec->fromUnicode(word).constData()); + if (n > 0) { + /* Collect word candidates from the Hunspell suggestions. + Insert word completions in the beginning of the list. + */ + const int firstWordCompletionIndex = wordList->size(); + int lastWordCompletionIndex = firstWordCompletionIndex; + bool suggestCapitalization = false; + for (int i = 0; i < n; i++) { + QString wordCandidate(textCodec->toUnicode(slst[i])); + wordCandidate.replace(QChar(0x2019), QLatin1Char('\'')); + QString normalizedWordCandidate = removeAccentsAndDiacritics(wordCandidate); + /* Prioritize word Capitalization */ + if (!wordCandidate.compare(word, Qt::CaseInsensitive)) { + if (suggestCapitalization) { + bool wordCandidateIsCapital = wordCandidate.at(0).isUpper(); + bool wordIsCapital = word.at(0).isUpper(); + if (wordCandidateIsCapital == wordIsCapital) { + if (wordCandidateIsCapital) + wordCandidate = wordCandidate.toLower(); + else + wordCandidate[0] = wordCandidate.at(0).toUpper(); + } + wordList->insertWord(1, wordCandidate); + lastWordCompletionIndex++; + suggestCapitalization = true; + } + /* Prioritize word completions, missing punctuation or missing accents */ + } else if ((normalizedWordCandidate.length() > word.length() && + normalizedWordCandidate.startsWith(word)) || + wordCandidate.contains(QLatin1Char('\''))) { + wordList->insertWord(lastWordCompletionIndex++, wordCandidate); + } else { + wordList->appendWord(wordCandidate); + } + } + /* Prioritize words with missing spaces next to word completions. + */ + for (int i = lastWordCompletionIndex; i < wordList->size(); i++) { + QString wordCandidate(wordList->wordAt(i)); + if (wordCandidate.contains(QLatin1String(" "))) { + wordList->updateWord(i, wordCandidate, wordList->wordFlagsAt(i) | HunspellWordList::CompoundWord); + if (i != lastWordCompletionIndex) { + wordList->moveWord(i, lastWordCompletionIndex); + } + lastWordCompletionIndex++; + } + } + /* Do spell checking and suggest the first candidate, if: + - the word matches partly the suggested word; or + - the quality of the suggested word is good enough. + + The quality is measured here using the Levenshtein Distance, + which may be suboptimal for the purpose, but gives some clue + how much the suggested word differs from the given word. + */ + if (autoCorrect && wordList->size() > 1 && (!spellCheck(word) || suggestCapitalization)) { + if (lastWordCompletionIndex > firstWordCompletionIndex || levenshteinDistance(word, wordList->wordAt(firstWordCompletionIndex)) < 3) + wordList->setIndex(firstWordCompletionIndex); + } + } + Hunspell_free_list(hunspell, &slst, n); + + for (int i = 0, count = wordList->size(); i < count; ++i) { + HunspellWordList::Flags flags; + wordList->wordAt(i, word, flags); + if (flags.testFlag(HunspellWordList::CompoundWord)) + continue; + if (Hunspell_spell(hunspell, textCodec->fromUnicode(word).constData()) != 0) + wordList->updateWord(i, word, wordList->wordFlagsAt(i) | HunspellWordList::SpellCheckOk); + } +} + +bool HunspellBuildSuggestionsTask::spellCheck(const QString &word) +{ + if (!hunspell) + return false; + if (word.contains(QRegularExpression(QLatin1Literal("[0-9]")))) + return true; + return Hunspell_spell(hunspell, textCodec->fromUnicode(word).constData()) != 0; +} + +// source: http://en.wikipedia.org/wiki/Levenshtein_distance +int HunspellBuildSuggestionsTask::levenshteinDistance(const QString &s, const QString &t) +{ + if (s == t) + return 0; + if (s.length() == 0) + return t.length(); + if (t.length() == 0) + return s.length(); + QVector<int> v0(t.length() + 1); + QVector<int> v1(t.length() + 1); + for (int i = 0; i < v0.size(); i++) + v0[i] = i; + for (int i = 0; i < s.size(); i++) { + v1[0] = i + 1; + for (int j = 0; j < t.length(); j++) { + int cost = (s[i].toLower() == t[j].toLower()) ? 0 : 1; + v1[j + 1] = qMin(qMin(v1[j] + 1, v0[j + 1] + 1), v0[j] + cost); + } + for (int j = 0; j < v0.size(); j++) + v0[j] = v1[j]; + } + return v1[t.length()]; +} + +QString HunspellBuildSuggestionsTask::removeAccentsAndDiacritics(const QString& s) +{ + QString normalized = s.normalized(QString::NormalizationForm_D); + for (int i = 0; i < normalized.length();) { + QChar::Category category = normalized[i].category(); + if (category <= QChar::Mark_Enclosing) { + normalized.remove(i, 1); + } else { + i++; + } + } + return normalized; +} + +/*! + \class QtVirtualKeyboard::HunspellUpdateSuggestionsTask + \internal +*/ + +void HunspellUpdateSuggestionsTask::run() +{ + emit updateSuggestions(wordList, tag); +} + +void HunspellAddWordTask::run() +{ + const QTextCodec *textCodec; + textCodec = QTextCodec::codecForName(Hunspell_get_dic_encoding(hunspell)); + if (!textCodec) + return; + + QString tmpWord; + tmpWord.reserve(64); + for (int i = 0, count = wordList->size(); i < count; ++i) { + const QString word(wordList->wordAt(i)); + if (word.length() < 2) + continue; + Hunspell_add(hunspell, textCodec->fromUnicode(word).constData()); + if (HunspellAddWordTask::alternativeForm(word, tmpWord)) + Hunspell_add(hunspell, textCodec->fromUnicode(tmpWord).constData()); + } +} + +bool HunspellAddWordTask::alternativeForm(const QString &word, QString &alternativeForm) +{ + if (word.length() < 2) + return false; + if (!word.mid(1).isLower()) + return false; + + const QChar initial(word.at(0)); + const QChar newInitial = initial.isUpper() ? initial.toLower() : initial.toUpper(); + if (newInitial == initial) + return false; + + alternativeForm.truncate(0); + alternativeForm.append(word); + alternativeForm[0] = newInitial; + + return true; +} + +void HunspellRemoveWordTask::run() +{ + const QTextCodec *textCodec; + textCodec = QTextCodec::codecForName(Hunspell_get_dic_encoding(hunspell)); + if (!textCodec) + return; + + QString tmpWord; + tmpWord.reserve(64); + for (int i = 0, count = wordList->size(); i < count; ++i) { + const QString word(wordList->wordAt(i)); + if (word.isEmpty()) + continue; + Hunspell_remove(hunspell, textCodec->fromUnicode(word).constData()); + if (HunspellAddWordTask::alternativeForm(word, tmpWord)) + Hunspell_remove(hunspell, textCodec->fromUnicode(tmpWord).constData()); + } +} + +void HunspellLoadWordListTask::run() +{ + wordList->clear(); + + QFile inputFile(filePath); + if (inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream inStream(&inputFile); + inStream.setCodec(QTextCodec::codecForName("UTF-8")); + QString word; + word.reserve(64); + while (inStream.readLineInto(&word)) { + if (!word.isEmpty()) + wordList->appendWord(word); + } + inputFile.close(); + } +} + +void HunspellSaveWordListTask::run() +{ + QFile outputFile(filePath); + if (!QFileInfo::exists(filePath)) + QDir().mkpath(QFileInfo(filePath).absoluteDir().path()); + if (outputFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream outStream(&outputFile); + outStream.setCodec(QTextCodec::codecForName("UTF-8")); + for (int i = 0, count = wordList->size(); i < count; ++i) { + const QString word(wordList->wordAt(i)); + outStream << word.toUtf8() << '\n'; + } + outputFile.close(); + } +} + +void HunspellFilterWordTask::run() +{ + if (filterList->isEmpty()) + return; + + filterList->rebuildSearchIndex(); + + for (int i = startIndex, count = wordList->size(); i < count;) { + if (filterList->contains(wordList->wordAt(i))) { + wordList->removeWordAt(i); + --count; + } else { + ++i; + } + } +} + +void HunspellBoostWordTask::run() +{ + if (boostList->isEmpty()) + return; + + boostList->rebuildSearchIndex(); + + const QString word(wordList->wordAt(0)); + const QString wordCompletion(boostList->findWordCompletion(word)); + if (!wordCompletion.isEmpty()) { + int from = wordList->indexOfWord(wordCompletion); + if (from != 1) { + int to; + for (to = 1; to < wordList->size() && wordList->wordAt(to).startsWith(word); ++to) + ; + if (from != -1) { + if (to < from) + wordList->moveWord(from, to); + } else { + wordList->insertWord(to, wordCompletion, HunspellWordList::SpellCheckOk); + } + } + } +} + +/*! + \class QtVirtualKeyboard::HunspellWorker + \internal +*/ + +HunspellWorker::HunspellWorker(QObject *parent) : + QThread(parent), + idleSema(), + taskSema(), + taskLock(), + hunspell(nullptr) +{ + abort = false; + qRegisterMetaType<QSharedPointer<HunspellWordList>>("QSharedPointer<HunspellWordList>"); +} + +HunspellWorker::~HunspellWorker() +{ + abort = true; + taskSema.release(1); + wait(); +} + +void HunspellWorker::addTask(QSharedPointer<HunspellTask> task) +{ + if (task) { + QMutexLocker guard(&taskLock); + taskList.append(task); + taskSema.release(); + } +} + +void HunspellWorker::removeAllTasks() +{ + QMutexLocker guard(&taskLock); + taskList.clear(); +} + +void HunspellWorker::waitForAllTasks() +{ + qCDebug(lcHunspell) << "waitForAllTasks enter"; + while (isRunning()) { + idleSema.acquire(); + QMutexLocker guard(&taskLock); + if (taskList.isEmpty()) { + idleSema.release(); + break; + } + idleSema.release(); + } + qCDebug(lcHunspell) << "waitForAllTasks leave"; +} + +void HunspellWorker::run() +{ + QTime perf; + while (!abort) { + idleSema.release(); + taskSema.acquire(); + if (abort) + break; + idleSema.acquire(); + QSharedPointer<HunspellTask> currentTask; + { + QMutexLocker guard(&taskLock); + if (!taskList.isEmpty()) { + currentTask = taskList.front(); + taskList.pop_front(); + } + } + if (currentTask) { + QSharedPointer<HunspellLoadDictionaryTask> loadDictionaryTask(currentTask.objectCast<HunspellLoadDictionaryTask>()); + if (loadDictionaryTask) + loadDictionaryTask->hunspellPtr = &hunspell; + else if (hunspell) + currentTask->hunspell = hunspell; + else + continue; + perf.start(); + currentTask->run(); + qCDebug(lcHunspell) << QString(QLatin1String(currentTask->metaObject()->className()) + QLatin1String("::run(): time:")).toLatin1().constData() << perf.elapsed() << "ms"; + } + } + if (hunspell) { + Hunspell_destroy(hunspell); + hunspell = nullptr; + } +} + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellworker_p.h b/src/plugins/hunspell/hunspellinputmethod/hunspellworker_p.h new file mode 100644 index 00000000..2a8a2694 --- /dev/null +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellworker_p.h @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef HUNSPELLWORKER_P_H +#define HUNSPELLWORKER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QThread> +#include <QSemaphore> +#include <QMutex> +#include <QStringList> +#include <QSharedPointer> +#include <QVector> +#include <QLoggingCategory> +#include <hunspell/hunspell.h> +#include <QtHunspellInputMethod/qhunspellinputmethod_global.h> + +QT_BEGIN_NAMESPACE +class QTextCodec; + +namespace QtVirtualKeyboard { + +Q_DECLARE_LOGGING_CATEGORY(lcHunspell) + +class QHUNSPELLINPUTMETHOD_EXPORT HunspellWordList +{ +public: + enum Flag + { + SpellCheckOk = 0x1, + CompoundWord = 0x2 + }; + Q_DECLARE_FLAGS(Flags, Flag) + + HunspellWordList(int limit = 0); + HunspellWordList(HunspellWordList &other); + + HunspellWordList &operator=(HunspellWordList &other); + + int index() const; + void setIndex(int index); + bool clear(); + bool clearSuggestions(); + bool hasSuggestions() const; + int size() const; + int isEmpty() const; + bool contains(const QString &word); + QString findWordCompletion(const QString &word); + int indexOfWord(const QString &word); + QString wordAt(int index); + void wordAt(int index, QString &word, Flags &flags); + const Flags &wordFlagsAt(int index); + void appendWord(const QString &word, const Flags &flags = Flags()); + void insertWord(int index, const QString &word, const Flags &flags = Flags()); + void updateWord(int index, const QString &word, const Flags &flags = Flags()); + void moveWord(int from, int to); + int removeWord(const QString &word); + void removeWordAt(int index); + void rebuildSearchIndex(); + +private: + class SearchContext { + public: + SearchContext(const QString &word, + const QStringList &list) : + word(word), + list(list) + {} + const QString &word; + const QStringList &list; + }; + +private: + QMutex _lock; + QStringList _list; + QVector<Flags> _flags; + QVector<int> _searchIndex; + int _index; + int _limit; +}; + +class HunspellTask : public QObject +{ + Q_OBJECT +public: + explicit HunspellTask(QObject *parent = nullptr) : + QObject(parent), + hunspell(nullptr) + {} + + virtual void run() = 0; + + Hunhandle *hunspell; +}; + +class HunspellLoadDictionaryTask : public HunspellTask +{ + Q_OBJECT +public: + explicit HunspellLoadDictionaryTask(const QString &locale, const QStringList &searchPaths); + + void run(); + +signals: + void completed(bool success); + +public: + Hunhandle **hunspellPtr; + const QString locale; + const QStringList searchPaths; +}; + +class HunspellBuildSuggestionsTask : public HunspellTask +{ + Q_OBJECT + const QTextCodec *textCodec; +public: + QSharedPointer<HunspellWordList> wordList; + bool autoCorrect; + + void run(); + bool spellCheck(const QString &word); + int levenshteinDistance(const QString &s, const QString &t); + QString removeAccentsAndDiacritics(const QString& s); +}; + +class HunspellUpdateSuggestionsTask : public HunspellTask +{ + Q_OBJECT +public: + QSharedPointer<HunspellWordList> wordList; + + void run(); + +signals: + void updateSuggestions(const QSharedPointer<HunspellWordList> &wordList, int tag); + +public: + int tag; +}; + +class HunspellAddWordTask : public HunspellTask +{ + Q_OBJECT +public: + QSharedPointer<HunspellWordList> wordList; + + void run(); + + static bool alternativeForm(const QString &word, QString &alternativeForm); +}; + +class HunspellRemoveWordTask : public HunspellTask +{ + Q_OBJECT +public: + QSharedPointer<HunspellWordList> wordList; + + void run(); +}; + +class HunspellLoadWordListTask : public HunspellTask +{ + Q_OBJECT +public: + QSharedPointer<HunspellWordList> wordList; + QString filePath; + + void run(); +}; + +class HunspellSaveWordListTask : public HunspellTask +{ + Q_OBJECT +public: + QSharedPointer<HunspellWordList> wordList; + QString filePath; + + void run(); +}; + +class HunspellFilterWordTask : public HunspellTask +{ + Q_OBJECT +public: + HunspellFilterWordTask() : + HunspellTask(), + startIndex(1) + {} + + QSharedPointer<HunspellWordList> wordList; + QSharedPointer<HunspellWordList> filterList; + int startIndex; + + void run(); +}; + +class HunspellBoostWordTask : public HunspellTask +{ + Q_OBJECT +public: + HunspellBoostWordTask() : + HunspellTask() + {} + + QSharedPointer<HunspellWordList> wordList; + QSharedPointer<HunspellWordList> boostList; + + void run(); +}; + +class HunspellWorker : public QThread +{ + Q_OBJECT +public: + explicit HunspellWorker(QObject *parent = nullptr); + ~HunspellWorker(); + + void addTask(QSharedPointer<HunspellTask> task); + void removeAllTasks(); + void waitForAllTasks(); + + template <class X> + void removeAllTasksOfType() { + QMutexLocker guard(&taskLock); + for (int i = 0; i < taskList.size();) { + QSharedPointer<X> task(taskList[i].objectCast<X>()); + if (task) { + qCDebug(lcHunspell) << "Remove task" << QLatin1String(task->metaObject()->className()); + taskList.removeAt(i); + } else { + i++; + } + } + } + +protected: + void run(); + +private: + void createHunspell(); + +private: + friend class HunspellLoadDictionaryTask; + QList<QSharedPointer<HunspellTask> > taskList; + QSemaphore idleSema; + QSemaphore taskSema; + QMutex taskLock; + Hunhandle *hunspell; + QBasicAtomicInt abort; +}; + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QSharedPointer<QT_PREPEND_NAMESPACE(QtVirtualKeyboard)::HunspellWordList>); + +#endif // HUNSPELLWORKER_P_H diff --git a/src/plugins/hunspell/hunspellinputmethod/qhunspellinputmethod_global.h b/src/plugins/hunspell/hunspellinputmethod/qhunspellinputmethod_global.h new file mode 100644 index 00000000..9a548c92 --- /dev/null +++ b/src/plugins/hunspell/hunspellinputmethod/qhunspellinputmethod_global.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ + +#ifndef QHUNSPELLINPUTMETHOD_GLOBAL_H +#define QHUNSPELLINPUTMETHOD_GLOBAL_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_STATIC +# if defined(QHUNSPELLINPUTMETHOD_LIBRARY) +# define QHUNSPELLINPUTMETHOD_EXPORT Q_DECL_EXPORT +# else +# define QHUNSPELLINPUTMETHOD_EXPORT Q_DECL_IMPORT +# endif +#else +# define QHUNSPELLINPUTMETHOD_EXPORT +#endif + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/hunspell/plugin/hunspell.json b/src/plugins/hunspell/plugin/hunspell.json new file mode 100644 index 00000000..449e440c --- /dev/null +++ b/src/plugins/hunspell/plugin/hunspell.json @@ -0,0 +1,6 @@ +{ + "Name": "default", + "Provider": "Qt Hunspell Extension", + "InputMethod": "DefaultInputMethod", + "Version": 100 +} diff --git a/src/plugins/hunspell/plugin/hunspellplugin.cpp b/src/plugins/hunspell/plugin/hunspellplugin.cpp new file mode 100644 index 00000000..0a952675 --- /dev/null +++ b/src/plugins/hunspell/plugin/hunspellplugin.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "hunspellplugin.h" +#include <QtHunspellInputMethod/private/hunspellinputmethod_p.h> +#include <QtQml> + +QT_BEGIN_NAMESPACE + +using namespace QtVirtualKeyboard; + +void QtVirtualKeyboardHunspellPlugin::registerTypes(const char *uri) const +{ + qmlRegisterType<HunspellInputMethod>(uri, 1, 0, "HunspellInputMethod"); + qmlRegisterType<HunspellInputMethod>(uri, 2, 0, "HunspellInputMethod"); + qmlRegisterType<HunspellInputMethod>(uri, 2, 3, "DefaultInputMethod"); +} + +QT_END_NAMESPACE diff --git a/src/plugins/hunspell/plugin/hunspellplugin.h b/src/plugins/hunspell/plugin/hunspellplugin.h new file mode 100644 index 00000000..6880f69c --- /dev/null +++ b/src/plugins/hunspell/plugin/hunspellplugin.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ + +#ifndef HUNSPELLPLUGIN_H +#define HUNSPELLPLUGIN_H + +#include <QVirtualKeyboardExtensionPlugin> + +QT_BEGIN_NAMESPACE + +class QtVirtualKeyboardHunspellPlugin : public QVirtualKeyboardExtensionPlugin +{ + Q_OBJECT + Q_INTERFACES(QVirtualKeyboardExtensionPlugin) + Q_PLUGIN_METADATA(IID QVirtualKeyboardExtensionPluginFactoryInterface_iid + FILE "hunspell.json") +public: + void registerTypes(const char *uri) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/hunspell/plugin/plugin.pro b/src/plugins/hunspell/plugin/plugin.pro new file mode 100644 index 00000000..1981b5e5 --- /dev/null +++ b/src/plugins/hunspell/plugin/plugin.pro @@ -0,0 +1,40 @@ +TARGET = qtvirtualkeyboard_hunspell +QT += qml virtualkeyboard hunspellinputmethod-private + +HEADERS += \ + hunspellplugin.h +SOURCES += \ + hunspellplugin.cpp +OTHER_FILES += \ + hunspell.json + +DEFINES += \ + QT_NO_CAST_TO_ASCII \ + QT_ASCII_CAST_WARNINGS \ + QT_NO_CAST_FROM_ASCII \ + QT_NO_CAST_FROM_BYTEARRAY + +include(../../../config.pri) + +hunspell-library { + exists(../3rdparty/hunspell/data) { + hunspell_data.files = \ + $$PWD/../3rdparty/hunspell/data/*.dic \ + $$PWD/../3rdparty/hunspell/data/*.aff + hunspell_data.path = $$VIRTUALKEYBOARD_INSTALL_DATA/hunspell + INSTALLS += hunspell_data + !prefix_build: COPIES += hunspell_data + } else { + error("Hunspell dictionaries are missing! Please copy .dic and .aff" \ + "files to src/plugins/hunspell/3rdparty/hunspell/data directory.") + } +} + +win32 { + QMAKE_TARGET_PRODUCT = "Qt Virtual Keyboard Hunspell (Qt $$QT_VERSION)" + QMAKE_TARGET_DESCRIPTION = "Virtual Keyboard Extension for Qt." +} + +PLUGIN_TYPE = virtualkeyboard +PLUGIN_CLASS_NAME = QtVirtualKeyboardHunspellPlugin +load(qt_plugin) |