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"