Skip to content
Snippets Groups Projects
CompletionProxyModel.cpp 4.82 KiB
Newer Older
  • Learn to ignore specific revisions
  • // SPDX-FileCopyrightText: Nheko Contributors
    
    Nicolas Werner's avatar
    Nicolas Werner committed
    //
    // 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,
    
      : QAbstractProxyModel(parent)
    
      , 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()
    
            if (!string1.isEmpty()) {
                trie_.insert<ElementRank::first>(string1.toUcs4(), i);
                insertParts(string1, i);
            }
    
    
            auto string2 = sourceModel()
                             ->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
                             .toString()
    
            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 {};
    
    CompletionProxyModel::setSearchString(const QString &s)
    
        emit newSearchString(s);