1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2010-06-01
7 * Description : A widget to search for places.
8 *
9 * Copyright (C) 2010-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10 * Copyright (C) 2010-2011 by Michael G. Hansen <mike at mghansen dot de>
11 *
12 * This program is free software; you can redistribute it
13 * and/or modify it under the terms of the GNU General
14 * Public License as published by the Free Software Foundation;
15 * either version 2, or (at your option)
16 * any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * ============================================================ */
24
25 #include "searchresultmodel.h"
26
27 // Qt includes
28
29 #include <QContextMenuEvent>
30 #include <QPainter>
31 #include <QAction>
32 #include <QStandardPaths>
33
34 // local includes
35
36 #include "searchbackend.h"
37 #include "gpscommon.h"
38 #include "gpsundocommand.h"
39 #include "gpsitemmodel.h"
40
41 namespace DigikamGenericGeolocationEditPlugin
42 {
43
RowRangeLessThan(const QPair<int,int> & a,const QPair<int,int> & b)44 static bool RowRangeLessThan(const QPair<int, int>& a, const QPair<int, int>& b)
45 {
46 return (a.first < b.first);
47 }
48
49 class Q_DECL_HIDDEN SearchResultModel::Private
50 {
51 public:
52
Private()53 explicit Private()
54 {
55 markerNormalUrl = QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation,
56 QLatin1String("digikam/geolocationedit/searchmarker-normal.png")));
57 markerNormal = QPixmap(markerNormalUrl.toLocalFile());
58 markerSelectedUrl = QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation,
59 QLatin1String("digikam/geolocationedit/searchmarker-selected.png")));
60 markerSelected = QPixmap(markerSelectedUrl.toLocalFile());
61 selectionModel = nullptr;
62 }
63
64 QList<SearchResultModel::SearchResultItem> searchResults;
65 QUrl markerNormalUrl;
66 QUrl markerSelectedUrl;
67 QPixmap markerNormal;
68 QPixmap markerSelected;
69 QItemSelectionModel* selectionModel;
70 };
71
SearchResultModel(QObject * const parent)72 SearchResultModel::SearchResultModel(QObject* const parent)
73 : QAbstractItemModel(parent),
74 d (new Private())
75 {
76 }
77
~SearchResultModel()78 SearchResultModel::~SearchResultModel()
79 {
80 delete d;
81 }
82
columnCount(const QModelIndex & parent) const83 int SearchResultModel::columnCount(const QModelIndex& parent) const
84 {
85 Q_UNUSED(parent)
86
87 return 1;
88 }
89
setData(const QModelIndex & index,const QVariant & value,int role)90 bool SearchResultModel::setData(const QModelIndex& index, const QVariant& value, int role)
91 {
92 Q_UNUSED(index)
93 Q_UNUSED(value)
94 Q_UNUSED(role)
95
96 return false;
97 }
98
data(const QModelIndex & index,int role) const99 QVariant SearchResultModel::data(const QModelIndex& index, int role) const
100 {
101 const int rowNumber = index.row();
102
103 if ((rowNumber < 0) || (rowNumber >= d->searchResults.count()))
104 {
105 return QVariant();
106 }
107
108 const int columnNumber = index.column();
109
110 if (columnNumber == 0)
111 {
112 switch (role)
113 {
114 case Qt::DisplayRole:
115 {
116 return d->searchResults.at(rowNumber).result.name;
117 }
118
119 case Qt::DecorationRole:
120 {
121 QPixmap markerIcon;
122 getMarkerIcon(index, nullptr, nullptr, &markerIcon, nullptr);
123 return markerIcon;
124 }
125
126 default:
127 {
128 return QVariant();
129 }
130 }
131 }
132
133 return QVariant();
134 }
135
index(int row,int column,const QModelIndex & parent) const136 QModelIndex SearchResultModel::index(int row, int column, const QModelIndex& parent) const
137 {
138 if (parent.isValid())
139 {
140 // there are no child items, only top level items
141
142 return QModelIndex();
143 }
144
145 if ((column < 0) || (column >= 1) || (row < 0) || (row >= d->searchResults.count()))
146 {
147 return QModelIndex();
148 }
149
150 return createIndex(row, column, (void*)nullptr);
151 }
152
parent(const QModelIndex & index) const153 QModelIndex SearchResultModel::parent(const QModelIndex& index) const
154 {
155 Q_UNUSED(index)
156
157 // we have only top level items
158
159 return QModelIndex();
160 }
161
rowCount(const QModelIndex & parent) const162 int SearchResultModel::rowCount(const QModelIndex& parent) const
163 {
164 if (parent.isValid())
165 {
166 return 0;
167 }
168
169 return d->searchResults.count();
170 }
171
setHeaderData(int section,Qt::Orientation orientation,const QVariant & value,int role)172 bool SearchResultModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role)
173 {
174 Q_UNUSED(section)
175 Q_UNUSED(orientation)
176 Q_UNUSED(value)
177 Q_UNUSED(role)
178
179 return false;
180 }
181
headerData(int section,Qt::Orientation orientation,int role) const182 QVariant SearchResultModel::headerData(int section, Qt::Orientation orientation, int role) const
183 {
184 Q_UNUSED(role)
185
186 if ((section >= 1) || (orientation != Qt::Horizontal))
187 {
188 return false;
189 }
190
191 return QVariant(QLatin1String("Name"));
192 }
193
flags(const QModelIndex & index) const194 Qt::ItemFlags SearchResultModel::flags(const QModelIndex& index) const
195 {
196 return QAbstractItemModel::flags(index);
197 }
198
addResults(const SearchBackend::SearchResult::List & results)199 void SearchResultModel::addResults(const SearchBackend::SearchResult::List& results)
200 {
201 // first check which items are not duplicates
202
203 QList<int> nonDuplicates;
204
205 for (int i = 0 ; i < results.count() ; ++i)
206 {
207 const SearchBackend::SearchResult& currentResult = results.at(i);
208 bool isDuplicate = false;
209
210 for (int j = 0 ; j < d->searchResults.count() ; ++j)
211 {
212 if (currentResult.internalId == d->searchResults.at(j).result.internalId)
213 {
214 isDuplicate = true;
215 break;
216 }
217 }
218
219 if (!isDuplicate)
220 {
221 nonDuplicates << i;
222 }
223 }
224
225 if (nonDuplicates.isEmpty())
226 {
227 return;
228 }
229
230 beginInsertRows(QModelIndex(), d->searchResults.count(), d->searchResults.count()+nonDuplicates.count()-1);
231
232 for (int i = 0 ; i < nonDuplicates.count() ; ++i)
233 {
234 SearchResultItem item;
235 item.result = results.at(nonDuplicates.at(i));
236 d->searchResults << item;
237 }
238
239 endInsertRows();
240 }
241
resultItem(const QModelIndex & index) const242 SearchResultModel::SearchResultItem SearchResultModel::resultItem(const QModelIndex& index) const
243 {
244 if (!index.isValid())
245 {
246 return SearchResultItem();
247 }
248
249 return d->searchResults.at(index.row());
250 }
251
getMarkerIcon(const QModelIndex & index,QPoint * const offset,QSize * const size,QPixmap * const pixmap,QUrl * const url) const252 bool SearchResultModel::getMarkerIcon(const QModelIndex& index, QPoint* const offset, QSize* const size, QPixmap* const pixmap, QUrl* const url) const
253 {
254 // determine the id of the marker
255
256 const int markerNumber = index.row();
257 const bool itemIsSelected = d->selectionModel ? d->selectionModel->isSelected(index) : false;
258 QPixmap markerPixmap = itemIsSelected ? d->markerSelected : d->markerNormal;
259
260 // if the caller requests a URL and the marker will not get
261 // a special label, return a URL. Otherwise, return a pixmap.
262
263 const bool returnViaUrl = url && (markerNumber > 26);
264
265 if (returnViaUrl)
266 {
267 *url = itemIsSelected ? d->markerSelectedUrl : d->markerNormalUrl;
268
269 if (size)
270 {
271 *size = markerPixmap.size();
272 }
273 }
274 else
275 {
276 if (markerNumber <= 26)
277 {
278 const QString markerId = QChar('A' + markerNumber);
279 QPainter painter(&markerPixmap);
280 painter.setRenderHint(QPainter::Antialiasing);
281 painter.setPen(Qt::black);
282 QRect textRect(0, 2, markerPixmap.width(), markerPixmap.height());
283 painter.drawText(textRect, Qt::AlignHCenter, markerId);
284 }
285
286 *pixmap = markerPixmap;
287 }
288
289 if (offset)
290 {
291 *offset = QPoint(markerPixmap.width()/2, markerPixmap.height()-1);
292 }
293
294 return true;
295 }
296
setSelectionModel(QItemSelectionModel * const selectionModel)297 void SearchResultModel::setSelectionModel(QItemSelectionModel* const selectionModel)
298 {
299 d->selectionModel = selectionModel;
300 }
301
clearResults()302 void SearchResultModel::clearResults()
303 {
304 beginResetModel();
305 d->searchResults.clear();
306 endResetModel();
307 }
308
removeRowsByIndexes(const QModelIndexList & rowsList)309 void SearchResultModel::removeRowsByIndexes(const QModelIndexList& rowsList)
310 {
311 // extract the row numbers first:
312
313 QList<int> rowNumbers;
314
315 foreach (const QModelIndex& index, rowsList)
316 {
317 if (index.isValid())
318 {
319 rowNumbers << index.row();
320 }
321 }
322
323 if (rowNumbers.isEmpty())
324 {
325 return;
326 }
327
328 std::sort(rowNumbers.begin(), rowNumbers.end());
329
330 // now delete the rows, starting with the last row:
331
332 for (int i = rowNumbers.count()-1 ; i >= 0 ; --i)
333 {
334 const int rowNumber = rowNumbers.at(i);
335
336 /// @todo This is very slow for several indexes, because the views update after every removal
337
338 beginRemoveRows(QModelIndex(), rowNumber, rowNumber);
339 d->searchResults.removeAt(rowNumber);
340 endRemoveRows();
341 }
342 }
343
removeRowsBySelection(const QItemSelection & selectionList)344 void SearchResultModel::removeRowsBySelection(const QItemSelection& selectionList)
345 {
346 // extract the row numbers first:
347
348 QList<QPair<int, int> > rowRanges;
349
350 foreach (const QItemSelectionRange& range, selectionList)
351 {
352 rowRanges << QPair<int, int>(range.top(), range.bottom());
353 }
354
355 // we expect the ranges to be sorted here
356
357 std::sort(rowRanges.begin(), rowRanges.end(), RowRangeLessThan);
358
359 // now delete the rows, starting with the last row:
360
361 for (int i = rowRanges.count()-1 ; i >= 0 ; --i)
362 {
363 const QPair<int, int> currentRange = rowRanges.at(i);
364
365 /// @todo This is very slow for several indexes, because the views update after every removal
366
367 beginRemoveRows(QModelIndex(), currentRange.first, currentRange.second);
368
369 for (int j = currentRange.second ; j >= currentRange.first ; --j)
370 {
371 d->searchResults.removeAt(j);
372 }
373
374 endRemoveRows();
375 }
376 }
377
378 } // namespace DigikamGenericGeolocationEditPlugin
379