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