File indexing completed on 2025-01-05 03:58:36

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-12-01
0007  * Description : world map widget library - Map Managament
0008  *
0009  * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2009-2011 by Michael G. Hansen <mike at mghansen dot de>
0011  * SPDX-FileCopyrightText:      2014 by Justus Schwartz <justus at gmx dot li>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "mapwidget_p.h"
0018 
0019 namespace Digikam
0020 {
0021 
0022 QStringList MapWidget::availableBackends() const
0023 {
0024     QStringList result;
0025 
0026     Q_FOREACH (MapBackend* const backend, d->loadedBackends)
0027     {
0028         result.append(backend->backendName());
0029     }
0030 
0031     return result;
0032 }
0033 
0034 QList<MapBackend*> MapWidget::backends() const
0035 {
0036     return d->loadedBackends;
0037 }
0038 
0039 bool MapWidget::setBackend(const QString& backendName)
0040 {
0041     if (backendName == d->currentBackendName)
0042     {
0043         return true;
0044     }
0045 
0046     saveBackendToCache();
0047 
0048     // switch to the placeholder widget:
0049 
0050     setShowPlaceholderWidget(true);
0051     removeMapWidgetFromFrame();
0052 
0053     // disconnect signals from old backend:
0054 
0055     if (d->currentBackend)
0056     {
0057         d->currentBackend->setActive(false);
0058 
0059         disconnect(d->currentBackend, SIGNAL(signalBackendReadyChanged(QString)),
0060                    this, SLOT(slotBackendReadyChanged(QString)));
0061 
0062         disconnect(d->currentBackend, SIGNAL(signalZoomChanged(QString)),
0063                    this, SLOT(slotBackendZoomChanged(QString)));
0064 
0065         disconnect(d->currentBackend, SIGNAL(signalClustersMoved(QIntList,QPair<int,QModelIndex>)),
0066                    this, SLOT(slotClustersMoved(QIntList,QPair<int,QModelIndex>)));
0067 
0068         disconnect(d->currentBackend, SIGNAL(signalClustersClicked(QIntList)),
0069                    this, SLOT(slotClustersClicked(QIntList)));
0070 
0071         disconnect(this, SIGNAL(signalUngroupedModelChanged(int)),
0072                    d->currentBackend, SLOT(slotUngroupedModelChanged(int)));
0073 
0074         if (s->markerModel)
0075         {
0076             disconnect(s->markerModel, SIGNAL(signalThumbnailAvailableForIndex(QVariant,QPixmap)),
0077                        d->currentBackend, SLOT(slotThumbnailAvailableForIndex(QVariant,QPixmap)));
0078         }
0079 
0080         disconnect(d->currentBackend, SIGNAL(signalSelectionHasBeenMade(Digikam::GeoCoordinates::Pair)),
0081                    this, SLOT(slotNewSelectionFromMap(Digikam::GeoCoordinates::Pair)));
0082 
0083     }
0084 
0085     Q_FOREACH (MapBackend* const backend, d->loadedBackends)
0086     {
0087         if (backend->backendName() == backendName)
0088         {   // cppcheck-suppress useStlAlgorithm
0089             qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("setting backend %1").arg(backendName);
0090 
0091             d->currentBackend     = backend;
0092             d->currentBackendName = backendName;
0093 
0094             connect(d->currentBackend, &MapBackend::signalBackendReadyChanged,
0095                     this, &MapWidget::slotBackendReadyChanged);
0096 
0097             connect(d->currentBackend, &MapBackend::signalZoomChanged,
0098                     this, &MapWidget::slotBackendZoomChanged);
0099 
0100             connect(d->currentBackend, &MapBackend::signalClustersMoved,
0101                     this, &MapWidget::slotClustersMoved);
0102 
0103             connect(d->currentBackend, &MapBackend::signalClustersClicked,
0104                     this, &MapWidget::slotClustersClicked);
0105 
0106             /**
0107              * @todo This connection is queued because otherwise QAbstractItemModel::itemSelected does not
0108              *       reflect the true state. Maybe monitor another signal instead?
0109              */
0110             connect(this, SIGNAL(signalUngroupedModelChanged(int)),
0111                     d->currentBackend, SLOT(slotUngroupedModelChanged(int)), Qt::QueuedConnection);
0112 
0113             if (s->markerModel)
0114             {
0115                 connect(s->markerModel, SIGNAL(signalThumbnailAvailableForIndex(QVariant,QPixmap)),
0116                         d->currentBackend, SLOT(slotThumbnailAvailableForIndex(QVariant,QPixmap)));
0117             }
0118 
0119             connect(d->currentBackend, &MapBackend::signalSelectionHasBeenMade,
0120                     this, &MapWidget::slotNewSelectionFromMap);
0121 
0122             if (s->activeState)
0123             {
0124                 setMapWidgetInFrame(d->currentBackend->mapWidget());
0125 
0126                 // call this slot manually in case the backend was ready right away:
0127 
0128                 if (d->currentBackend->isReady())
0129                 {
0130                     slotBackendReadyChanged(d->currentBackendName);
0131                 }
0132                 else
0133                 {
0134                     rebuildConfigurationMenu();
0135                 }
0136             }
0137 
0138             d->currentBackend->setActive(s->activeState);
0139 
0140             return true;
0141         }
0142     }
0143 
0144     return false;
0145 }
0146 
0147 void MapWidget::applyCacheToBackend()
0148 {
0149     if ((!currentBackendReady()) || (!s->activeState))
0150     {
0151         return;
0152     }
0153 
0154     /// @todo Only do this if the zoom was changed!
0155 
0156     qCDebug(DIGIKAM_GEOIFACE_LOG) << d->cacheZoom;
0157 
0158     setZoom(d->cacheZoom);
0159     setCenter(d->cacheCenterCoordinate);
0160     d->currentBackend->mouseModeChanged();
0161     d->currentBackend->regionSelectionChanged();
0162 }
0163 
0164 void MapWidget::saveBackendToCache()
0165 {
0166     if (!currentBackendReady())
0167     {
0168         return;
0169     }
0170 
0171     d->cacheCenterCoordinate = getCenter();
0172     d->cacheZoom             = getZoom();
0173 }
0174 
0175 GeoCoordinates MapWidget::getCenter() const
0176 {
0177     if (!currentBackendReady())
0178     {
0179         return d->cacheCenterCoordinate;
0180     }
0181 
0182     return d->currentBackend->getCenter();
0183 }
0184 
0185 void MapWidget::setCenter(const GeoCoordinates& coordinate)
0186 {
0187     d->cacheCenterCoordinate = coordinate;
0188 
0189     if (!currentBackendReady())
0190     {
0191         return;
0192     }
0193 
0194     d->currentBackend->setCenter(coordinate);
0195 }
0196 
0197 void MapWidget::slotBackendReadyChanged(const QString& backendName)
0198 {
0199     qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("backend %1 is ready!").arg(backendName);
0200 
0201     if (backendName != d->currentBackendName)
0202     {
0203         return;
0204     }
0205 
0206     if (!currentBackendReady())
0207     {
0208         return;
0209     }
0210 
0211     applyCacheToBackend();
0212 
0213     setShowPlaceholderWidget(false);
0214 
0215     if (!d->thumbnailsHaveBeenLoaded)
0216     {
0217         d->thumbnailTimer      = new QTimer(this);
0218         d->thumbnailTimerCount = 0;
0219 
0220         connect(d->thumbnailTimer, &QTimer::timeout,
0221                 this, &MapWidget::stopThumbnailTimer);
0222 
0223         d->thumbnailTimer->start(2000);
0224     }
0225 
0226     updateMarkers();
0227     markClustersAsDirty();
0228     rebuildConfigurationMenu();
0229 
0230     if (dynamic_cast<BackendMarble*>(sender()) ||
0231         (dynamic_cast<QActionGroup*>(sender()) &&
0232         (backendName == QLatin1String("marble")))
0233        )
0234     {
0235         QTimer::singleShot(2000, this, SLOT(slotApplySettings()));
0236     }
0237 }
0238 
0239 void MapWidget::setZoom(const QString& newZoom)
0240 {
0241     d->cacheZoom = newZoom;
0242 
0243     if (currentBackendReady())
0244     {
0245         d->currentBackend->setZoom(d->cacheZoom);
0246     }
0247 }
0248 
0249 QString MapWidget::getZoom()
0250 {
0251     if (currentBackendReady())
0252     {
0253         d->cacheZoom = d->currentBackend->getZoom();
0254     }
0255 
0256     return d->cacheZoom;
0257 }
0258 
0259 void MapWidget::adjustBoundariesToGroupedMarkers(const bool useSaneZoomLevel)
0260 {
0261     if ((!s->activeState) || (!s->markerModel) || (!currentBackendReady()))
0262     {
0263         return;
0264     }
0265 
0266 #ifdef HAVE_GEOLOCATION
0267 
0268     Marble::GeoDataLineString tileString;
0269 
0270     /// @todo not sure that this is the best way to find the bounding box of all items
0271 
0272     for (AbstractMarkerTiler::NonEmptyIterator tileIterator(s->markerModel, TileIndex::MaxLevel);
0273          !tileIterator.atEnd();
0274          tileIterator.nextIndex())
0275     {
0276         const TileIndex tileIndex = tileIterator.currentIndex();
0277 
0278         for (int corner = 1 ; corner <= 4 ; ++corner)
0279         {
0280             const GeoCoordinates currentTileCoordinate = tileIndex.toCoordinates(TileIndex::CornerPosition(corner));
0281 
0282             const Marble::GeoDataCoordinates tileCoordinate(currentTileCoordinate.lon(),
0283                                                             currentTileCoordinate.lat(),
0284                                                             0,
0285                                                             Marble::GeoDataCoordinates::Degree);
0286 
0287             tileString.append(tileCoordinate);
0288         }
0289     }
0290 
0291     const Marble::GeoDataLatLonBox latLonBox = Marble::GeoDataLatLonBox::fromLineString(tileString);
0292 
0293     /// @todo use a sane zoom level
0294 
0295     d->currentBackend->centerOn(latLonBox, useSaneZoomLevel);
0296 
0297 #else
0298 
0299     Q_UNUSED(useSaneZoomLevel);
0300 
0301 #endif
0302 }
0303 
0304 void MapWidget::refreshMap()
0305 {
0306     slotRequestLazyReclustering();
0307 }
0308 
0309 void MapWidget::setRegionSelection(const GeoCoordinates::Pair& region)
0310 {
0311     s->selectionRectangle = region;
0312 
0313     d->currentBackend->regionSelectionChanged();
0314 
0315     slotUpdateActionsEnabled();
0316 }
0317 
0318 void MapWidget::clearRegionSelection()
0319 {
0320     s->selectionRectangle.first.clear();
0321 
0322     d->currentBackend->regionSelectionChanged();
0323 
0324     slotUpdateActionsEnabled();
0325 }
0326 
0327 void MapWidget::slotNewSelectionFromMap(const Digikam::GeoCoordinates::Pair& sel)
0328 {
0329     /// @todo Should the backend update s on its own?
0330 
0331     s->selectionRectangle = sel;
0332 
0333     slotUpdateActionsEnabled();
0334 
0335     Q_EMIT signalRegionSelectionChanged();
0336 }
0337 
0338 void MapWidget::slotRemoveCurrentRegionSelection()
0339 {
0340     clearRegionSelection();
0341     d->currentBackend->regionSelectionChanged();
0342 
0343     slotUpdateActionsEnabled();
0344 
0345     Q_EMIT signalRegionSelectionChanged();
0346 }
0347 
0348 GeoCoordinates::Pair MapWidget::getRegionSelection()
0349 {
0350     return s->selectionRectangle;
0351 }
0352 
0353 void MapWidget::updateMarkers()
0354 {
0355     if (!currentBackendReady())
0356     {
0357         return;
0358     }
0359 
0360     // tell the backend to update the markers
0361 
0362     d->currentBackend->updateMarkers();
0363 }
0364 
0365 void MapWidget::updateClusters()
0366 {
0367     /// @todo Find a better way to tell the TileGrouper about the backend
0368 
0369     s->tileGrouper->setCurrentBackend(d->currentBackend);
0370     s->tileGrouper->updateClusters();
0371 }
0372 
0373 void MapWidget::markClustersAsDirty()
0374 {
0375     s->tileGrouper->setClustersDirty();
0376 }
0377 
0378 
0379 QPixmap MapWidget::getDecoratedPixmapForCluster(const int clusterId,
0380                                                 const GeoGroupState* const selectedStateOverride,
0381                                                 const int* const countOverride,
0382                                                 QPoint* const centerPoint)
0383 {
0384     GeoIfaceCluster& cluster = s->clusterList[clusterId];
0385     int markerCount          = cluster.markerCount;
0386     GeoGroupState groupState = cluster.groupState;
0387 
0388     if (selectedStateOverride)
0389     {
0390         groupState  = *selectedStateOverride;
0391         markerCount = *countOverride;
0392     }
0393 
0394     const GeoGroupState selectedState = groupState & SelectedMask;
0395 
0396     // first determine all the color and style values
0397 
0398     QColor       fillColor;
0399     QColor       strokeColor;
0400     Qt::PenStyle strokeStyle;
0401     QColor       labelColor;
0402     QString      labelText;
0403     getColorInfos(clusterId, &fillColor, &strokeColor,
0404                   &strokeStyle, &labelText, &labelColor,
0405                   &selectedState,
0406                   &markerCount);
0407 
0408     // determine whether we should use a pixmap or a placeholder
0409 
0410     if (!s->showThumbnails)
0411     {
0412         /// @todo Handle positive filtering and region selection!
0413 
0414         QString pixmapName = fillColor.name().mid(1);
0415 
0416         if (selectedState == SelectedAll)
0417         {
0418             pixmapName += QLatin1String("-selected");
0419         }
0420 
0421         if (selectedState == SelectedSome)
0422         {
0423             pixmapName += QLatin1String("-someselected");
0424         }
0425 
0426         const QPixmap& markerPixmap = GeoIfaceGlobalObject::instance()->getMarkerPixmap(pixmapName);
0427 
0428         // update the display information stored in the cluster:
0429 
0430         cluster.pixmapType          = GeoIfaceCluster::PixmapMarker;
0431         cluster.pixmapOffset        = QPoint(markerPixmap.width()/2, markerPixmap.height()-1);
0432         cluster.pixmapSize          = markerPixmap.size();
0433 
0434         if (centerPoint)
0435         {
0436             *centerPoint = cluster.pixmapOffset;
0437         }
0438 
0439         return markerPixmap;
0440     }
0441 
0442     /// @todo This check is strange, there can be no clusters without a markerModel?
0443 
0444     bool displayThumbnail = (s->markerModel != nullptr);
0445 
0446     if (displayThumbnail)
0447     {
0448         if (markerCount == 1)
0449         {
0450             displayThumbnail = s->previewSingleItems;
0451         }
0452         else
0453         {
0454             displayThumbnail = s->previewGroupedItems;
0455         }
0456     }
0457 
0458     if (displayThumbnail)
0459     {
0460         const QVariant representativeMarker = getClusterRepresentativeMarker(clusterId, s->sortKey);
0461         const int undecoratedThumbnailSize  = getUndecoratedThumbnailSize();
0462         QPixmap clusterPixmap               = s->markerModel->pixmapFromRepresentativeIndex(representativeMarker,
0463                                                                                             QSize(undecoratedThumbnailSize,
0464                                                                                                   undecoratedThumbnailSize));
0465 
0466         if (!clusterPixmap.isNull())
0467         {
0468             QPixmap resultPixmap(clusterPixmap.size() + QSize(2, 2));
0469 
0470             // we may draw with partially transparent pixmaps later, so make sure we have a defined
0471             // background color
0472 
0473             resultPixmap.fill(QColor::fromRgb(0xff, 0xff, 0xff));
0474             QPainter painter(&resultPixmap);
0475 /*
0476              painter.setRenderHint(QPainter::Antialiasing);
0477 */
0478             const int borderWidth = (groupState&SelectedSome) ? 2 : 1;
0479             QPen borderPen;
0480             borderPen.setWidth(borderWidth);
0481             borderPen.setJoinStyle(Qt::MiterJoin);
0482 
0483             GeoGroupState globalState = s->markerModel->getGlobalGroupState();
0484 
0485             /// @todo What about partially in the region or positively filtered?
0486 
0487             const bool clusterIsNotInRegionSelection  = (globalState & RegionSelectedMask) &&
0488                                                         ((groupState & RegionSelectedMask) == RegionSelectedNone);
0489             const bool clusterIsNotPositivelyFiltered = (globalState & FilteredPositiveMask) &&
0490                                                         ((groupState & FilteredPositiveMask) == FilteredPositiveNone);
0491 
0492             const bool shouldGrayOut                  = clusterIsNotInRegionSelection || clusterIsNotPositivelyFiltered;
0493             const bool shouldCrossOut                 = clusterIsNotInRegionSelection;
0494 
0495             if (shouldGrayOut)
0496             {
0497                 /// @todo Cache the alphaPixmap!
0498 
0499                 QPixmap alphaPixmap(clusterPixmap.size());
0500                 alphaPixmap.fill(QColor::fromRgb(0x80, 0x80, 0x80));
0501 /*
0502                 NOTE : old Qt4 code ported to Qt5 due to deprecated QPixmap::setAlphaChannel()
0503                 clusterPixmap.setAlphaChannel(alphaPixmap);
0504 */
0505                 QPainter p(&clusterPixmap);
0506                 p.setOpacity(0.2);
0507                 p.drawPixmap(0, 0, alphaPixmap);
0508                 p.end();
0509             }
0510 
0511             painter.drawPixmap(QPoint(1, 1), clusterPixmap);
0512 
0513             if (shouldGrayOut || shouldCrossOut)
0514             {
0515                 // draw a red cross above the pixmap
0516 
0517                 QPen crossPen(Qt::red);
0518 
0519                 if (!shouldCrossOut)
0520                 {
0521                     /// @todo Maybe we should also do a cross for not positively filtered images?
0522 
0523                     crossPen.setColor(Qt::blue);
0524                 }
0525 
0526                 crossPen.setWidth(2);
0527                 painter.setPen(crossPen);
0528 
0529                 const int width  = resultPixmap.size().width();
0530                 const int height = resultPixmap.size().height();
0531                 painter.drawLine(0, 0, width-1, height-1);
0532                 painter.drawLine(width-1, 0, 0, height-1);
0533             }
0534 
0535             if (strokeStyle != Qt::SolidLine)
0536             {
0537                 // paint a white border around the image
0538 
0539                 borderPen.setColor(Qt::white);
0540                 painter.setPen(borderPen);
0541                 painter.drawRect(borderWidth-1, borderWidth-1,
0542                                  resultPixmap.size().width()-borderWidth,
0543                                  resultPixmap.size().height()-borderWidth);
0544             }
0545 
0546             // now draw the selection border
0547 
0548             borderPen.setColor(strokeColor);
0549             borderPen.setStyle(strokeStyle);
0550             painter.setPen(borderPen);
0551             painter.drawRect(borderWidth-1, borderWidth-1,
0552                              resultPixmap.size().width()-borderWidth,
0553                              resultPixmap.size().height()-borderWidth);
0554 
0555             if (s->showNumbersOnItems)
0556             {
0557                 QPen labelPen(labelColor);
0558 
0559                 // note: the pen has to be set, otherwise the bounding rect is 0 x 0!!!
0560 
0561                 painter.setPen(labelPen);
0562                 const QRect textRect(0, 0, resultPixmap.width(), resultPixmap.height());
0563                 QRect textBoundingRect = painter.boundingRect(textRect,
0564                                                               Qt::AlignHCenter | Qt::AlignVCenter,
0565                                                               labelText);
0566                 textBoundingRect.adjust(-1, -1, 1, 1);
0567 
0568                 // fill the bounding rect:
0569 
0570                 painter.setPen(Qt::NoPen);
0571                 painter.setBrush(QColor::fromRgb(0xff, 0xff, 0xff, 0x80));
0572                 painter.drawRect(textBoundingRect);
0573 
0574                 // draw the text:
0575 
0576                 painter.setPen(labelPen);
0577                 painter.setBrush(Qt::NoBrush);
0578                 painter.drawText(textRect, Qt::AlignHCenter|Qt::AlignVCenter, labelText);
0579             }
0580 
0581             // update the display information stored in the cluster:
0582 
0583             cluster.pixmapType   = GeoIfaceCluster::PixmapImage;
0584             cluster.pixmapOffset = QPoint(resultPixmap.width()/2, resultPixmap.height()/2);
0585             cluster.pixmapSize   = resultPixmap.size();
0586 
0587             if (centerPoint)
0588             {
0589                 *centerPoint = cluster.pixmapOffset;
0590             }
0591 
0592             return resultPixmap;
0593         }
0594     }
0595 
0596     // we do not have a thumbnail, draw the circle instead:
0597 
0598     const int circleRadius = s->thumbnailSize / 2;
0599     QPen circlePen;
0600     circlePen.setColor(strokeColor);
0601     circlePen.setStyle(strokeStyle);
0602     circlePen.setWidth(2);
0603     QBrush circleBrush(fillColor);
0604     QPen labelPen;
0605     labelPen.setColor(labelColor);
0606     const QRect circleRect(0, 0, 2*circleRadius, 2*circleRadius);
0607 
0608     const int pixmapDiameter = 2*(circleRadius+1);
0609     QPixmap circlePixmap(pixmapDiameter, pixmapDiameter);
0610 
0611     /// @todo cache this somehow
0612 
0613     circlePixmap.fill(QColor(0, 0, 0, 0));
0614 
0615     QPainter circlePainter(&circlePixmap);
0616     circlePainter.setPen(circlePen);
0617     circlePainter.setBrush(circleBrush);
0618     circlePainter.drawEllipse(circleRect);
0619 
0620     circlePainter.setPen(labelPen);
0621     circlePainter.setBrush(Qt::NoBrush);
0622     circlePainter.drawText(circleRect, Qt::AlignHCenter|Qt::AlignVCenter, labelText);
0623 
0624     // update the display information stored in the cluster:
0625 
0626     cluster.pixmapType   = GeoIfaceCluster::PixmapCircle;
0627     cluster.pixmapOffset = QPoint(circlePixmap.width() / 2, circlePixmap.height() / 2);
0628     cluster.pixmapSize   = circlePixmap.size();
0629 
0630     if (centerPoint)
0631     {
0632         *centerPoint = QPoint(circlePixmap.width() / 2, circlePixmap.height() / 2);
0633     }
0634 
0635     return circlePixmap;
0636 }
0637 
0638 QVariant MapWidget::getClusterRepresentativeMarker(const int clusterIndex, const int sortKey)
0639 {
0640     if (!s->markerModel)
0641     {
0642         return QVariant();
0643     }
0644 
0645     const GeoIfaceCluster cluster          = s->clusterList.at(clusterIndex);
0646     QMap<int, QVariant>::const_iterator it = cluster.representativeMarkers.find(sortKey);
0647 
0648     if (it != cluster.representativeMarkers.end())
0649     {
0650         return *it;
0651     }
0652 
0653     QList<QVariant> repIndices;
0654 
0655     for (int i = 0 ; i < cluster.tileIndicesList.count() ; ++i)
0656     {
0657         repIndices << s->markerModel->getTileRepresentativeMarker(cluster.tileIndicesList.at(i), sortKey);
0658     }
0659 
0660     const QVariant clusterRepresentative                        = s->markerModel->bestRepresentativeIndexFromList(repIndices, sortKey);
0661     s->clusterList[clusterIndex].representativeMarkers[sortKey] = clusterRepresentative;
0662 
0663     return clusterRepresentative;
0664 }
0665 
0666 bool MapWidget::currentBackendReady() const
0667 {
0668     if (!d->currentBackend)
0669     {
0670         return false;
0671     }
0672 
0673     return d->currentBackend->isReady();
0674 }
0675 
0676 void MapWidget::setShowPlaceholderWidget(const bool state)
0677 {
0678     if (state)
0679     {
0680         d->stackedLayout->setCurrentIndex(0);
0681     }
0682     else
0683     {
0684         if (d->stackedLayout->count() > 1)
0685         {
0686             d->stackedLayout->setCurrentIndex(1);
0687         }
0688     }
0689 }
0690 
0691 void MapWidget::setMapWidgetInFrame(QWidget* const widgetForFrame)
0692 {
0693     if (d->stackedLayout->count() > 1)
0694     {
0695         // widget 0 is the status widget, widget 1 is the map widget
0696 
0697         if (d->stackedLayout->widget(1) == widgetForFrame)
0698         {
0699             return;
0700         }
0701 
0702         // there is some other widget at the target position.
0703         // remove it and add our widget instead
0704 
0705         d->stackedLayout->removeWidget(d->stackedLayout->widget(1));
0706     }
0707 
0708     d->stackedLayout->addWidget(widgetForFrame);
0709 }
0710 
0711 void MapWidget::removeMapWidgetFromFrame()
0712 {
0713     if (d->stackedLayout->count() > 1)
0714     {
0715         d->stackedLayout->removeWidget(d->stackedLayout->widget(1));
0716     }
0717 
0718     d->stackedLayout->setCurrentIndex(0);
0719 }
0720 
0721 void MapWidget::slotClustersNeedUpdating()
0722 {
0723     if (currentBackendReady())
0724     {
0725         d->currentBackend->slotClustersNeedUpdating();
0726     }
0727 }
0728 
0729 void MapWidget::slotChangeBackend(QAction* action)
0730 {
0731     GEOIFACE_ASSERT(action != nullptr);
0732 
0733     if (!action)
0734     {
0735         return;
0736     }
0737 
0738     const QString newBackendName = action->data().toString();
0739     setBackend(newBackendName);
0740 }
0741 
0742 void MapWidget::slotBackendZoomChanged(const QString& newZoom)
0743 {
0744     d->cacheZoom = newZoom;
0745 }
0746 
0747 void MapWidget::slotClustersMoved(const QIntList& clusterIndices,
0748                                   const QPair<int, QModelIndex>& snapTarget)
0749 {
0750     qCDebug(DIGIKAM_GEOIFACE_LOG) << clusterIndices;
0751 
0752     /// @todo We actually expect only one clusterindex
0753 
0754     int             clusterIndex      = clusterIndices.first();
0755     GeoCoordinates  targetCoordinates = s->clusterList.at(clusterIndex).coordinates;
0756     TileIndex::List movedTileIndices;
0757 
0758     if (s->clusterList.at(clusterIndex).groupState == SelectedNone)
0759     {
0760         // a not-selected marker was moved. update all of its items:
0761 
0762         const GeoIfaceCluster& cluster = s->clusterList.at(clusterIndex);
0763 
0764         for (int i = 0 ; i < cluster.tileIndicesList.count() ; ++i)
0765         {
0766             const TileIndex tileIndex = cluster.tileIndicesList.at(i);
0767             movedTileIndices << tileIndex;
0768         }
0769     }
0770     else
0771     {
0772         // selected items were moved. The model helper should know which tiles are selected,
0773         // therefore we give him an empty list
0774     }
0775 
0776     s->markerModel->onIndicesMoved(movedTileIndices, targetCoordinates, snapTarget.second);
0777 
0778     /**
0779      * @todo Clusters are marked as dirty by slotClustersNeedUpdating
0780      * which is called while we update the model
0781      */
0782 }
0783 
0784 /**
0785  * @todo Clicking on several clusters at once is not actually possible
0786  */
0787 void MapWidget::slotClustersClicked(const QIntList& clusterIndices)
0788 {
0789     qCDebug(DIGIKAM_GEOIFACE_LOG) << clusterIndices;
0790 
0791     if ((s->currentMouseMode == MouseModeZoomIntoGroup) ||
0792         (s->currentMouseMode == MouseModeRegionSelectionFromIcon))
0793     {
0794 
0795 #ifdef HAVE_GEOLOCATION
0796 
0797         int maxTileLevel = 0;
0798 
0799         Marble::GeoDataLineString tileString;
0800 
0801         for (int i = 0 ; i < clusterIndices.count() ; ++i)
0802         {
0803             const int clusterIndex               = clusterIndices.at(i);
0804             const GeoIfaceCluster currentCluster = s->clusterList.at(clusterIndex);
0805 
0806             for (int j = 0 ; j < currentCluster.tileIndicesList.count() ; ++j)
0807             {
0808                 const TileIndex& currentTileIndex = currentCluster.tileIndicesList.at(j);
0809 
0810                 for (int corner = 1 ; corner <= 4 ; ++corner)
0811                 {
0812                     GeoCoordinates currentTileCoordinate;
0813 
0814                     if      (corner == 1)
0815                     {
0816                         currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerNW);
0817                     }
0818                     else if (corner == 2)
0819                     {
0820                         currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerSW);
0821                     }
0822                     else if (corner == 3)
0823                     {
0824                         currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerNE);
0825                     }
0826                     else if (corner == 4)
0827                     {
0828                         currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerSE);
0829                     }
0830 
0831                     const Marble::GeoDataCoordinates tileCoordinate(currentTileCoordinate.lon(),
0832                                                                     currentTileCoordinate.lat(),
0833                                                                     0,
0834                                                                     Marble::GeoDataCoordinates::Degree);
0835 
0836                     if (maxTileLevel < currentTileIndex.level())
0837                     {
0838                         maxTileLevel = currentTileIndex.level();
0839                     }
0840 
0841                     tileString.append(tileCoordinate);
0842                 }
0843             }
0844         }
0845 
0846         Marble::GeoDataLatLonBox latLonBox = Marble::GeoDataLatLonBox::fromLineString(tileString);
0847 
0848         /// @todo Review this section
0849 /*
0850         if (maxTileLevel != 0)
0851         {
0852             //increase the selection boundaries with 0.1 degrees because some thumbnails aren't caught by selection
0853 
0854             latLonBox.setWest((latLonBox.west(Marble::GeoDataCoordinates::Degree) - (0.1 / maxTileLevel)),   Marble::GeoDataCoordinates::Degree);
0855             latLonBox.setNorth((latLonBox.north(Marble::GeoDataCoordinates::Degree) + (0.1 / maxTileLevel)), Marble::GeoDataCoordinates::Degree);
0856             latLonBox.setEast((latLonBox.east(Marble::GeoDataCoordinates::Degree) + (0.1 / maxTileLevel)),   Marble::GeoDataCoordinates::Degree);
0857             latLonBox.setSouth((latLonBox.south(Marble::GeoDataCoordinates::Degree) - (0.1 / maxTileLevel)), Marble::GeoDataCoordinates::Degree);
0858         }
0859         else
0860         {
0861 */
0862             latLonBox.setWest((latLonBox.west(Marble::GeoDataCoordinates::Degree) - 0.0001),   Marble::GeoDataCoordinates::Degree);
0863             latLonBox.setNorth((latLonBox.north(Marble::GeoDataCoordinates::Degree) + 0.0001), Marble::GeoDataCoordinates::Degree);
0864             latLonBox.setEast((latLonBox.east(Marble::GeoDataCoordinates::Degree) + 0.0001),   Marble::GeoDataCoordinates::Degree);
0865             latLonBox.setSouth((latLonBox.south(Marble::GeoDataCoordinates::Degree) - 0.0001), Marble::GeoDataCoordinates::Degree);
0866 /*
0867         }
0868 */
0869 
0870         if (s->currentMouseMode == MouseModeZoomIntoGroup)
0871         {
0872             /// @todo Very small latLonBoxes can crash Marble
0873 
0874             d->currentBackend->centerOn(latLonBox);
0875         }
0876         else
0877         {
0878             const GeoCoordinates::Pair newSelection(
0879                     GeoCoordinates(latLonBox.north(Marble::GeoDataCoordinates::Degree),
0880                                 latLonBox.west(Marble::GeoDataCoordinates::Degree)),
0881                     GeoCoordinates(latLonBox.south(Marble::GeoDataCoordinates::Degree),
0882                                 latLonBox.east(Marble::GeoDataCoordinates::Degree))
0883                 );
0884 
0885             s->selectionRectangle = newSelection;
0886             d->currentBackend->regionSelectionChanged();
0887 
0888             Q_EMIT signalRegionSelectionChanged();
0889         }
0890 
0891 #endif
0892 
0893     }
0894     else if (((s->currentMouseMode == MouseModeFilter) && s->selectionRectangle.first.hasCoordinates()) ||
0895              (s->currentMouseMode == MouseModeSelectThumbnail))
0896     {
0897         // update the selection and filtering state of the clusters
0898 
0899         for (int i = 0 ; i < clusterIndices.count() ; ++i)
0900         {
0901             const int clusterIndex               = clusterIndices.at(i);
0902             const GeoIfaceCluster currentCluster = s->clusterList.at(clusterIndex);
0903             const TileIndex::List tileIndices    = currentCluster.tileIndicesList;
0904 
0905             /// @todo Isn't this cached in the cluster?
0906 
0907             const QVariant representativeIndex   = getClusterRepresentativeMarker(clusterIndex, s->sortKey);
0908 
0909             AbstractMarkerTiler::ClickInfo clickInfo;
0910             clickInfo.tileIndicesList            = tileIndices;
0911             clickInfo.representativeIndex        = representativeIndex;
0912             clickInfo.groupSelectionState        = currentCluster.groupState;
0913             clickInfo.currentMouseMode           = s->currentMouseMode;
0914             s->markerModel->onIndicesClicked(clickInfo);
0915         }
0916     }
0917 }
0918 
0919 void MapWidget::slotLazyReclusteringRequestCallBack()
0920 {
0921     if (!d->lazyReclusteringRequested)
0922     {
0923         return;
0924     }
0925 
0926     d->lazyReclusteringRequested = false;
0927     slotClustersNeedUpdating();
0928 }
0929 
0930 void MapWidget::slotRequestLazyReclustering()
0931 {
0932     if (d->lazyReclusteringRequested)
0933     {
0934         return;
0935     }
0936 
0937     s->tileGrouper->setClustersDirty();
0938 
0939     if (s->activeState)
0940     {
0941         d->lazyReclusteringRequested = true;
0942         QTimer::singleShot(0, this, SLOT(slotLazyReclusteringRequestCallBack()));
0943     }
0944 }
0945 
0946 } // namespace Digikam