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 }