You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
nheko/src/CompletionProxyModel.cpp

172 lines
4.8 KiB

// SPDX-FileCopyrightText: Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "CompletionProxyModel.h"
#include <QRegularExpression>
#include <QTextBoundaryFinder>
#include "CompletionModelRoles.h"
#include "Logging.h"
#include "Utils.h"
CompletionProxyModel::CompletionProxyModel(QAbstractItemModel *model,
int max_mistakes,
size_t max_completions,
QObject *parent)
: QAbstractProxyModel(parent)
, maxMistakes_(max_mistakes)
, max_completions_(max_completions)
{
setSourceModel(model);
auto insertParts = [this](const QString &str, int id) {
QTextBoundaryFinder finder(QTextBoundaryFinder::BoundaryType::Word, str);
finder.toStart();
do {
auto start = finder.position();
finder.toNextBoundary();
auto end = finder.position();
auto ref = str.midRef(start, end - start).trimmed();
if (!ref.isEmpty())
trie_.insert<ElementRank::second>(ref.toUcs4(), id);
} while (finder.position() < str.size());
};
const auto start_at = std::chrono::steady_clock::now();
// insert full texts and partial matches
for (int i = 0; i < sourceModel()->rowCount(); i++) {
// full texts are ranked first and partial matches second
// that way when searching full texts will be first in result list
auto string1 = sourceModel()
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
.toString()
.toCaseFolded();
if (!string1.isEmpty()) {
trie_.insert<ElementRank::first>(string1.toUcs4(), i);
insertParts(string1, i);
}
auto string2 = sourceModel()
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
.toString()
.toCaseFolded();
if (!string2.isEmpty()) {
trie_.insert<ElementRank::first>(string2.toUcs4(), i);
insertParts(string2, i);
}
}
const auto end_at = std::chrono::steady_clock::now();
const auto build_time = std::chrono::duration<double, std::milli>(end_at - start_at);
nhlog::ui()->debug("CompletionProxyModel: build trie: {} ms", build_time.count());
// initialize default mapping
mapping.resize(std::min(max_completions_, static_cast<size_t>(model->rowCount())));
std::iota(mapping.begin(), mapping.end(), 0);
connect(
this,
&CompletionProxyModel::newSearchString,
this,
[this](const QString &s) {
searchString_ = s.toCaseFolded();
invalidate();
},
Qt::QueuedConnection);
}
void
CompletionProxyModel::invalidate()
{
auto key = searchString_.toUcs4();
beginResetModel();
if (!key.empty()) // return default model data, if no search string
mapping = trie_.search(key, max_completions_, maxMistakes_);
endResetModel();
}
QHash<int, QByteArray>
CompletionProxyModel::roleNames() const
{
return this->sourceModel()->roleNames();
}
int
CompletionProxyModel::rowCount(const QModelIndex &) const
{
if (searchString_.isEmpty())
return std::min(
static_cast<int>(std::min<size_t>(max_completions_, std::numeric_limits<int>::max())),
sourceModel()->rowCount());
else
return (int)mapping.size();
}
QModelIndex
CompletionProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
// return default model data, if no search string
if (searchString_.isEmpty()) {
return index(sourceIndex.row(), 0);
}
for (int i = 0; i < (int)mapping.size(); i++) {
if (mapping[i] == sourceIndex.row()) {
return index(i, 0);
}
}
return QModelIndex();
}
QModelIndex
CompletionProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
auto row = proxyIndex.row();
// return default model data, if no search string
if (searchString_.isEmpty()) {
return index(row, 0);
}
if (row < 0 || row >= (int)mapping.size())
return QModelIndex();
return sourceModel()->index(mapping[row], 0);
}
QModelIndex
CompletionProxyModel::index(int row, int column, const QModelIndex &) const
{
return createIndex(row, column);
}
QModelIndex
CompletionProxyModel::parent(const QModelIndex &) const
{
return QModelIndex{};
}
int
CompletionProxyModel::columnCount(const QModelIndex &) const
{
return sourceModel()->columnCount();
}
QVariant
CompletionProxyModel::completionAt(int i) const
{
if (i >= 0 && i < rowCount())
return data(index(i, 0), CompletionModel::CompletionRole);
else
return {};
}
void
CompletionProxyModel::setSearchString(const QString &s)
{
emit newSearchString(s);
}