From de69ef24b15fde429bbc04b7bffea667e83b8168 Mon Sep 17 00:00:00 2001 From: Jarkko Koivikko Date: Thu, 31 Mar 2022 08:25:40 +0300 Subject: Cleanup direct hunspell.h include from hunspell module interface Including the hunspellinputmethod.h module header was not possible without also having hunspell headers in the include path. This was because the module header included the hunspellworker_p.h header, which in turn had the dependency to hunspell.h. Fix this by making the following changes: - Use the common QObjectPrivate derived private class for virtual keyboard input methods QVirtualKeyboardAbstractInputMethod to enable use of private slots (to hide connections with the HunspellWorker class). - Move HunspellWordList class out of the hunspellworker_p.h header to another private header, which does not depend on the hunspell.h. - Use private slots in the HunspellInputMethodPrivate to communicate with the HunspellWorker. Change-Id: I2b2e0dc922237388108470a2ae8da3a145fa4078 Reviewed-by: Ulf Hermann --- .../hunspell/hunspellinputmethod/CMakeLists.txt | 1 + .../hunspellinputmethod/hunspellinputmethod.cpp | 45 +-- .../hunspellinputmethod/hunspellinputmethod_p.cpp | 46 ++- .../hunspellinputmethod/hunspellinputmethod_p.h | 11 +- .../hunspellinputmethod/hunspellinputmethod_p_p.h | 14 +- .../hunspellinputmethod/hunspellwordlist.cpp | 334 +++++++++++++++++++++ .../hunspellinputmethod/hunspellwordlist_p.h | 112 +++++++ .../hunspellinputmethod/hunspellworker.cpp | 296 ------------------ .../hunspellinputmethod/hunspellworker_p.h | 61 +--- 9 files changed, 505 insertions(+), 415 deletions(-) create mode 100644 src/plugins/hunspell/hunspellinputmethod/hunspellwordlist.cpp create mode 100644 src/plugins/hunspell/hunspellinputmethod/hunspellwordlist_p.h diff --git a/src/plugins/hunspell/hunspellinputmethod/CMakeLists.txt b/src/plugins/hunspell/hunspellinputmethod/CMakeLists.txt index d01f4eac..f07cf03f 100644 --- a/src/plugins/hunspell/hunspellinputmethod/CMakeLists.txt +++ b/src/plugins/hunspell/hunspellinputmethod/CMakeLists.txt @@ -10,6 +10,7 @@ qt_internal_add_module(HunspellInputMethodPrivate hunspellinputmethod.cpp hunspellinputmethod_p.cpp hunspellinputmethod_p.h hunspellinputmethod_p_p.h hunspellworker.cpp hunspellworker_p.h + hunspellwordlist.cpp hunspellwordlist_p.h qhunspellinputmethod_global.h DEFINES QHUNSPELLINPUTMETHOD_LIBRARY diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.cpp b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.cpp index 0b365c08..d179e2a6 100644 --- a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.cpp +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.cpp @@ -27,7 +27,7 @@ ** ****************************************************************************/ -#include +#include "hunspellinputmethod_p.h" #include #include @@ -41,15 +41,13 @@ Q_LOGGING_CATEGORY(lcHunspell, "qt.virtualkeyboard.hunspell") \internal */ -HunspellInputMethod::HunspellInputMethod(HunspellInputMethodPrivate *d_ptr, QObject *parent) : - QVirtualKeyboardAbstractInputMethod(parent), - d_ptr(d_ptr) +HunspellInputMethod::HunspellInputMethod(HunspellInputMethodPrivate &dd, QObject *parent) : + QVirtualKeyboardAbstractInputMethod(dd, parent) { } HunspellInputMethod::HunspellInputMethod(QObject *parent) : - QVirtualKeyboardAbstractInputMethod(parent), - d_ptr(new HunspellInputMethodPrivate(this)) + QVirtualKeyboardAbstractInputMethod(*new HunspellInputMethodPrivate(this), parent) { } @@ -374,40 +372,5 @@ void HunspellInputMethod::update() 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 diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.cpp b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.cpp index 4a54a332..9d01a7bf 100644 --- a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.cpp +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.cpp @@ -27,9 +27,10 @@ ** ****************************************************************************/ -#include +#include "hunspellinputmethod_p_p.h" +#include "hunspellinputmethod_p.h" +#include "hunspellworker_p.h" #include -#include #include #include #include @@ -94,7 +95,7 @@ bool HunspellInputMethodPrivate::createHunspell(const QString &locale) searchPaths.append(defaultPath); } QSharedPointer loadDictionaryTask(new HunspellLoadDictionaryTask(locale, searchPaths)); - QObject::connect(loadDictionaryTask.data(), &HunspellLoadDictionaryTask::completed, q, &HunspellInputMethod::dictionaryLoadCompleted); + QObjectPrivate::connect(loadDictionaryTask.data(), &HunspellLoadDictionaryTask::completed, this, &HunspellInputMethodPrivate::dictionaryLoadCompleted); dictionaryState = HunspellInputMethodPrivate::DictionaryLoading; emit q->selectionListsChanged(); hunspellWorker->addTask(loadDictionaryTask); @@ -154,8 +155,7 @@ bool HunspellInputMethodPrivate::updateSuggestions() QSharedPointer updateSuggestionsTask(new HunspellUpdateSuggestionsTask()); updateSuggestionsTask->wordList = wordList; updateSuggestionsTask->tag = ++wordCandidatesUpdateTag; - Q_Q(HunspellInputMethod); - QObject::connect(updateSuggestionsTask.data(), &HunspellUpdateSuggestionsTask::updateSuggestions, q, &HunspellInputMethod::updateSuggestions); + QObjectPrivate::connect(updateSuggestionsTask.data(), &HunspellUpdateSuggestionsTask::updateSuggestions, this, &HunspellInputMethodPrivate::updateSuggestionsCompleted); hunspellWorker->addTask(updateSuggestionsTask); } } @@ -333,5 +333,41 @@ void HunspellInputMethodPrivate::addToDictionary() } } +void HunspellInputMethodPrivate::updateSuggestionsCompleted(const QSharedPointer &wordList, int tag) +{ + if (dictionaryState == HunspellInputMethodPrivate::DictionaryNotLoaded) { + qCDebug(lcHunspell) << "updateSuggestions: skip (dictionary not loaded)"; + Q_Q(HunspellInputMethod); + q->update(); + return; + } + if (wordCandidatesUpdateTag != tag) { + qCDebug(lcHunspell) << "updateSuggestions: skip tag" << tag << "current" << wordCandidatesUpdateTag; + return; + } + QString word(wordCandidates.wordAt(0)); + wordCandidates = *wordList; + if (wordCandidates.wordAt(0).compare(word) != 0) + wordCandidates.updateWord(0, word); + Q_Q(HunspellInputMethod); + emit q->selectionListChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList); + emit q->selectionListActiveItemChanged(QVirtualKeyboardSelectionListModel::Type::WordCandidateList, wordCandidates.index()); +} + +void HunspellInputMethodPrivate::dictionaryLoadCompleted(bool success) +{ + Q_Q(HunspellInputMethod); + QVirtualKeyboardInputContext *ic = q->inputContext(); + if (!ic) + return; + + QList oldSelectionLists = q->selectionLists(); + dictionaryState = success ? HunspellInputMethodPrivate::DictionaryReady : + HunspellInputMethodPrivate::DictionaryNotLoaded; + QList newSelectionLists = q->selectionLists(); + if (oldSelectionLists != newSelectionLists) + emit q->selectionListsChanged(); +} + } // namespace QtVirtualKeyboard QT_END_NAMESPACE diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.h b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.h index 6144536f..39c88f0c 100644 --- a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.h +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.h @@ -43,11 +43,11 @@ #include #include +#include QT_BEGIN_NAMESPACE namespace QtVirtualKeyboard { -class HunspellInputMethodPrivate; class HunspellWordList; class Q_HUNSPELLINPUTMETHOD_EXPORT HunspellInputMethod : public QVirtualKeyboardAbstractInputMethod @@ -55,7 +55,7 @@ class Q_HUNSPELLINPUTMETHOD_EXPORT HunspellInputMethod : public QVirtualKeyboard Q_OBJECT Q_DECLARE_PRIVATE(HunspellInputMethod) protected: - HunspellInputMethod(HunspellInputMethodPrivate *d_ptr, QObject *parent); + HunspellInputMethod(HunspellInputMethodPrivate &dd, QObject *parent); public: explicit HunspellInputMethod(QObject *parent = nullptr); ~HunspellInputMethod(); @@ -77,13 +77,6 @@ public: void reset() override; void update() override; - -protected Q_SLOTS: - void updateSuggestions(const QSharedPointer &wordList, int tag); - void dictionaryLoadCompleted(bool success); - -protected: - QScopedPointer d_ptr; }; } // namespace QtVirtualKeyboard diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p_p.h b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p_p.h index 763a8c43..42b862e5 100644 --- a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p_p.h +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p_p.h @@ -41,17 +41,21 @@ // We mean it. // -#include -#include +#include +#include +#include QT_BEGIN_NAMESPACE namespace QtVirtualKeyboard { -class Q_HUNSPELLINPUTMETHOD_EXPORT HunspellInputMethodPrivate +class HunspellInputMethod; +class HunspellWorker; + +class Q_HUNSPELLINPUTMETHOD_EXPORT HunspellInputMethodPrivate : public QVirtualKeyboardAbstractInputMethodPrivate { +public: Q_DECLARE_PUBLIC(HunspellInputMethod) -public: HunspellInputMethodPrivate(HunspellInputMethod *q_ptr); ~HunspellInputMethodPrivate(); @@ -76,6 +80,8 @@ public: void removeFromHunspell(const QSharedPointer &wordList) const; void removeFromDictionary(const QString &word); void addToDictionary(); + void updateSuggestionsCompleted(const QSharedPointer &wordList, int tag); + void dictionaryLoadCompleted(bool success); HunspellInputMethod *q_ptr; QScopedPointer hunspellWorker; diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellwordlist.cpp b/src/plugins/hunspell/hunspellinputmethod/hunspellwordlist.cpp new file mode 100644 index 00000000..87b3ad24 --- /dev/null +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellwordlist.cpp @@ -0,0 +1,334 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 "hunspellwordlist_p.h" +#include +#include + +QT_BEGIN_NAMESPACE +namespace QtVirtualKeyboard { + +/*! + \class QtVirtualKeyboard::HunspellWordList + \internal +*/ + +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; }); +} + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellwordlist_p.h b/src/plugins/hunspell/hunspellinputmethod/hunspellwordlist_p.h new file mode 100644 index 00000000..036076a1 --- /dev/null +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellwordlist_p.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 HUNSPELLWORDLIST_P_H +#define HUNSPELLWORDLIST_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 +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QtVirtualKeyboard { + +class Q_HUNSPELLINPUTMETHOD_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; + QList _flags; + QList _searchIndex; + int _index; + int _limit; +}; + +} // namespace QtVirtualKeyboard +QT_END_NAMESPACE + +#endif // HUNSPELLWORDLIST_P_H diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellworker.cpp b/src/plugins/hunspell/hunspellinputmethod/hunspellworker.cpp index 51926eec..16d5fd62 100644 --- a/src/plugins/hunspell/hunspellinputmethod/hunspellworker.cpp +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellworker.cpp @@ -39,307 +39,11 @@ 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 diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellworker_p.h b/src/plugins/hunspell/hunspellinputmethod/hunspellworker_p.h index 187f9f70..aa1dd078 100644 --- a/src/plugins/hunspell/hunspellinputmethod/hunspellworker_p.h +++ b/src/plugins/hunspell/hunspellinputmethod/hunspellworker_p.h @@ -51,7 +51,7 @@ #include #include #include -#include +#include "hunspellwordlist_p.h" QT_BEGIN_NAMESPACE @@ -59,63 +59,6 @@ namespace QtVirtualKeyboard { Q_DECLARE_LOGGING_CATEGORY(lcHunspell) -class Q_HUNSPELLINPUTMETHOD_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; - QList _flags; - QList _searchIndex; - int _index; - int _limit; -}; - class HunspellTask : public QObject { Q_OBJECT @@ -293,6 +236,4 @@ private: } // namespace QtVirtualKeyboard QT_END_NAMESPACE -Q_DECLARE_METATYPE(QSharedPointer); - #endif // HUNSPELLWORKER_P_H -- cgit v1.2.3