File indexing completed on 2025-01-19 03:51:19
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2010-06-01 0007 * Description : A widget to search for places. 0008 * 0009 * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * SPDX-FileCopyrightText: 2010-2011 by Michael G. Hansen <mike at mghansen dot de> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "searchresultmodel.h" 0017 0018 // Qt includes 0019 0020 #include <QContextMenuEvent> 0021 #include <QPainter> 0022 #include <QAction> 0023 #include <QStandardPaths> 0024 0025 // local includes 0026 0027 #include "gpscommon.h" 0028 #include "gpsundocommand.h" 0029 #include "gpsitemmodel.h" 0030 0031 namespace DigikamGenericGeolocationEditPlugin 0032 { 0033 0034 static bool RowRangeLessThan(const QPair<int, int>& a, const QPair<int, int>& b) 0035 { 0036 return (a.first < b.first); 0037 } 0038 0039 class Q_DECL_HIDDEN SearchResultModel::Private 0040 { 0041 public: 0042 0043 explicit Private() 0044 { 0045 markerNormalUrl = QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, 0046 QLatin1String("digikam/geolocationedit/searchmarker-normal.png"))); 0047 markerNormal = QPixmap(markerNormalUrl.toLocalFile()); 0048 markerSelectedUrl = QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, 0049 QLatin1String("digikam/geolocationedit/searchmarker-selected.png"))); 0050 markerSelected = QPixmap(markerSelectedUrl.toLocalFile()); 0051 selectionModel = nullptr; 0052 } 0053 0054 QList<SearchResultModel::SearchResultItem> searchResults; 0055 QUrl markerNormalUrl; 0056 QUrl markerSelectedUrl; 0057 QPixmap markerNormal; 0058 QPixmap markerSelected; 0059 QItemSelectionModel* selectionModel; 0060 }; 0061 0062 SearchResultModel::SearchResultModel(QObject* const parent) 0063 : QAbstractItemModel(parent), 0064 d (new Private()) 0065 { 0066 } 0067 0068 SearchResultModel::~SearchResultModel() 0069 { 0070 delete d; 0071 } 0072 0073 int SearchResultModel::columnCount(const QModelIndex& parent) const 0074 { 0075 Q_UNUSED(parent) 0076 0077 return 1; 0078 } 0079 0080 bool SearchResultModel::setData(const QModelIndex& index, const QVariant& value, int role) 0081 { 0082 Q_UNUSED(index) 0083 Q_UNUSED(value) 0084 Q_UNUSED(role) 0085 0086 return false; 0087 } 0088 0089 QVariant SearchResultModel::data(const QModelIndex& index, int role) const 0090 { 0091 const int rowNumber = index.row(); 0092 0093 if ((rowNumber < 0) || (rowNumber >= d->searchResults.count())) 0094 { 0095 return QVariant(); 0096 } 0097 0098 const int columnNumber = index.column(); 0099 0100 if (columnNumber == 0) 0101 { 0102 switch (role) 0103 { 0104 case Qt::DisplayRole: 0105 { 0106 return d->searchResults.at(rowNumber).result.name; 0107 } 0108 0109 case Qt::DecorationRole: 0110 { 0111 QPixmap markerIcon; 0112 getMarkerIcon(index, nullptr, nullptr, &markerIcon, nullptr); 0113 return markerIcon; 0114 } 0115 0116 default: 0117 { 0118 return QVariant(); 0119 } 0120 } 0121 } 0122 0123 return QVariant(); 0124 } 0125 0126 QModelIndex SearchResultModel::index(int row, int column, const QModelIndex& parent) const 0127 { 0128 if (parent.isValid()) 0129 { 0130 // there are no child items, only top level items 0131 0132 return QModelIndex(); 0133 } 0134 0135 if ((column < 0) || (column >= 1) || (row < 0) || (row >= d->searchResults.count())) 0136 { 0137 return QModelIndex(); 0138 } 0139 0140 return createIndex(row, column, (void*)nullptr); 0141 } 0142 0143 QModelIndex SearchResultModel::parent(const QModelIndex& index) const 0144 { 0145 Q_UNUSED(index) 0146 0147 // we have only top level items 0148 0149 return QModelIndex(); 0150 } 0151 0152 int SearchResultModel::rowCount(const QModelIndex& parent) const 0153 { 0154 if (parent.isValid()) 0155 { 0156 return 0; 0157 } 0158 0159 return d->searchResults.count(); 0160 } 0161 0162 bool SearchResultModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role) 0163 { 0164 Q_UNUSED(section) 0165 Q_UNUSED(orientation) 0166 Q_UNUSED(value) 0167 Q_UNUSED(role) 0168 0169 return false; 0170 } 0171 0172 QVariant SearchResultModel::headerData(int section, Qt::Orientation orientation, int role) const 0173 { 0174 Q_UNUSED(role) 0175 0176 if ((section >= 1) || (orientation != Qt::Horizontal)) 0177 { 0178 return false; 0179 } 0180 0181 return QVariant(QLatin1String("Name")); 0182 } 0183 0184 Qt::ItemFlags SearchResultModel::flags(const QModelIndex& index) const 0185 { 0186 return QAbstractItemModel::flags(index); 0187 } 0188 0189 void SearchResultModel::addResults(const SearchResultBackend::SearchResult::List& results) 0190 { 0191 // first check which items are not duplicates 0192 0193 QList<int> nonDuplicates; 0194 0195 for (int i = 0 ; i < results.count() ; ++i) 0196 { 0197 const SearchResultBackend::SearchResult& currentResult = results.at(i); 0198 bool isDuplicate = false; 0199 0200 for (int j = 0 ; j < d->searchResults.count() ; ++j) 0201 { 0202 if (currentResult.internalId == d->searchResults.at(j).result.internalId) 0203 { 0204 isDuplicate = true; 0205 break; 0206 } 0207 } 0208 0209 if (!isDuplicate) 0210 { 0211 nonDuplicates << i; 0212 } 0213 } 0214 0215 if (nonDuplicates.isEmpty()) 0216 { 0217 return; 0218 } 0219 0220 beginInsertRows(QModelIndex(), d->searchResults.count(), d->searchResults.count()+nonDuplicates.count()-1); 0221 0222 for (int i = 0 ; i < nonDuplicates.count() ; ++i) 0223 { 0224 SearchResultItem item; 0225 item.result = results.at(nonDuplicates.at(i)); 0226 d->searchResults << item; 0227 } 0228 0229 endInsertRows(); 0230 } 0231 0232 SearchResultModel::SearchResultItem SearchResultModel::resultItem(const QModelIndex& index) const 0233 { 0234 if (!index.isValid()) 0235 { 0236 return SearchResultItem(); 0237 } 0238 0239 return d->searchResults.at(index.row()); 0240 } 0241 0242 bool SearchResultModel::getMarkerIcon(const QModelIndex& index, QPoint* const offset, QSize* const size, QPixmap* const pixmap, QUrl* const url) const 0243 { 0244 // determine the id of the marker 0245 0246 const int markerNumber = index.row(); 0247 const bool itemIsSelected = d->selectionModel ? d->selectionModel->isSelected(index) : false; 0248 QPixmap markerPixmap = itemIsSelected ? d->markerSelected : d->markerNormal; 0249 0250 // if the caller requests a URL and the marker will not get 0251 // a special label, return a URL. Otherwise, return a pixmap. 0252 0253 const bool returnViaUrl = url && (markerNumber > 26); 0254 0255 if (returnViaUrl) 0256 { 0257 *url = itemIsSelected ? d->markerSelectedUrl : d->markerNormalUrl; 0258 0259 if (size) 0260 { 0261 *size = markerPixmap.size(); 0262 } 0263 } 0264 else 0265 { 0266 if (markerNumber <= 26) 0267 { 0268 const QString markerId = QChar('A' + markerNumber); 0269 QPainter painter(&markerPixmap); 0270 painter.setRenderHint(QPainter::Antialiasing); 0271 painter.setPen(Qt::black); 0272 QRect textRect(0, 2, markerPixmap.width(), markerPixmap.height()); 0273 painter.drawText(textRect, Qt::AlignHCenter, markerId); 0274 } 0275 0276 *pixmap = markerPixmap; 0277 } 0278 0279 if (offset) 0280 { 0281 *offset = QPoint(markerPixmap.width()/2, markerPixmap.height()-1); 0282 } 0283 0284 return true; 0285 } 0286 0287 void SearchResultModel::setSelectionModel(QItemSelectionModel* const selectionModel) 0288 { 0289 d->selectionModel = selectionModel; 0290 } 0291 0292 void SearchResultModel::clearResults() 0293 { 0294 beginResetModel(); 0295 d->searchResults.clear(); 0296 endResetModel(); 0297 } 0298 0299 void SearchResultModel::removeRowsByIndexes(const QModelIndexList& rowsList) 0300 { 0301 // extract the row numbers first: 0302 0303 QList<int> rowNumbers; 0304 0305 Q_FOREACH (const QModelIndex& index, rowsList) 0306 { 0307 if (index.isValid()) 0308 { 0309 rowNumbers << index.row(); 0310 } 0311 } 0312 0313 if (rowNumbers.isEmpty()) 0314 { 0315 return; 0316 } 0317 0318 std::sort(rowNumbers.begin(), rowNumbers.end()); 0319 0320 // now delete the rows, starting with the last row: 0321 0322 for (int i = rowNumbers.count()-1 ; i >= 0 ; --i) 0323 { 0324 const int rowNumber = rowNumbers.at(i); 0325 0326 /// @todo This is very slow for several indexes, because the views update after every removal 0327 0328 beginRemoveRows(QModelIndex(), rowNumber, rowNumber); 0329 d->searchResults.removeAt(rowNumber); 0330 endRemoveRows(); 0331 } 0332 } 0333 0334 void SearchResultModel::removeRowsBySelection(const QItemSelection& selectionList) 0335 { 0336 // extract the row numbers first: 0337 0338 QList<QPair<int, int> > rowRanges; 0339 0340 Q_FOREACH (const QItemSelectionRange& range, selectionList) 0341 { 0342 rowRanges << QPair<int, int>(range.top(), range.bottom()); 0343 } 0344 0345 // we expect the ranges to be sorted here 0346 0347 std::sort(rowRanges.begin(), rowRanges.end(), RowRangeLessThan); 0348 0349 // now delete the rows, starting with the last row: 0350 0351 for (int i = rowRanges.count()-1 ; i >= 0 ; --i) 0352 { 0353 const QPair<int, int> currentRange = rowRanges.at(i); 0354 0355 /// @todo This is very slow for several indexes, because the views update after every removal 0356 0357 beginRemoveRows(QModelIndex(), currentRange.first, currentRange.second); 0358 0359 for (int j = currentRange.second ; j >= currentRange.first ; --j) 0360 { 0361 d->searchResults.removeAt(j); 0362 } 0363 0364 endRemoveRows(); 0365 } 0366 } 0367 0368 } // namespace DigikamGenericGeolocationEditPlugin 0369 0370 #include "moc_searchresultmodel.cpp"