/**************************************************************************** ** ** 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