File indexing completed on 2025-01-19 03:50:46

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2010-07-15
0007  * Description : central Map view
0008  *
0009  * SPDX-FileCopyrightText: 2010         by Gabriel Voicu <ping dot gabi at gmail dot com>
0010  * SPDX-FileCopyrightText: 2010         by Michael G. Hansen <mike at mghansen dot de>
0011  * SPDX-FileCopyrightText: 2014-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "mapwidgetview.h"
0018 
0019 // Qt includes
0020 
0021 #include <QVBoxLayout>
0022 #include <QApplication>
0023 #include <QItemSelectionModel>
0024 #include <QAbstractItemModel>
0025 #include <QPersistentModelIndex>
0026 #include <QScopedPointer>
0027 #include <QTimer>
0028 #include <QDir>
0029 
0030 // KDE includes
0031 
0032 #include <kconfiggroup.h>
0033 
0034 // Local includes
0035 
0036 #include "mapwidget.h"
0037 #include "trackmanager.h"
0038 #include "itemmarkertiler.h"
0039 #include "digikam_debug.h"
0040 #include "camerathumbsctrl.h"
0041 #include "itemposition.h"
0042 #include "iteminfo.h"
0043 #include "itemmodel.h"
0044 #include "importfiltermodel.h"
0045 #include "importimagemodel.h"
0046 #include "coredbwatch.h"
0047 #include "coredbfields.h"
0048 #include "itempropertiessidebardb.h"
0049 #include "gpsiteminfosorter.h"
0050 #include "importui.h"
0051 
0052 namespace Digikam
0053 {
0054 
0055 class ItemAlbumModel;
0056 class ItemFilterModel;
0057 
0058 /**
0059  * @class MapWidgetView
0060  *
0061  * @brief Class containing digiKam's central map view.
0062  */
0063 class Q_DECL_HIDDEN MapWidgetView::Private
0064 {
0065 public:
0066 
0067     explicit Private()
0068        : mapWidget                  (nullptr),
0069          imageModel                 (nullptr),
0070          imageFilterModel           (nullptr),
0071          importModel                (nullptr),
0072          importFilterModel          (nullptr),
0073          selectionModel             (nullptr),
0074          mapViewModelHelper         (nullptr),
0075          gpsItemInfoSorter          (nullptr),
0076          trackManager               (nullptr),
0077          modelTimer                 (nullptr),
0078          application                (MapWidgetView::ApplicationDigikam),
0079          boundariesShouldBeAdjusted (false)
0080     {
0081     }
0082 
0083     MapWidget*                 mapWidget;
0084 
0085     ItemAlbumModel*            imageModel;
0086     ItemFilterModel*           imageFilterModel;
0087     ImportItemModel*           importModel;
0088     ImportFilterModel*         importFilterModel;
0089     QItemSelectionModel*       selectionModel;
0090 
0091     MapViewModelHelper*        mapViewModelHelper;
0092     GPSItemInfoSorter*         gpsItemInfoSorter;
0093     TrackManager*              trackManager;
0094 
0095     QTimer*                    modelTimer;
0096 
0097     MapWidgetView::Application application;
0098 
0099     bool                       boundariesShouldBeAdjusted;
0100 };
0101 
0102 /**
0103  * @brief Constructor
0104  * @param selectionModel digiKam's selection model
0105  * @param imageFilterModel digikam's filter model
0106  * @param parent the parent object
0107  */
0108 MapWidgetView::MapWidgetView(QItemSelectionModel* const selectionModel,
0109                              DCategorizedSortFilterProxyModel* const imageFilterModel,
0110                              QWidget* const parent,
0111                              const MapWidgetView::Application application)
0112     : QWidget          (parent),
0113       StateSavingObject(this),
0114       d                (new Private())
0115 {
0116     d->application    = application;
0117     d->selectionModel = selectionModel;
0118 
0119     d->modelTimer     = new QTimer(this);
0120     d->modelTimer->setSingleShot(true);
0121     d->modelTimer->setInterval(50);
0122 
0123     switch (d->application)
0124     {
0125         case ApplicationDigikam:
0126         {
0127             d->imageFilterModel   = dynamic_cast<ItemFilterModel*>(imageFilterModel);
0128             d->imageModel         = dynamic_cast<ItemAlbumModel*>(imageFilterModel->sourceModel());
0129             d->mapViewModelHelper = new MapViewModelHelper(d->selectionModel,
0130                                                            d->imageFilterModel,
0131                                                            this, ApplicationDigikam);
0132 
0133             connect(d->imageModel, SIGNAL(allRefreshingFinished()),
0134                     d->modelTimer, SLOT(start()));
0135             break;
0136         }
0137 
0138         case ApplicationImportUI:
0139         {
0140             d->importFilterModel  = dynamic_cast<ImportFilterModel*>(imageFilterModel);
0141             d->importModel        = dynamic_cast<ImportItemModel*>(imageFilterModel->sourceModel());
0142             d->mapViewModelHelper = new MapViewModelHelper(d->selectionModel,
0143                                                            d->importFilterModel,
0144                                                            this, ApplicationImportUI);
0145 
0146             connect(d->importModel, SIGNAL(allRefreshingFinished()),
0147                     d->modelTimer, SLOT(start()));
0148             break;
0149         }
0150     }
0151 
0152     connect(d->modelTimer, SIGNAL(timeout()),
0153             this, SLOT(slotModelChanged()));
0154 
0155     QVBoxLayout* const vBoxLayout              = new QVBoxLayout(this);
0156     d->mapWidget                               = new MapWidget(this);
0157     d->mapWidget->setAvailableMouseModes(MouseModePan | MouseModeZoomIntoGroup | MouseModeSelectThumbnail);
0158     d->mapWidget->setVisibleMouseModes(MouseModePan | MouseModeZoomIntoGroup | MouseModeSelectThumbnail);
0159 
0160     if (d->application == ApplicationDigikam)
0161     {
0162         d->mapWidget->setVisibleExtraActions(ExtraLoadTracks);
0163 
0164         connect(d->mapWidget, &MapWidget::signalLoadTracksFromAlbums,
0165                 this, &MapWidgetView::slotLoadTracksFromAlbums);
0166     }
0167 
0168     ItemMarkerTiler* const geoifaceMarkerModel = new ItemMarkerTiler(d->mapViewModelHelper, this);
0169     d->mapWidget->setGroupedModel(geoifaceMarkerModel);
0170     d->mapWidget->setBackend(QLatin1String("marble"));
0171 
0172     d->gpsItemInfoSorter                       = new GPSItemInfoSorter(this);
0173     d->gpsItemInfoSorter->addToMapWidget(d->mapWidget);
0174 
0175     d->trackManager                            = new TrackManager(this);
0176     d->mapWidget->setTrackManager(d->trackManager);
0177 
0178     connect(d->trackManager, &TrackManager::signalAllTrackFilesReady,
0179             this, &MapWidgetView::slotAllTrackFilesReady);
0180 
0181     vBoxLayout->addWidget(d->mapWidget);
0182     vBoxLayout->addWidget(d->mapWidget->getControlWidget());
0183     vBoxLayout->setContentsMargins(QMargins());
0184 }
0185 
0186 /**
0187  * @brief Destructor
0188  */
0189 MapWidgetView::~MapWidgetView()
0190 {
0191 }
0192 
0193 void MapWidgetView::doLoadState()
0194 {
0195     KConfigGroup group = getConfigGroup();
0196 
0197     d->gpsItemInfoSorter->setSortOptions(GPSItemInfoSorter::SortOptions(group.readEntry(QLatin1String("Sort Order"),
0198                                                                                         int(d->gpsItemInfoSorter->getSortOptions()))));
0199 
0200     const KConfigGroup groupCentralMap = KConfigGroup(&group, QLatin1String("Central Map Widget"));
0201     d->mapWidget->readSettingsFromGroup(&groupCentralMap);
0202 }
0203 
0204 void MapWidgetView::doSaveState()
0205 {
0206     KConfigGroup group = getConfigGroup();
0207 
0208     group.writeEntry(QLatin1String("Sort Order"), int(d->gpsItemInfoSorter->getSortOptions()));
0209 
0210     KConfigGroup groupCentralMap = KConfigGroup(&group, QLatin1String("Central Map Widget"));
0211     d->mapWidget->saveSettingsToGroup(&groupCentralMap);
0212 
0213     group.sync();
0214 }
0215 
0216 /**
0217  * @brief Set the map active/inactive.
0218  * @param state If true, the map is active.
0219  */
0220 void MapWidgetView::setActive(const bool state)
0221 {
0222     d->mapWidget->setActive(state);
0223 
0224     if (state && d->boundariesShouldBeAdjusted)
0225     {
0226         d->modelTimer->start();
0227     }
0228 }
0229 
0230 /**
0231  * @return The map's active state
0232  */
0233 bool MapWidgetView::getActiveState() const
0234 {
0235     return d->mapWidget->getActiveState();
0236 }
0237 
0238 /**
0239  * @brief Returns the ItemInfo for the current image
0240  */
0241 ItemInfo MapWidgetView::currentItemInfo() const
0242 {
0243     /// @todo Have geoifacewidget honor the 'current index'
0244 
0245     QModelIndex currentIndex = d->selectionModel->currentIndex();
0246 
0247     if (!currentIndex.isValid())
0248     {
0249         /// @todo This is temporary until geoifacewidget marks a 'current index'
0250 
0251         if (!d->selectionModel->hasSelection())
0252         {
0253             return ItemInfo();
0254         }
0255 
0256         currentIndex = d->selectionModel->selectedIndexes().first();
0257     }
0258 
0259     return d->imageFilterModel->imageInfo(currentIndex);
0260 }
0261 
0262 /**
0263  * @brief Returns the CamItemInfo for the current image
0264  */
0265 CamItemInfo MapWidgetView::currentCamItemInfo() const
0266 {
0267     /// @todo Have geoifacewidget honor the 'current index'
0268 
0269     QModelIndex currentIndex = d->selectionModel->currentIndex();
0270 
0271     if (!currentIndex.isValid())
0272     {
0273         /// @todo This is temporary until geoifacewidget marks a 'current index'
0274 
0275         if (!d->selectionModel->hasSelection())
0276         {
0277             return CamItemInfo();
0278         }
0279 
0280         currentIndex = d->selectionModel->selectedIndexes().first();
0281     }
0282 
0283     return d->importFilterModel->camItemInfo(currentIndex);
0284 }
0285 
0286 void MapWidgetView::slotModelChanged()
0287 {
0288     if (!d->mapWidget->getActiveState())
0289     {
0290         d->boundariesShouldBeAdjusted = true;
0291         return;
0292     }
0293 
0294     d->trackManager->setVisibility(false);
0295     d->trackManager->clear();
0296 
0297     bool hasCoordinates = false;
0298 
0299     switch (d->application)
0300     {
0301         case ApplicationDigikam:
0302         {
0303             Q_FOREACH (const ItemInfo& info, d->imageModel->imageInfos())
0304             {
0305                 if (info.hasCoordinates())
0306                 {
0307                     hasCoordinates = true;
0308                     break;
0309                 }
0310             }
0311 
0312             break;
0313         }
0314 
0315         case ApplicationImportUI:
0316         {
0317             Q_FOREACH (const CamItemInfo& info, d->importModel->camItemInfos())
0318             {
0319                 QScopedPointer<DMetadata> meta(new DMetadata(info.url().toLocalFile()));
0320                 double lat, lng;
0321 
0322                 if (meta->getGPSLatitudeNumber(&lat) &&
0323                     meta->getGPSLongitudeNumber(&lng))
0324                 {
0325                     hasCoordinates = true;
0326                     break;
0327                 }
0328             }
0329 
0330             break;
0331         }
0332     }
0333 
0334     if (!hasCoordinates)
0335     {
0336         setEnabled(false);
0337         d->mapWidget->setCenter(GeoCoordinates(52.0, 6.0));
0338         d->mapWidget->setZoom(QLatin1String("marble:1108"));
0339     }
0340     else
0341     {
0342         setEnabled(true);
0343         d->mapWidget->adjustBoundariesToGroupedMarkers();
0344     }
0345 
0346     d->boundariesShouldBeAdjusted = false;
0347 }
0348 
0349 void MapWidgetView::slotAllTrackFilesReady()
0350 {
0351     if (d->trackManager->getTrackList().isEmpty())
0352     {
0353         return;
0354     }
0355 
0356     const TrackManager::Track track = d->trackManager->getTrackList().constFirst();
0357 
0358     if ((track.id != 0) && (track.points.size() > 0))
0359     {
0360         d->mapWidget->setCenter(track.points.at(0).coordinates);
0361         d->mapWidget->setZoom(QLatin1String("marble:2220"));
0362     }
0363 }
0364 
0365 void MapWidgetView::slotLoadTracksFromAlbums()
0366 {
0367     QApplication::setOverrideCursor(Qt::WaitCursor);
0368 
0369     d->trackManager->setVisibility(false);
0370     d->trackManager->clear();
0371 
0372     QList<QUrl> fileUrls;
0373 
0374     Q_FOREACH (const ItemInfo& info, d->imageModel->imageInfos())
0375     {
0376         if (info.hasCoordinates())
0377         {
0378             QUrl url = info.fileUrl().adjusted(QUrl::RemoveFilename |
0379                                                QUrl::StripTrailingSlash);
0380 
0381             if (!fileUrls.contains(url))
0382             {
0383                 fileUrls << url;
0384             }
0385         }
0386     }
0387 
0388     if (fileUrls.isEmpty())
0389     {
0390         QApplication::restoreOverrideCursor();
0391 
0392         return;
0393     }
0394 
0395     QList<QUrl> gpxList;
0396     const QStringList filter({QLatin1String("*.gpx")});
0397 
0398     Q_FOREACH (const QUrl& url, fileUrls)
0399     {
0400         QDir dir(url.toLocalFile());
0401 
0402         Q_FOREACH (const QFileInfo& finfo, dir.entryInfoList(filter, QDir::Files))
0403         {
0404             gpxList << QUrl::fromLocalFile(finfo.filePath());
0405         }
0406     }
0407 
0408     QApplication::restoreOverrideCursor();
0409 
0410     if (!gpxList.isEmpty())
0411     {
0412         d->trackManager->loadTrackFiles(gpxList);
0413         d->trackManager->setVisibility(true);
0414     }
0415 }
0416 
0417 //-------------------------------------------------------------------------------------------------------------
0418 
0419 class Q_DECL_HIDDEN MapViewModelHelper::Private
0420 {
0421 public:
0422 
0423     explicit Private()
0424         : model              (nullptr),
0425           importModel        (nullptr),
0426           selectionModel     (nullptr),
0427           thumbnailLoadThread(nullptr),
0428           application        (MapWidgetView::ApplicationDigikam)
0429     {
0430     }
0431 
0432     ItemFilterModel*            model;
0433     ImportFilterModel*          importModel;
0434     QItemSelectionModel*        selectionModel;
0435     ThumbnailLoadThread*        thumbnailLoadThread;
0436     MapWidgetView::Application  application;
0437 };
0438 
0439 MapViewModelHelper::MapViewModelHelper(QItemSelectionModel* const selection,
0440                                        DCategorizedSortFilterProxyModel* const filterModel,
0441                                        QObject* const parent,
0442                                        const MapWidgetView::Application application)
0443     : GeoModelHelper(parent),
0444       d(new Private())
0445 {
0446     d->selectionModel = selection;
0447     d->application    = application;
0448 
0449     switch (d->application)
0450     {
0451         case MapWidgetView::ApplicationDigikam:
0452         {
0453             d->model               = dynamic_cast<ItemFilterModel*>(filterModel);
0454             d->thumbnailLoadThread = new ThumbnailLoadThread(this);
0455 
0456             connect(d->thumbnailLoadThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)),
0457                     this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap)));
0458 
0459             // Note: Here we only monitor changes to the database, because changes to the model
0460             //       are also sent when thumbnails are generated, and we don't want to update
0461             //       the marker tiler for that!
0462 
0463             connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)),
0464                     this, SLOT(slotImageChange(ImageChangeset)), Qt::QueuedConnection);
0465 
0466             break;
0467         }
0468 
0469         case MapWidgetView::ApplicationImportUI:
0470         {
0471             d->importModel = dynamic_cast<ImportFilterModel*>(filterModel);
0472 
0473             connect(ImportUI::instance()->getCameraThumbsCtrl(), SIGNAL(signalThumbInfoReady(CamItemInfo)),
0474                     this, SLOT(slotThumbnailLoaded(CamItemInfo)));
0475 
0476             break;
0477         }
0478     }
0479 }
0480 
0481 /**
0482  * @brief Destructor
0483  */
0484 MapViewModelHelper::~MapViewModelHelper()
0485 {
0486 }
0487 
0488 /**
0489  * @return Returns digiKam's filter model.
0490  */
0491 QAbstractItemModel* MapViewModelHelper::model() const
0492 {
0493     switch (d->application)
0494     {
0495         case MapWidgetView::ApplicationDigikam:
0496         {
0497             return d->model;
0498         }
0499 
0500         case MapWidgetView::ApplicationImportUI:
0501         {
0502             return d->importModel;
0503         }
0504     }
0505 
0506     return nullptr;
0507 }
0508 
0509 /**
0510  * @return Returns digiKam's selection model.
0511  */
0512 QItemSelectionModel* MapViewModelHelper::selectionModel() const
0513 {
0514     return d->selectionModel;
0515 }
0516 
0517 /**
0518  * @brief Gets the coordinates of a marker found at current model index.
0519  * @param index Current model index.
0520  * @param coordinates Here will be returned the coordinates of the current marker.
0521  * @return True, if the marker has coordinates.
0522  */
0523 bool MapViewModelHelper::itemCoordinates(const QModelIndex& index, GeoCoordinates* const coordinates) const
0524 {
0525     switch (d->application)
0526     {
0527         case MapWidgetView::ApplicationDigikam:
0528         {
0529             const ItemInfo info = d->model->imageInfo(index);
0530 
0531             if (info.isNull() || !info.hasCoordinates())
0532             {
0533                 return false;
0534             }
0535 
0536             *coordinates = GeoCoordinates(info.latitudeNumber(), info.longitudeNumber());
0537             break;
0538         }
0539 
0540         case MapWidgetView::ApplicationImportUI:
0541         {
0542             const CamItemInfo info = d->importModel->camItemInfo(index);
0543 
0544             if (info.isNull())
0545             {
0546                 return false;
0547             }
0548 
0549             QScopedPointer<DMetadata> meta(new DMetadata(info.url().toLocalFile()));
0550             double     lat, lng;
0551             const bool haveCoordinates = meta->getGPSLatitudeNumber(&lat) && meta->getGPSLongitudeNumber(&lng);
0552 
0553             if (!haveCoordinates)
0554             {
0555                 return false;
0556             }
0557 
0558             GeoCoordinates tmpCoordinates(lat, lng);
0559 
0560             double     alt;
0561             const bool haveAlt = meta->getGPSAltitude(&alt);
0562 
0563             if (haveAlt)
0564             {
0565                 tmpCoordinates.setAlt(alt);
0566             }
0567 
0568             *coordinates = tmpCoordinates;
0569             break;
0570         }
0571     }
0572 
0573     return true;
0574 }
0575 
0576 /**
0577  * @brief This function retrieves the thumbnail for an index.
0578  * @param index The marker's index.
0579  * @param size The size of the thumbnail.
0580  * @return If the thumbnail has been loaded in the ThumbnailLoadThread instance, it is returned.
0581  * If not, a QPixmap is returned and ThumbnailLoadThread's signal named signalThumbnailLoaded is emitted when the thumbnail becomes available.
0582  */
0583 QPixmap MapViewModelHelper::pixmapFromRepresentativeIndex(const QPersistentModelIndex& index, const QSize& size)
0584 {
0585     if (index == QPersistentModelIndex())
0586     {
0587         return QPixmap();
0588     }
0589 
0590     switch (d->application)
0591     {
0592         case MapWidgetView::ApplicationDigikam:
0593         {
0594             const ItemInfo info = d->model->imageInfo(index);
0595 
0596             if (!info.isNull())
0597             {
0598                 QPixmap thumbnail;
0599 
0600                 if (d->thumbnailLoadThread->find(info.thumbnailIdentifier(), thumbnail, qMax(size.width()+2, size.height()+2)))
0601                 {
0602                     return thumbnail.copy(1, 1, thumbnail.size().width()-2, thumbnail.size().height()-2);
0603                 }
0604                 else
0605                 {
0606                     return QPixmap();
0607                 }
0608             }
0609 
0610             break;
0611         }
0612 
0613         case MapWidgetView::ApplicationImportUI:
0614         {
0615             QPixmap thumbnail = index.data(ImportItemModel::ThumbnailRole).value<QPixmap>();
0616 
0617             return thumbnail.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0618         }
0619     }
0620 
0621     return QPixmap();
0622 }
0623 
0624 /**
0625  * @brief This function finds the best representative marker from a group of markers. This is needed to display a thumbnail for a marker group.
0626  * @param indices A list containing markers.
0627  * @param sortKey Determines the sorting options and is actually of type GPSItemInfoSorter::SortOptions
0628  * @return Returns the index of the marker.
0629  */
0630 QPersistentModelIndex MapViewModelHelper::bestRepresentativeIndexFromList(const QList<QPersistentModelIndex>& list,
0631                                                                           const int sortKey)
0632 {
0633     if (list.isEmpty())
0634     {
0635         return QPersistentModelIndex();
0636     }
0637 
0638     // first convert from QPersistentModelIndex to QModelIndex
0639 
0640     QList<QModelIndex> indexList;
0641     QModelIndex        bestIndex;
0642 
0643     for (int i = 0 ; i < list.count() ; ++i)
0644     {
0645         const QModelIndex newIndex(list.at(i));
0646         indexList.append(newIndex);
0647     }
0648 
0649     switch (d->application)
0650     {
0651         case MapWidgetView::ApplicationDigikam:
0652         {
0653             // now get the ItemInfos and convert them to GPSItemInfos
0654 
0655             const QList<ItemInfo> imageInfoList = d->model->imageInfos(indexList);
0656             GPSItemInfo::List gpsItemInfoList;
0657 
0658             Q_FOREACH (const ItemInfo& imageInfo, imageInfoList)
0659             {
0660                 GPSItemInfo gpsItemInfo;
0661 
0662                 if (ItemPropertiesSideBarDB::GPSItemInfofromItemInfo(imageInfo, &gpsItemInfo))
0663                 {
0664                     gpsItemInfoList << gpsItemInfo;
0665                 }
0666             }
0667 
0668             if (gpsItemInfoList.size() != indexList.size())
0669             {
0670                 // this is a problem, and unexpected
0671 
0672                 return indexList.first();
0673             }
0674 
0675             // now determine the best available index
0676 
0677             bestIndex                   = indexList.first();
0678             GPSItemInfo bestGPSItemInfo = gpsItemInfoList.first();
0679 
0680             for (int i = 1 ; i < gpsItemInfoList.count() ; ++i)
0681             {
0682                 const GPSItemInfo& currentInfo = gpsItemInfoList.at(i);
0683 
0684                 if (GPSItemInfoSorter::fitsBetter(bestGPSItemInfo, SelectedNone,
0685                                                   currentInfo, SelectedNone,
0686                                                   SelectedNone, GPSItemInfoSorter::SortOptions(sortKey)))
0687                 {
0688                     bestIndex       = indexList.at(i);
0689                     bestGPSItemInfo = currentInfo;
0690                 }
0691             }
0692 
0693             break;
0694         }
0695 
0696         case MapWidgetView::ApplicationImportUI:
0697         {
0698             // now get the CamItemInfo and convert them to GPSItemInfos
0699 
0700             const QList<CamItemInfo> imageInfoList =  d->importModel->camItemInfos(indexList);
0701             GPSItemInfo::List gpsItemInfoList;
0702 
0703             Q_FOREACH (const CamItemInfo& imageInfo, imageInfoList)
0704             {
0705                 QScopedPointer<DMetadata> meta(new DMetadata(imageInfo.url().toLocalFile()));
0706                 double          lat, lng;
0707                 const bool      hasCoordinates = meta->getGPSLatitudeNumber(&lat) && meta->getGPSLongitudeNumber(&lng);
0708 
0709                 if (!hasCoordinates)
0710                 {
0711                     continue;
0712                 }
0713 
0714                 GeoCoordinates coordinates(lat, lng);
0715 
0716                 double alt;
0717                 const bool haveAlt = meta->getGPSAltitude(&alt);
0718 
0719                 if (haveAlt)
0720                 {
0721                     coordinates.setAlt(alt);
0722                 }
0723 
0724                 GPSItemInfo gpsItemInfo;
0725                 gpsItemInfo.coordinates = coordinates;
0726                 gpsItemInfo.dateTime    = meta->getItemDateTime();
0727                 gpsItemInfo.rating      = meta->getItemRating();
0728                 gpsItemInfo.url         = imageInfo.url();
0729                 gpsItemInfoList << gpsItemInfo;
0730             }
0731 
0732             if (gpsItemInfoList.size() != indexList.size())
0733             {
0734                 // this is a problem, and unexpected
0735 
0736                 return indexList.first();
0737             }
0738 
0739             // now determine the best available index
0740 
0741             bestIndex                   = indexList.first();
0742             GPSItemInfo bestGPSItemInfo = gpsItemInfoList.first();
0743 
0744             for (int i = 1 ; i < gpsItemInfoList.count() ; ++i)
0745             {
0746                 const GPSItemInfo& currentInfo = gpsItemInfoList.at(i);
0747 
0748                 if (GPSItemInfoSorter::fitsBetter(bestGPSItemInfo, SelectedNone,
0749                                                   currentInfo, SelectedNone,
0750                                                   SelectedNone, GPSItemInfoSorter::SortOptions(sortKey)))
0751                 {
0752                     bestIndex       = indexList.at(i);
0753                     bestGPSItemInfo = currentInfo;
0754                 }
0755             }
0756 
0757             break;
0758         }
0759     }
0760 
0761     // and return the index
0762 
0763     return QPersistentModelIndex(bestIndex);
0764 }
0765 
0766 /**
0767  * @brief Because of a call to pixmapFromRepresentativeIndex, some thumbnails are not yet loaded at the time of requesting.
0768  *        When each thumbnail loads, this slot is called and emits a signal that announces the map that the thumbnail is available.
0769  *
0770  * This slot is used in the Digikam main application UI.
0771  */
0772 void MapViewModelHelper::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb)
0773 {
0774     if (thumb.isNull())
0775     {
0776         return;
0777     }
0778 
0779     const QModelIndex currentIndex = d->model->indexForPath(loadingDescription.filePath);
0780 
0781     if (currentIndex.isValid())
0782     {
0783         QPersistentModelIndex goodIndex(currentIndex);
0784         Q_EMIT signalThumbnailAvailableForIndex(goodIndex, thumb.copy(1, 1, thumb.size().width()-2, thumb.size().height()-2));
0785     }
0786 }
0787 
0788 /**
0789  * @brief Because of a call to pixmapFromRepresentativeIndex, some thumbnails are not yet loaded at the time of requesting.
0790  *        When each thumbnail loads, this slot is called and emits a signal that announces to the map that the thumbnail is available.
0791  *
0792  * This slot is used in the ImportUI interface.
0793  */
0794 void MapViewModelHelper::slotThumbnailLoaded(const CamItemInfo& info)
0795 {
0796     CachedItem item;
0797     ImportUI::instance()->getCameraThumbsCtrl()->getThumbInfo(info, item);
0798 
0799     QPixmap pix = item.second;
0800 
0801     if (pix.isNull())
0802     {
0803         return;
0804     }
0805 
0806     const QModelIndex currentIndex = d->importModel->indexForPath(info.folder + info.name);
0807 
0808     if (currentIndex.isValid())
0809     {
0810         QPersistentModelIndex goodIndex(currentIndex);
0811         Q_EMIT signalThumbnailAvailableForIndex(goodIndex, pix.copy(1, 1, pix.size().width()-2, pix.size().height()-2));
0812     }
0813 }
0814 
0815 /**
0816  * This functions is called when one clicks on a thumbnail.
0817  * @param clickedIndices A list containing the marker indices belonging the group whose thumbnail has been clicked.
0818  */
0819 void MapViewModelHelper::onIndicesClicked(const QList<QPersistentModelIndex>& clickedIndices)
0820 {
0821     /// @todo What do we do when an image is clicked?
0822 
0823 #if 0
0824 
0825     QList<QModelIndex> indexList;
0826 
0827     for (int i = 0 ; i < clickedIndices.count() ; ++i)
0828     {
0829         const QModelIndex newIndex(clickedIndices.at(i));
0830         indexList.append(newIndex);
0831     }
0832 
0833     const QList<ItemInfo> imageInfoList = d->model->imageInfos(indexList);
0834 
0835     QList<qlonglong> imagesIdList;
0836 
0837     for (int i = 0 ; i < imageInfoList.count() ; ++i)
0838     {
0839         imagesIdList.append(imageInfoList[i].id());
0840     }
0841 
0842     Q_EMIT signalFilteredImages(imagesIdList);
0843 
0844 #else
0845 
0846     Q_UNUSED(clickedIndices);
0847 
0848 #endif
0849 
0850 }
0851 
0852 void MapViewModelHelper::slotImageChange(const ImageChangeset& changeset)
0853 {
0854     const DatabaseFields::Set changes = changeset.changes();
0855 //    const DatabaseFields::ItemPositions imagePositionChanges = changes;
0856 
0857     /// @todo More detailed check
0858 
0859     if ((changes & DatabaseFields::LatitudeNumber)  ||
0860         (changes & DatabaseFields::LongitudeNumber) ||
0861         (changes & DatabaseFields::Altitude))
0862     {
0863         Q_FOREACH (const qlonglong& id, changeset.ids())
0864         {
0865             const QModelIndex index = d->model->indexForImageId(id);
0866 
0867             if (index.isValid())
0868             {
0869                 Q_EMIT signalModelChangedDrastically();
0870                 break;
0871             }
0872         }
0873     }
0874 }
0875 
0876 } // namespace Digikam
0877 
0878 #include "moc_mapwidgetview.cpp"