Revision control

++ // Copyright (C) 2022 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "hunspellwordlist_p.h"
++ #include <QtAlgorithms>
++ #include <hunspell/hunspell.h>
++
++ 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.size() > bestMatch.size() &&
++ word.size() < wordB.size() &&
++ 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