File indexing completed on 2024-12-15 05:02:05

0001 /*
0002     SPDX-FileCopyrightText: 2016 Ivan Čukić <ivan.cukic(at)kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "ResultModel.h"
0008 
0009 ResultModel::ResultModel()
0010 {
0011     QObject::connect(&m_collector, &Agents::Collector::newResultsAppeared,
0012                      this  ,       &ResultModel::addResults);
0013 }
0014 
0015 int ResultModel::rowCount(const QModelIndex &parent) const
0016 {
0017     if (parent.isValid()) return 0;
0018 
0019     return m_currentResults.size();
0020 }
0021 
0022 QVariant ResultModel::data(const QModelIndex &index, int role) const
0023 {
0024     if (!index.isValid()) return {};
0025 
0026     const auto row = index.row();
0027 
0028     if (row < 0 || row >= m_currentResults.size()) return {};
0029 
0030     const auto &result = m_currentResults[row];
0031 
0032     switch (role) {
0033     case ResultId:
0034         return result.url;
0035 
0036     case ResultTitle:
0037         return result.title;
0038 
0039     case ResultDescription:
0040         return result.description;
0041 
0042     case ResultIcon:
0043         return result.icon;
0044 
0045     case ResultRelevance:
0046         return (qreal)result.relevance;
0047 
0048     case ResultMatchedText:
0049         return result.matchedText;
0050 
0051     case ResultMimeType:
0052         return result.mimeType;
0053 
0054     default:
0055         return {};
0056 
0057     }
0058 }
0059 
0060 QVariant ResultModel::headerData(int section, Qt::Orientation orientation,
0061                     int role) const
0062 {
0063     return "";
0064 }
0065 
0066 QHash<int, QByteArray> ResultModel::roleNames() const
0067 {
0068     return {
0069         { ResultId          , "id" },
0070 
0071         { ResultTitle       , "title" },
0072         { ResultDescription , "description" },
0073         { ResultIcon        , "icon" },
0074 
0075         { ResultUrl         , "url" },
0076         { ResultRelevance   , "relevance" },
0077         { ResultMatchedText , "matchedText" },
0078         { ResultMimeType    , "mimeType" }
0079     };
0080 }
0081 
0082 QString ResultModel::queryString() const
0083 {
0084     return m_collector.queryString();
0085 }
0086 
0087 void ResultModel::setQueryString(const QString &queryString)
0088 {
0089     if (queryString == m_collector.queryString()) {
0090         return;
0091     }
0092 
0093     if (queryString.isEmpty()) {
0094         if (m_currentResults.size() > 0) {
0095             beginResetModel();
0096             m_currentResults.clear();
0097             endResetModel();
0098         }
0099 
0100     } else {
0101         // When the query string changes, we need to go through
0102         // our list of items and remove those that do not contain
0103         // this string.
0104         // The alternative is to remove all the items, but this
0105         // will have nicer behaviour for the user - if we found
0106         // Konsole when the user typed 'kon' it will not be removed
0107         // if the user adds 's'.
0108         for (int row = m_currentResults.size() -1 ; row >= 0; --row) {
0109             // TODO: Some runners do matching by words, some by phrases
0110             //       how it should be handled here?
0111             if (!m_currentResults[row].matchedText.contains(queryString, Qt::CaseInsensitive)) {
0112                 removeRow(row);
0113             }
0114         }
0115 
0116         // The query has changed. If we are keeping any items,
0117         // we are settinh their relevance to zero. This way,
0118         // if the runners do not return them again, they will
0119         // end up at the end of the list
0120         for (auto& currentResult: m_currentResults) {
0121             currentResult.relevance = 0;
0122         }
0123 
0124         qDebug() << "ResultModel - query string changed to" << queryString
0125                  << ", kept" << m_currentResults;
0126     }
0127 
0128     m_collector.setQueryString(queryString);
0129     emit queryStringChanged(queryString);
0130 }
0131 
0132 void ResultModel::addResults(const ResultList &newResults)
0133 {
0134     qDebug() << "ResultModel - adding results" << newResults <<
0135                 "to the" << m_currentResults;
0136 
0137     for (auto newResult: newResults) {
0138         // Check whether this one is already in the list.
0139         // If it is, move it to its rightful place based
0140         // on the relevance.
0141         const auto oldPositionIter
0142             = Result::findResult(m_currentResults, newResult.url);
0143 
0144         if (oldPositionIter != m_currentResults.cend()) {
0145             const auto oldPosition = rowFor(oldPositionIter);
0146 
0147             // If more than one runner returned this result, lets
0148             // combine the relevance
0149             qDebug() << "Old relevance: " << newResult.relevance
0150                      << "Previous relevance: " << oldPositionIter->relevance
0151                      << "New relevance: " << (newResult.relevance || oldPositionIter->relevance);
0152                      ;
0153             newResult.relevance
0154                 = newResult.relevance || oldPositionIter->relevance;
0155 
0156             if (newResult.relevance == oldPositionIter->relevance) {
0157                 continue;
0158             }
0159 
0160             // We have found the item. If the new score is greater,
0161             // it should be moved up, otherwise down.
0162             if (Result::betterThan(newResult, *oldPositionIter)) {
0163                 auto newPositionIter = destinationFor(newResult);
0164 
0165                 oldPositionIter->relevance = newResult.relevance;
0166 
0167                 // We are at a point where the item pointed at by
0168                 // newPositionIter is not better than our current item
0169                 moveRow(oldPosition,
0170                         rowFor(newPositionIter));
0171             } else {
0172                 qFatal("This should not happen - we should always move things "
0173                        "upwards");
0174             }
0175 
0176         } else {
0177             auto newPositionIter = destinationFor(newResult);
0178             insertRow(rowFor(newPositionIter), newResult);
0179 
0180         }
0181 
0182     }
0183 }
0184 
0185 ResultList::iterator ResultModel::destinationFor(const Result &result)
0186 {
0187     auto destination = m_currentResults.begin();
0188 
0189     // This is a small collection, linear search will
0190     // probably be faster than binary
0191     const auto end = m_currentResults.end();
0192     while (destination != end && Result::betterThan(*destination, result)) {
0193         ++destination;
0194     }
0195 
0196     return destination;
0197 }
0198 
0199 void ResultModel::removeRow(int position)
0200 {
0201     beginRemoveRows(QModelIndex(), position, position);
0202     m_currentResults.removeAt(position);
0203     endRemoveRows();
0204 }
0205 
0206 void ResultModel::insertRow(int position, const Result &result)
0207 {
0208     beginInsertRows(QModelIndex(), position, position);
0209     m_currentResults.insert(position, result);
0210     endInsertRows();
0211 }
0212 
0213 void ResultModel::moveRow(int from, int to)
0214 {
0215     if (from == to) return;
0216 
0217     beginMoveRows(QModelIndex(), from, from,
0218                   QModelIndex(), to);
0219 
0220     if (from > to) {
0221         //  _____.......#___
0222         //       `to    `from
0223         //               `from + 1
0224         std::rotate(iterFor(to),
0225                     iterFor(from),
0226                     iterFor(from + 1));
0227     } else if (from < to) {
0228         //  _____#......____
0229         //       `from  `to
0230         //        `from + 1
0231         std::rotate(iterFor(from),
0232                     iterFor(from + 1),
0233                     iterFor(to));
0234     }
0235 
0236     endMoveRows();
0237 }
0238 
0239 int ResultModel::rowFor(ResultList::const_iterator iter) const
0240 {
0241     return std::distance(m_currentResults.cbegin(), iter);
0242 }
0243 
0244 ResultList::iterator ResultModel::iterFor(int row)
0245 {
0246     return m_currentResults.begin() + row;
0247 }