File indexing completed on 2025-03-09 03:57:14
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2009-12-01 0007 * Description : Google-Maps-backend for geolocation interface 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 "backendgooglemaps.h" 0018 0019 // Qt includes 0020 0021 #include <QMenu> 0022 #include <QTimer> 0023 #include <QAction> 0024 #include <QBuffer> 0025 #include <QPointer> 0026 #include <QResizeEvent> 0027 #include <QActionGroup> 0028 #include <QApplication> 0029 #include <QStandardPaths> 0030 0031 // KDE includes 0032 0033 #include <kconfiggroup.h> 0034 #include <klocalizedstring.h> 0035 0036 // Marble includes 0037 0038 #ifdef HAVE_GEOLOCATION 0039 0040 # include "GeoDataLatLonAltBox.h" 0041 0042 #endif 0043 0044 // Local includes 0045 0046 #include "digikam_config.h" 0047 #include "digikam_debug.h" 0048 #include "mapwidget.h" 0049 #include "abstractmarkertiler.h" 0050 #include "geomodelhelper.h" 0051 0052 #ifdef HAVE_QWEBENGINE 0053 0054 # include "htmlwidget_qwebengine.h" 0055 0056 #else 0057 0058 # include "htmlwidget_qwebkit.h" 0059 0060 #endif 0061 0062 namespace Digikam 0063 { 0064 0065 class Q_DECL_HIDDEN GMInternalWidgetInfo 0066 { 0067 public: 0068 0069 GMInternalWidgetInfo() 0070 : htmlWidget(nullptr) 0071 { 0072 } 0073 0074 HTMLWidget* htmlWidget; 0075 }; 0076 0077 } // namespace Digikam 0078 0079 Q_DECLARE_METATYPE(Digikam::GMInternalWidgetInfo) 0080 0081 namespace Digikam 0082 { 0083 0084 class Q_DECL_HIDDEN BackendGoogleMaps::Private 0085 { 0086 public: 0087 0088 explicit Private() 0089 : htmlWidget (nullptr), 0090 htmlWidgetWrapper (nullptr), 0091 isReady (false), 0092 mapTypeActionGroup (nullptr), 0093 floatItemsActionGroup (nullptr), 0094 showMapTypeControlAction (nullptr), 0095 showNavigationControlAction (nullptr), 0096 showScaleControlAction (nullptr), 0097 htmlFileName (QLatin1String("backend-googlemaps.html")), 0098 cacheMapType (QLatin1String("ROADMAP")), 0099 cacheShowMapTypeControl (true), 0100 cacheShowNavigationControl (true), 0101 cacheShowScaleControl (true), 0102 cacheZoom (8), 0103 cacheMaxZoom (0), 0104 cacheMinZoom (0), 0105 cacheCenter (52.0, 6.0), 0106 cacheBounds (), 0107 activeState (false), 0108 widgetIsDocked (false), 0109 trackChangeTracker () 0110 { 0111 } 0112 0113 QPointer<HTMLWidget> htmlWidget; 0114 QPointer<QWidget> htmlWidgetWrapper; 0115 bool isReady; 0116 QActionGroup* mapTypeActionGroup; 0117 QActionGroup* floatItemsActionGroup; 0118 QAction* showMapTypeControlAction; 0119 QAction* showNavigationControlAction; 0120 QAction* showScaleControlAction; 0121 QString htmlFileName; 0122 QString cacheMapType; 0123 bool cacheShowMapTypeControl; 0124 bool cacheShowNavigationControl; 0125 bool cacheShowScaleControl; 0126 int cacheZoom; 0127 int cacheMaxZoom; 0128 int cacheMinZoom; 0129 GeoCoordinates cacheCenter; 0130 QPair<GeoCoordinates, GeoCoordinates> cacheBounds; 0131 bool activeState; 0132 bool widgetIsDocked; 0133 QList<TrackManager::TrackChanges> trackChangeTracker; 0134 }; 0135 0136 BackendGoogleMaps::BackendGoogleMaps(const QExplicitlySharedDataPointer<GeoIfaceSharedData>& sharedData, 0137 QObject* const parent) 0138 : MapBackend(sharedData, parent), 0139 d (new Private()) 0140 { 0141 createActions(); 0142 } 0143 0144 BackendGoogleMaps::~BackendGoogleMaps() 0145 { 0146 /// @todo Should we leave our widget in this list and not destroy it? 0147 /// Maybe for now this should simply be limited to leaving one 0148 /// unused widget in the global cache. 0149 0150 GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); 0151 go->removeMyInternalWidgetFromPool(this); 0152 0153 if (d->htmlWidgetWrapper) 0154 { 0155 delete d->htmlWidgetWrapper; 0156 } 0157 0158 delete d; 0159 } 0160 0161 void BackendGoogleMaps::createActions() 0162 { 0163 // actions for selecting the map type: 0164 0165 d->mapTypeActionGroup = new QActionGroup(this); 0166 d->mapTypeActionGroup->setExclusive(true); 0167 0168 connect(d->mapTypeActionGroup, SIGNAL(triggered(QAction*)), 0169 this, SLOT(slotMapTypeActionTriggered(QAction*))); 0170 0171 QStringList mapTypes, mapTypesHumanNames; 0172 mapTypes 0173 << QLatin1String("ROADMAP") 0174 << QLatin1String("SATELLITE") 0175 << QLatin1String("HYBRID") 0176 << QLatin1String("TERRAIN"); 0177 0178 mapTypesHumanNames 0179 << i18n("Roadmap") 0180 << i18n("Satellite") 0181 << i18n("Hybrid") 0182 << i18n("Terrain"); 0183 0184 for (int i = 0 ; i < mapTypes.count() ; ++i) 0185 { 0186 QAction* const mapTypeAction = new QAction(d->mapTypeActionGroup); 0187 mapTypeAction->setData(mapTypes.at(i)); 0188 mapTypeAction->setText(mapTypesHumanNames.at(i)); 0189 mapTypeAction->setCheckable(true); 0190 } 0191 0192 // float items: 0193 0194 d->floatItemsActionGroup = new QActionGroup(this); 0195 d->floatItemsActionGroup->setExclusive(false); 0196 0197 connect(d->floatItemsActionGroup, SIGNAL(triggered(QAction*)), 0198 this, SLOT(slotFloatSettingsTriggered(QAction*))); 0199 0200 d->showMapTypeControlAction = new QAction(i18n("Show Map Type Control"), d->floatItemsActionGroup); 0201 d->showMapTypeControlAction->setCheckable(true); 0202 d->showMapTypeControlAction->setChecked(d->cacheShowMapTypeControl); 0203 d->showMapTypeControlAction->setData(QLatin1String("showmaptypecontrol")); 0204 0205 d->showNavigationControlAction = new QAction(i18n("Show Navigation Control"), d->floatItemsActionGroup); 0206 d->showNavigationControlAction->setCheckable(true); 0207 d->showNavigationControlAction->setChecked(d->cacheShowNavigationControl); 0208 d->showNavigationControlAction->setData(QLatin1String("shownavigationcontrol")); 0209 0210 d->showScaleControlAction = new QAction(i18n("Show Scale Control"), d->floatItemsActionGroup); 0211 d->showScaleControlAction->setCheckable(true); 0212 d->showScaleControlAction->setChecked(d->cacheShowScaleControl); 0213 d->showScaleControlAction->setData(QLatin1String("showscalecontrol")); 0214 } 0215 0216 QString BackendGoogleMaps::backendName() const 0217 { 0218 return QLatin1String("googlemaps"); 0219 } 0220 0221 QString BackendGoogleMaps::backendHumanName() const 0222 { 0223 return i18n("Google Maps"); 0224 } 0225 0226 QWidget* BackendGoogleMaps::mapWidget() 0227 { 0228 if (!d->htmlWidgetWrapper) 0229 { 0230 GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); 0231 0232 GeoIfaceInternalWidgetInfo info; 0233 bool foundReusableWidget = go->getInternalWidgetFromPool(this, &info); 0234 0235 if (foundReusableWidget) 0236 { 0237 d->htmlWidgetWrapper = info.widget; 0238 const GMInternalWidgetInfo intInfo = info.backendData.value<GMInternalWidgetInfo>(); 0239 d->htmlWidget = intInfo.htmlWidget; 0240 } 0241 else 0242 { 0243 // the widget has not been created yet, create it now: 0244 0245 d->htmlWidgetWrapper = new QWidget(); 0246 d->htmlWidgetWrapper->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 0247 d->htmlWidget = new HTMLWidget(d->htmlWidgetWrapper); 0248 d->htmlWidgetWrapper->resize(400, 400); 0249 } 0250 0251 connect(d->htmlWidget, SIGNAL(signalJavaScriptReady()), 0252 this, SLOT(slotHTMLInitialized())); 0253 0254 connect(d->htmlWidget, SIGNAL(signalHTMLEvents(QStringList)), 0255 this, SLOT(slotHTMLEvents(QStringList))); 0256 0257 connect(d->htmlWidget, SIGNAL(signalMessageEvent(QString)), 0258 this, SLOT(slotMessageEvent(QString))); 0259 0260 connect(d->htmlWidget, SIGNAL(selectionHasBeenMade(Digikam::GeoCoordinates::Pair)), 0261 this, SLOT(slotSelectionHasBeenMade(Digikam::GeoCoordinates::Pair))); 0262 0263 d->htmlWidget->setSharedGeoIfaceObject(s.data()); 0264 d->htmlWidgetWrapper->installEventFilter(this); 0265 0266 if (foundReusableWidget) 0267 { 0268 slotHTMLInitialized(); 0269 } 0270 else 0271 { 0272 reload(); 0273 } 0274 } 0275 0276 return d->htmlWidgetWrapper.data(); 0277 } 0278 0279 void BackendGoogleMaps::reload() 0280 { 0281 if (d->htmlWidget) 0282 { 0283 const QUrl htmlUrl = GeoIfaceGlobalObject::instance()->locateDataFile(d->htmlFileName); 0284 d->htmlWidget->load(htmlUrl); 0285 } 0286 } 0287 0288 GeoCoordinates BackendGoogleMaps::getCenter() const 0289 { 0290 return d->cacheCenter; 0291 } 0292 0293 void BackendGoogleMaps::setCenter(const GeoCoordinates& coordinate) 0294 { 0295 d->cacheCenter = coordinate; 0296 0297 if (isReady()) 0298 { 0299 QTimer::singleShot(0, this, SLOT(slotSetCenterTimer())); 0300 } 0301 } 0302 0303 void BackendGoogleMaps::slotSetCenterTimer() 0304 { 0305 d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetCenter(%1, %2);") 0306 .arg(d->cacheCenter.latString()) 0307 .arg(d->cacheCenter.lonString())); 0308 } 0309 0310 bool BackendGoogleMaps::isReady() const 0311 { 0312 return d->isReady; 0313 } 0314 0315 void BackendGoogleMaps::slotHTMLInitialized() 0316 { 0317 d->isReady = true; 0318 d->htmlWidget->runScript(QString::fromLatin1("kgeomapWidgetResized(%1, %2)") 0319 .arg(d->htmlWidgetWrapper->width()) 0320 .arg(d->htmlWidgetWrapper->height())); 0321 0322 // TODO: call javascript directly here and update action availability in one shot 0323 0324 setMapType(d->cacheMapType); 0325 setShowScaleControl(d->cacheShowScaleControl); 0326 setShowMapTypeControl(d->cacheShowMapTypeControl); 0327 setShowNavigationControl(d->cacheShowNavigationControl); 0328 0329 Q_EMIT signalBackendReadyChanged(backendName()); 0330 } 0331 0332 void BackendGoogleMaps::zoomIn() 0333 { 0334 if (!d->isReady) 0335 { 0336 return; 0337 } 0338 0339 d->htmlWidget->runScript(QLatin1String("kgeomapZoomIn();")); 0340 } 0341 0342 void BackendGoogleMaps::zoomOut() 0343 { 0344 if (!d->isReady) 0345 { 0346 return; 0347 } 0348 0349 d->htmlWidget->runScript(QLatin1String("kgeomapZoomOut();")); 0350 } 0351 0352 QString BackendGoogleMaps::getMapType() const 0353 { 0354 return d->cacheMapType; 0355 } 0356 0357 void BackendGoogleMaps::setMapType(const QString& newMapType) 0358 { 0359 d->cacheMapType = newMapType; 0360 qCDebug(DIGIKAM_GEOIFACE_LOG) << newMapType; 0361 0362 if (isReady()) 0363 { 0364 d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetMapType(\"%1\");").arg(newMapType)); 0365 updateZoomMinMaxCache(); 0366 updateActionAvailability(); 0367 } 0368 } 0369 0370 void BackendGoogleMaps::slotMapTypeActionTriggered(QAction* action) 0371 { 0372 const QString newMapType = action->data().toString(); 0373 setMapType(newMapType); 0374 } 0375 0376 void BackendGoogleMaps::addActionsToConfigurationMenu(QMenu* const configurationMenu) 0377 { 0378 GEOIFACE_ASSERT(configurationMenu!=nullptr); 0379 0380 if (!d->isReady) 0381 { 0382 return; 0383 } 0384 0385 configurationMenu->addSeparator(); 0386 0387 // map type actions: 0388 0389 const QList<QAction*> mapTypeActions = d->mapTypeActionGroup->actions(); 0390 0391 for (int i = 0 ; i < mapTypeActions.count() ; ++i) 0392 { 0393 QAction* const mapTypeAction = mapTypeActions.at(i); 0394 configurationMenu->addAction(mapTypeAction); 0395 } 0396 0397 configurationMenu->addSeparator(); 0398 0399 // float items visibility: 0400 0401 QMenu* const floatItemsSubMenu = new QMenu(i18n("Float items"), configurationMenu); 0402 configurationMenu->addMenu(floatItemsSubMenu); 0403 0404 floatItemsSubMenu->addAction(d->showMapTypeControlAction); 0405 floatItemsSubMenu->addAction(d->showNavigationControlAction); 0406 floatItemsSubMenu->addAction(d->showScaleControlAction); 0407 0408 configurationMenu->addSeparator(); 0409 0410 addCommonOptions(configurationMenu); 0411 0412 updateActionAvailability(); 0413 } 0414 0415 void BackendGoogleMaps::saveSettingsToGroup(KConfigGroup* const group) 0416 { 0417 GEOIFACE_ASSERT(group != nullptr); 0418 0419 if (!group) 0420 { 0421 return; 0422 } 0423 0424 group->writeEntry("GoogleMaps Map Type", getMapType()); 0425 group->writeEntry("GoogleMaps Show Scale Control", d->cacheShowScaleControl); 0426 group->writeEntry("GoogleMaps Show Map Type Control", d->cacheShowMapTypeControl); 0427 group->writeEntry("GoogleMaps Show Navigation Control", d->cacheShowNavigationControl); 0428 } 0429 0430 void BackendGoogleMaps::readSettingsFromGroup(const KConfigGroup* const group) 0431 { 0432 GEOIFACE_ASSERT(group != nullptr); 0433 0434 if (!group) 0435 { 0436 return; 0437 } 0438 0439 setMapType(group->readEntry("GoogleMaps Map Type", "ROADMAP")); 0440 setShowScaleControl(group->readEntry("GoogleMaps Show Scale Control", true)); 0441 setShowMapTypeControl(group->readEntry("GoogleMaps Show Map Type Control", true)); 0442 setShowNavigationControl(group->readEntry("GoogleMaps Show Navigation Control", true)); 0443 } 0444 0445 void BackendGoogleMaps::slotUngroupedModelChanged(const int mindex) 0446 { 0447 GEOIFACE_ASSERT(isReady()); 0448 0449 if (!isReady()) 0450 { 0451 return; 0452 } 0453 0454 d->htmlWidget->runScript(QString::fromLatin1("kgeomapClearMarkers(%1);").arg(mindex)); 0455 0456 // this can happen when a model was removed and we are simply asked to remove its markers 0457 0458 if (mindex > s->ungroupedModels.count()) 0459 { 0460 return; 0461 } 0462 0463 GeoModelHelper* const modelHelper = s->ungroupedModels.at(mindex); 0464 0465 if (!modelHelper) 0466 { 0467 return; 0468 } 0469 0470 if (!modelHelper->modelFlags().testFlag(GeoModelHelper::FlagVisible)) 0471 { 0472 return; 0473 } 0474 0475 QAbstractItemModel* const model = modelHelper->model(); 0476 0477 for (int row = 0 ; row < model->rowCount() ; ++row) 0478 { 0479 const QModelIndex currentIndex = model->index(row, 0); 0480 const GeoModelHelper::PropertyFlags itemFlags = modelHelper->itemFlags(currentIndex); 0481 0482 // TODO: this is untested! We need to make sure the indices stay correct inside the JavaScript part! 0483 0484 if (!itemFlags.testFlag(GeoModelHelper::FlagVisible)) 0485 { 0486 continue; 0487 } 0488 0489 GeoCoordinates currentCoordinates; 0490 0491 if (!modelHelper->itemCoordinates(currentIndex, ¤tCoordinates)) 0492 { 0493 continue; 0494 } 0495 0496 // TODO: use the pixmap supplied by the modelHelper 0497 0498 d->htmlWidget->runScript(QString::fromLatin1("kgeomapAddMarker(%1, %2, %3, %4, %5, %6);") 0499 .arg(mindex) 0500 .arg(row) 0501 .arg(currentCoordinates.latString()) 0502 .arg(currentCoordinates.lonString()) 0503 .arg(itemFlags.testFlag(GeoModelHelper::FlagMovable) ? QLatin1String("true") 0504 : QLatin1String("false")) 0505 .arg(itemFlags.testFlag(GeoModelHelper::FlagSnaps) ? QLatin1String("true") 0506 : QLatin1String("false")) 0507 ); 0508 0509 QPoint markerCenterPoint; 0510 QSize markerSize; 0511 QPixmap markerPixmap; 0512 QUrl markerUrl; 0513 const bool markerHasIcon = modelHelper->itemIcon(currentIndex, &markerCenterPoint, 0514 &markerSize, &markerPixmap, &markerUrl); 0515 0516 if (markerHasIcon) 0517 { 0518 if (!markerUrl.isEmpty()) 0519 { 0520 setMarkerPixmap(mindex, row, markerCenterPoint, markerSize, markerUrl); 0521 } 0522 else 0523 { 0524 setMarkerPixmap(mindex, row, markerCenterPoint, markerPixmap); 0525 } 0526 } 0527 } 0528 } 0529 void BackendGoogleMaps::updateMarkers() 0530 { 0531 // re-transfer all markers to the javascript-part: 0532 0533 for (int i = 0 ; i < s->ungroupedModels.count() ; ++i) 0534 { 0535 slotUngroupedModelChanged(i); 0536 } 0537 } 0538 0539 void BackendGoogleMaps::slotHTMLEvents(const QStringList& events) 0540 { 0541 // for some events, we just note that they appeared and then process them later on: 0542 0543 bool centerProbablyChanged = false; 0544 bool mapTypeChanged = false; 0545 bool zoomProbablyChanged = false; 0546 bool mapBoundsProbablyChanged = false; 0547 QIntList movedClusters; 0548 QList<QPersistentModelIndex> movedMarkers; 0549 QIntList clickedClusters; 0550 0551 // TODO: verify that the order of the events is still okay 0552 // or that the order does not matter 0553 0554 for (QStringList::const_iterator it = events.constBegin() ; it != events.constEnd() ; ++it) 0555 { 0556 const QString eventCode = it->left(2); 0557 const QString eventParameter = it->mid(2); 0558 const QStringList eventParameters = eventParameter.split(QLatin1Char('/')); 0559 0560 if (eventCode == QLatin1String("MT")) 0561 { 0562 // map type changed 0563 0564 mapTypeChanged = true; 0565 d->cacheMapType = eventParameter; 0566 } 0567 else if (eventCode == QLatin1String("MB")) 0568 { 0569 // NOTE: event currently disabled in javascript part 0570 // map bounds changed 0571 0572 centerProbablyChanged = true; 0573 zoomProbablyChanged = true; 0574 mapBoundsProbablyChanged = true; 0575 } 0576 else if (eventCode == QLatin1String("ZC")) 0577 { 0578 // NOTE: event currently disabled in javascript part 0579 // zoom changed 0580 0581 zoomProbablyChanged = true; 0582 mapBoundsProbablyChanged = true; 0583 } 0584 else if (eventCode == QLatin1String("id")) 0585 { 0586 // idle after drastic map changes 0587 0588 centerProbablyChanged = true; 0589 zoomProbablyChanged = true; 0590 mapBoundsProbablyChanged = true; 0591 } 0592 else if (eventCode == QLatin1String("cm")) 0593 { 0594 /// @todo buffer this event type! 0595 // cluster moved 0596 0597 bool okay = false; 0598 const int clusterIndex = eventParameter.toInt(&okay); 0599 GEOIFACE_ASSERT(okay); 0600 0601 if (!okay) 0602 { 0603 continue; 0604 } 0605 0606 GEOIFACE_ASSERT(clusterIndex >= 0); 0607 GEOIFACE_ASSERT(clusterIndex < s->clusterList.size()); 0608 0609 if ((clusterIndex < 0) || (clusterIndex > s->clusterList.size())) 0610 { 0611 continue; 0612 } 0613 0614 // re-read the marker position: 0615 0616 GeoCoordinates clusterCoordinates; 0617 const bool isValid = d->htmlWidget->runScript2Coordinates( 0618 QString::fromLatin1("kgeomapGetClusterPosition(%1);").arg(clusterIndex), 0619 &clusterCoordinates); 0620 0621 GEOIFACE_ASSERT(isValid); 0622 0623 if (!isValid) 0624 { 0625 continue; 0626 } 0627 0628 /// @todo this discards the altitude! 0629 /// @todo is this really necessary? clusters should be regenerated anyway... 0630 0631 s->clusterList[clusterIndex].coordinates = clusterCoordinates; 0632 0633 movedClusters << clusterIndex; 0634 } 0635 else if (eventCode == QLatin1String("cs")) 0636 { 0637 /// @todo buffer this event type! 0638 // cluster snapped 0639 0640 bool okay = false; 0641 const int clusterIndex = eventParameters.first().toInt(&okay); 0642 GEOIFACE_ASSERT(okay); 0643 0644 if (!okay) 0645 { 0646 continue; 0647 } 0648 0649 GEOIFACE_ASSERT(clusterIndex >= 0); 0650 GEOIFACE_ASSERT(clusterIndex < s->clusterList.size()); 0651 0652 if ((clusterIndex < 0) || (clusterIndex > s->clusterList.size())) 0653 { 0654 continue; 0655 } 0656 0657 // determine to which marker we snapped: 0658 0659 okay = false; 0660 const int snapModelId = eventParameters.at(1).toInt(&okay); 0661 GEOIFACE_ASSERT(okay); 0662 0663 if (!okay) 0664 { 0665 continue; 0666 } 0667 0668 okay = false; 0669 const int snapMarkerId = eventParameters.at(2).toInt(&okay); 0670 GEOIFACE_ASSERT(okay); 0671 0672 if (!okay) 0673 { 0674 continue; 0675 } 0676 0677 /// @todo Q_EMIT signal here or later? 0678 0679 GeoModelHelper* const modelHelper = s->ungroupedModels.at(snapModelId); 0680 QAbstractItemModel* const model = modelHelper->model(); 0681 QPair<int, QModelIndex> snapTargetIndex(snapModelId, model->index(snapMarkerId, 0)); 0682 Q_EMIT signalClustersMoved(QIntList() << clusterIndex, snapTargetIndex); 0683 } 0684 else if (eventCode == QLatin1String("cc")) 0685 { 0686 /// @todo buffer this event type! 0687 // cluster clicked 0688 0689 bool okay = false; 0690 const int clusterIndex = eventParameter.toInt(&okay); 0691 GEOIFACE_ASSERT(okay); 0692 0693 if (!okay) 0694 { 0695 continue; 0696 } 0697 0698 GEOIFACE_ASSERT(clusterIndex >= 0); 0699 GEOIFACE_ASSERT(clusterIndex < s->clusterList.size()); 0700 0701 if ((clusterIndex < 0) || (clusterIndex > s->clusterList.size())) 0702 { 0703 continue; 0704 } 0705 0706 clickedClusters << clusterIndex; 0707 } 0708 else if (eventCode == QLatin1String("mm")) 0709 { 0710 /* 0711 // TODO: buffer this event type! 0712 // marker moved 0713 0714 bool okay = false; 0715 const int markerRow = eventParameter.toInt(&okay); 0716 GEOIFACE_ASSERT(okay); 0717 0718 if (!okay) 0719 { 0720 continue; 0721 } 0722 0723 GEOIFACE_ASSERT(markerRow >= 0); 0724 GEOIFACE_ASSERT(markerRow<s->specialMarkersModel->rowCount()); 0725 0726 if ((markerRow<0)||(markerRow>=s->specialMarkersModel->rowCount())) 0727 { 0728 continue; 0729 } 0730 0731 // re-read the marker position: 0732 0733 GeoCoordinates markerCoordinates; 0734 const bool isValid = d->htmlWidget->runScript2Coordinates( 0735 QString::fromLatin1("kgeomapGetMarkerPosition(%1);").arg(markerRow), 0736 &markerCoordinates 0737 ); 0738 0739 GEOIFACE_ASSERT(isValid); 0740 0741 if (!isValid) 0742 { 0743 continue; 0744 } 0745 0746 // TODO: this discards the altitude! 0747 0748 const QModelIndex markerIndex = s->specialMarkersModel->index(markerRow, 0); 0749 s->specialMarkersModel->setData(markerIndex, QVariant::fromValue(markerCoordinates), s->specialMarkersCoordinatesRole); 0750 0751 movedMarkers << QPersistentModelIndex(markerIndex); 0752 */ 0753 } 0754 else if (eventCode == QLatin1String("do")) 0755 { 0756 // debug output: 0757 0758 qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("javascript:%1").arg(eventParameter); 0759 } 0760 } 0761 0762 if (!movedClusters.isEmpty()) 0763 { 0764 qCDebug(DIGIKAM_GEOIFACE_LOG) << movedClusters; 0765 0766 Q_EMIT signalClustersMoved(movedClusters, QPair<int, QModelIndex>(-1, QModelIndex())); 0767 } 0768 0769 // cppcheck-suppress knownConditionTrueFalse 0770 if (!movedMarkers.isEmpty()) 0771 { 0772 qCDebug(DIGIKAM_GEOIFACE_LOG) << movedMarkers; 0773 /* 0774 Q_EMIT signalSpecialMarkersMoved(movedMarkers); 0775 */ 0776 } 0777 0778 if (!clickedClusters.isEmpty()) 0779 { 0780 Q_EMIT signalClustersClicked(clickedClusters); 0781 } 0782 0783 // now process the buffered events: 0784 0785 if (mapTypeChanged) 0786 { 0787 updateZoomMinMaxCache(); 0788 } 0789 0790 if (zoomProbablyChanged && !mapTypeChanged) 0791 { 0792 d->cacheZoom = d->htmlWidget->runScript(QLatin1String("kgeomapGetZoom();"), false).toInt(); 0793 0794 Q_EMIT signalZoomChanged(QString::fromLatin1("googlemaps:%1").arg(d->cacheZoom)); 0795 } 0796 0797 if (centerProbablyChanged && !mapTypeChanged) 0798 { 0799 // there is nothing we can do if the coordinates are invalid 0800 /* 0801 const bool isValid = 0802 */ 0803 d->htmlWidget->runScript2Coordinates(QLatin1String("kgeomapGetCenter();"), &(d->cacheCenter)); 0804 } 0805 0806 // update the actions if necessary: 0807 0808 if (zoomProbablyChanged || mapTypeChanged || centerProbablyChanged) 0809 { 0810 updateActionAvailability(); 0811 } 0812 0813 if (mapBoundsProbablyChanged) 0814 { 0815 const QString mapBoundsString = d->htmlWidget->runScript(QLatin1String("kgeomapGetBounds();"), false).toString(); 0816 const bool isValid = GeoIfaceHelperParseBoundsString(mapBoundsString, &d->cacheBounds); 0817 0818 if (!isValid) 0819 { 0820 qCDebug(DIGIKAM_GEOIFACE_LOG) << "Invalid map bounds"; 0821 } 0822 } 0823 0824 if (mapBoundsProbablyChanged || !movedClusters.isEmpty()) 0825 { 0826 s->worldMapWidget->markClustersAsDirty(); 0827 s->worldMapWidget->updateClusters(); 0828 } 0829 } 0830 0831 void BackendGoogleMaps::updateClusters() 0832 { 0833 qCDebug(DIGIKAM_GEOIFACE_LOG) << "start updateclusters"; 0834 0835 // re-transfer the clusters to the map: 0836 0837 GEOIFACE_ASSERT(isReady()); 0838 0839 if (!isReady()) 0840 { 0841 return; 0842 } 0843 0844 // TODO: only update clusters that have actually changed! 0845 0846 // re-transfer all markers to the javascript-part: 0847 0848 const bool canMoveItems = !s->showThumbnails && 0849 s->modificationsAllowed && 0850 s->markerModel->tilerFlags().testFlag(AbstractMarkerTiler::FlagMovable); 0851 0852 d->htmlWidget->runScript(QLatin1String("kgeomapClearClusters();")); 0853 d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetIsInEditMode(%1);") 0854 .arg(s->showThumbnails ? QLatin1String("false") 0855 : QLatin1String("true")) 0856 ); 0857 0858 for (int currentIndex = 0 ; currentIndex < s->clusterList.size() ; ++currentIndex) 0859 { 0860 const GeoIfaceCluster& currentCluster = s->clusterList.at(currentIndex); 0861 0862 d->htmlWidget->runScript(QString::fromLatin1("kgeomapAddCluster(%1, %2, %3, %4, %5, %6);") 0863 .arg(currentIndex) 0864 .arg(currentCluster.coordinates.latString()) 0865 .arg(currentCluster.coordinates.lonString()) 0866 .arg(canMoveItems ? QLatin1String("true") 0867 : QLatin1String("false")) 0868 .arg(currentCluster.markerCount) 0869 .arg(currentCluster.markerSelectedCount) 0870 ); 0871 0872 // TODO: for now, only set generated pixmaps when not in edit mode 0873 // this can be changed once we figure out how to appropriately handle 0874 // the selection state changes when a marker is dragged 0875 0876 if (s->showThumbnails) 0877 { 0878 QPoint clusterCenterPoint; 0879 0880 // TODO: who calculates the override values? 0881 0882 const QPixmap clusterPixmap = s->worldMapWidget->getDecoratedPixmapForCluster(currentIndex, nullptr, nullptr, &clusterCenterPoint); 0883 0884 setClusterPixmap(currentIndex, clusterCenterPoint, clusterPixmap); 0885 } 0886 } 0887 0888 qCDebug(DIGIKAM_GEOIFACE_LOG) << "end updateclusters"; 0889 } 0890 0891 bool BackendGoogleMaps::screenCoordinates(const GeoCoordinates& coordinates, QPoint* const point) 0892 { 0893 if (!d->isReady) 0894 { 0895 return false; 0896 } 0897 0898 const QString pointStringResult = d->htmlWidget->runScript( 0899 QString::fromLatin1("kgeomapLatLngToPixel(%1, %2);") 0900 .arg(coordinates.latString()) 0901 .arg(coordinates.lonString()), 0902 false).toString(); 0903 0904 const bool isValid = GeoIfaceHelperParseXYStringToPoint(pointStringResult, point); 0905 0906 // TODO: apparently, even points outside the visible area are returned as valid 0907 // check whether they are actually visible 0908 0909 return isValid; 0910 } 0911 0912 bool BackendGoogleMaps::geoCoordinates(const QPoint& point, GeoCoordinates* const coordinates) const 0913 { 0914 if (!d->isReady) 0915 { 0916 return false; 0917 } 0918 0919 const bool isValid = d->htmlWidget->runScript2Coordinates( 0920 QString::fromLatin1("kgeomapPixelToLatLng(%1, %2);") 0921 .arg(point.x()) 0922 .arg(point.y()), 0923 coordinates); 0924 0925 return isValid; 0926 } 0927 0928 QSize BackendGoogleMaps::mapSize() const 0929 { 0930 GEOIFACE_ASSERT(d->htmlWidgetWrapper != nullptr); 0931 0932 return d->htmlWidgetWrapper->size(); 0933 } 0934 0935 void BackendGoogleMaps::slotFloatSettingsTriggered(QAction* action) 0936 { 0937 const QString actionIdString = action->data().toString(); 0938 const bool actionState = action->isChecked(); 0939 0940 if (actionIdString == QLatin1String("showmaptypecontrol")) 0941 { 0942 setShowMapTypeControl(actionState); 0943 } 0944 else if (actionIdString == QLatin1String("shownavigationcontrol")) 0945 { 0946 setShowNavigationControl(actionState); 0947 } 0948 else if (actionIdString == QLatin1String("showscalecontrol")) 0949 { 0950 setShowScaleControl(actionState); 0951 } 0952 } 0953 0954 void BackendGoogleMaps::setShowScaleControl(const bool state) 0955 { 0956 d->cacheShowScaleControl = state; 0957 0958 if (d->showScaleControlAction) 0959 { 0960 d->showScaleControlAction->setChecked(state); 0961 } 0962 0963 if (!isReady()) 0964 { 0965 return; 0966 } 0967 0968 d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetShowScaleControl(%1);") 0969 .arg(state ? QLatin1String("true") 0970 : QLatin1String("false")) 0971 ); 0972 } 0973 0974 void BackendGoogleMaps::setShowNavigationControl(const bool state) 0975 { 0976 d->cacheShowNavigationControl = state; 0977 0978 if (d->showNavigationControlAction) 0979 { 0980 d->showNavigationControlAction->setChecked(state); 0981 } 0982 0983 if (!isReady()) 0984 { 0985 return; 0986 } 0987 0988 d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetShowNavigationControl(%1);") 0989 .arg(state ? QLatin1String("true") 0990 : QLatin1String("false")) 0991 ); 0992 } 0993 0994 void BackendGoogleMaps::setShowMapTypeControl(const bool state) 0995 { 0996 d->cacheShowMapTypeControl = state; 0997 0998 if (d->showMapTypeControlAction) 0999 { 1000 d->showMapTypeControlAction->setChecked(state); 1001 } 1002 1003 if (!isReady()) 1004 { 1005 return; 1006 } 1007 1008 d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetShowMapTypeControl(%1);") 1009 .arg(state ? QLatin1String("true") 1010 : QLatin1String("false")) 1011 ); 1012 } 1013 1014 void BackendGoogleMaps::slotClustersNeedUpdating() 1015 { 1016 s->worldMapWidget->updateClusters(); 1017 } 1018 1019 void BackendGoogleMaps::setZoom(const QString& newZoom) 1020 { 1021 const QString myZoomString = s->worldMapWidget->convertZoomToBackendZoom(newZoom, QLatin1String("googlemaps")); 1022 GEOIFACE_ASSERT(myZoomString.startsWith(QLatin1String("googlemaps:"))); 1023 1024 const int myZoom = myZoomString.mid(QString::fromLatin1("googlemaps:").length()).toInt(); 1025 d->cacheZoom = myZoom; 1026 1027 if (isReady()) 1028 { 1029 d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetZoom(%1);").arg(d->cacheZoom)); 1030 } 1031 } 1032 1033 QString BackendGoogleMaps::getZoom() const 1034 { 1035 return QString::fromLatin1("googlemaps:%1").arg(d->cacheZoom); 1036 } 1037 1038 int BackendGoogleMaps::getMarkerModelLevel() 1039 { 1040 GEOIFACE_ASSERT(isReady()); 1041 1042 if (!isReady()) 1043 { 1044 return 0; 1045 } 1046 1047 // get the current zoom level: 1048 1049 const int currentZoom = d->cacheZoom; 1050 1051 int tileLevel = 0; 1052 1053 if (currentZoom == 0) { tileLevel = 1; } 1054 else if (currentZoom == 1) { tileLevel = 1; } 1055 else if (currentZoom == 2) { tileLevel = 1; } 1056 else if (currentZoom == 3) { tileLevel = 2; } 1057 else if (currentZoom == 4) { tileLevel = 2; } 1058 else if (currentZoom == 5) { tileLevel = 3; } 1059 else if (currentZoom == 6) { tileLevel = 3; } 1060 else if (currentZoom == 7) { tileLevel = 3; } 1061 else if (currentZoom == 8) { tileLevel = 4; } 1062 else if (currentZoom == 9) { tileLevel = 4; } 1063 else if (currentZoom == 10) { tileLevel = 4; } 1064 else if (currentZoom == 11) { tileLevel = 4; } 1065 else if (currentZoom == 12) { tileLevel = 4; } 1066 else if (currentZoom == 13) { tileLevel = 4; } 1067 else if (currentZoom == 14) { tileLevel = 5; } 1068 else if (currentZoom == 15) { tileLevel = 5; } 1069 else if (currentZoom == 16) { tileLevel = 6; } 1070 else if (currentZoom == 17) { tileLevel = 7; } 1071 else if (currentZoom == 18) { tileLevel = 7; } 1072 else if (currentZoom == 19) { tileLevel = 8; } 1073 else if (currentZoom == 20) { tileLevel = 9; } 1074 else if (currentZoom == 21) { tileLevel = 9; } 1075 else if (currentZoom == 22) { tileLevel = 9; } 1076 else 1077 { 1078 tileLevel = TileIndex::MaxLevel - 1; 1079 } 1080 1081 GEOIFACE_ASSERT(tileLevel <= TileIndex::MaxLevel-1); 1082 1083 return tileLevel; 1084 } 1085 1086 GeoCoordinates::PairList BackendGoogleMaps::getNormalizedBounds() 1087 { 1088 return GeoIfaceHelperNormalizeBounds(d->cacheBounds); 1089 } 1090 1091 /* 1092 void BackendGoogleMaps::updateDragDropMarker(const QPoint& pos, const GeoIfaceDragData* const dragData) 1093 { 1094 if (!isReady()) 1095 { 1096 return; 1097 } 1098 1099 if (!dragData) 1100 { 1101 d->htmlWidget->runScript("kgeomapRemoveDragMarker();"); 1102 } 1103 else 1104 { 1105 d->htmlWidget->runScript(QLatin1String("kgeomapSetDragMarker(%1, %2, %3, %4);") 1106 .arg(pos.x()) 1107 .arg(pos.y()) 1108 .arg(dragData->itemCount) 1109 .arg(dragData->itemCount) 1110 ); 1111 } 1112 1113 // TODO: hide dragged markers on the map 1114 } 1115 1116 void BackendGoogleMaps::updateDragDropMarkerPosition(const QPoint& pos) 1117 { 1118 // TODO: buffer this! 1119 1120 if (!isReady()) 1121 { 1122 return; 1123 } 1124 1125 d->htmlWidget->runScript(QLatin1String("kgeomapMoveDragMarker(%1, %2);") 1126 .arg(pos.x()) 1127 .arg(pos.y()) 1128 ); 1129 } 1130 */ 1131 1132 void BackendGoogleMaps::updateActionAvailability() 1133 { 1134 if (!d->activeState || !isReady()) 1135 { 1136 return; 1137 } 1138 1139 const QString currentMapType = getMapType(); 1140 const QList<QAction*> mapTypeActions = d->mapTypeActionGroup->actions(); 1141 1142 for (int i = 0 ; i < mapTypeActions.size() ; ++i) 1143 { 1144 mapTypeActions.at(i)->setChecked(mapTypeActions.at(i)->data().toString()==currentMapType); 1145 } 1146 1147 s->worldMapWidget->getControlAction(QLatin1String("zoomin"))->setEnabled(true/*d->cacheZoom<d->cacheMaxZoom*/); 1148 s->worldMapWidget->getControlAction(QLatin1String("zoomout"))->setEnabled(true/*d->cacheZoom>d->cacheMinZoom*/); 1149 } 1150 1151 void BackendGoogleMaps::updateZoomMinMaxCache() 1152 { 1153 // TODO: these functions seem to cause problems, the map is not fully updated after a few calls 1154 /* 1155 d->cacheMaxZoom = d->htmlWidget->runScript("kgeomapGetMaxZoom();", false).toInt(); 1156 d->cacheMinZoom = d->htmlWidget->runScript("kgeomapGetMinZoom();", false).toInt(); 1157 */ 1158 } 1159 1160 void BackendGoogleMaps::slotThumbnailAvailableForIndex(const QVariant& index, const QPixmap& pixmap) 1161 { 1162 qCDebug(DIGIKAM_GEOIFACE_LOG) << index<<pixmap.size(); 1163 1164 if (pixmap.isNull() || !s->showThumbnails) 1165 { 1166 return; 1167 } 1168 1169 // TODO: properly reject pixmaps with the wrong size 1170 1171 const int expectedThumbnailSize = s->worldMapWidget->getUndecoratedThumbnailSize(); 1172 1173 if ((pixmap.size().height() > expectedThumbnailSize) || (pixmap.size().width() > expectedThumbnailSize)) 1174 { 1175 return; 1176 } 1177 1178 // find the cluster which is represented by this index: 1179 1180 for (int i = 0 ; i < s->clusterList.count() ; ++i) 1181 { 1182 // TODO: use the right sortkey 1183 // TODO: let the representativeChooser handle the index comparison 1184 1185 const QVariant representativeMarker = s->worldMapWidget->getClusterRepresentativeMarker(i, s->sortKey); 1186 1187 if (s->markerModel->indicesEqual(index, representativeMarker)) 1188 { 1189 QPoint clusterCenterPoint; 1190 1191 // TODO: who calculates the override values? 1192 1193 const QPixmap clusterPixmap = s->worldMapWidget->getDecoratedPixmapForCluster(i, nullptr, nullptr, &clusterCenterPoint); 1194 1195 setClusterPixmap(i, clusterCenterPoint, clusterPixmap); 1196 1197 break; 1198 } 1199 } 1200 } 1201 1202 void BackendGoogleMaps::setClusterPixmap(const int clusterId, const QPoint& centerPoint, const QPixmap& clusterPixmap) 1203 { 1204 // decorate the pixmap: 1205 1206 const QPixmap styledPixmap = clusterPixmap; 1207 1208 QByteArray bytes; 1209 QBuffer buffer(&bytes); 1210 buffer.open(QIODevice::WriteOnly); 1211 clusterPixmap.save(&buffer, "PNG"); 1212 buffer.close(); 1213 1214 // www.faqs.org/rfcs/rfc2397.html 1215 1216 const QString imageData = QString::fromLatin1("data:image/png;base64,%1").arg(QString::fromLatin1(bytes.toBase64())); 1217 d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetClusterPixmap(%1,%5,%6,%2,%3,'%4');") 1218 .arg(clusterId) 1219 .arg(centerPoint.x()) 1220 .arg(centerPoint.y()) 1221 .arg(imageData) 1222 .arg(clusterPixmap.width()) 1223 .arg(clusterPixmap.height()) 1224 ); 1225 } 1226 1227 void BackendGoogleMaps::setMarkerPixmap(const int modelId, const int markerId, 1228 const QPoint& centerPoint, const QPixmap& markerPixmap) 1229 { 1230 QByteArray bytes; 1231 QBuffer buffer(&bytes); 1232 buffer.open(QIODevice::WriteOnly); 1233 markerPixmap.save(&buffer, "PNG"); 1234 buffer.close(); 1235 1236 // www.faqs.org/rfcs/rfc2397.html 1237 1238 const QString imageData = QString::fromLatin1("data:image/png;base64,%1").arg(QString::fromLatin1(bytes.toBase64())); 1239 d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetMarkerPixmap(%7,%1,%5,%6,%2,%3,'%4');") 1240 .arg(markerId) 1241 .arg(centerPoint.x()) 1242 .arg(centerPoint.y()) 1243 .arg(imageData) 1244 .arg(markerPixmap.width()) 1245 .arg(markerPixmap.height()) 1246 .arg(modelId) 1247 ); 1248 } 1249 1250 void BackendGoogleMaps::setMarkerPixmap(const int modelId, const int markerId, 1251 const QPoint& centerPoint, const QSize& iconSize, 1252 const QUrl& iconUrl) 1253 { 1254 /// @todo Sort the parameters 1255 1256 d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetMarkerPixmap(%7,%1,%5,%6,%2,%3,'%4');") 1257 .arg(markerId) 1258 .arg(centerPoint.x()) 1259 .arg(centerPoint.y()) 1260 .arg(iconUrl.url()) /// @todo Escape characters like apostrophe 1261 .arg(iconSize.width()) 1262 .arg(iconSize.height()) 1263 .arg(modelId) 1264 ); 1265 } 1266 1267 bool BackendGoogleMaps::eventFilter(QObject* object, QEvent* event) 1268 { 1269 if (object == d->htmlWidgetWrapper) 1270 { 1271 if (event->type() == QEvent::Resize) 1272 { 1273 QResizeEvent* const resizeEvent = dynamic_cast<QResizeEvent*>(event); 1274 1275 if (resizeEvent) 1276 { 1277 // TODO: the map div does not adjust its height properly if height=100%, 1278 // therefore we adjust it manually here 1279 1280 if (d->isReady) 1281 { 1282 d->htmlWidget->runScript(QString::fromLatin1("kgeomapWidgetResized(%1, %2)") 1283 .arg(d->htmlWidgetWrapper->width()) 1284 .arg(d->htmlWidgetWrapper->height()) 1285 ); 1286 } 1287 } 1288 } 1289 } 1290 1291 return false; 1292 } 1293 1294 void BackendGoogleMaps::regionSelectionChanged() 1295 { 1296 if (!d->htmlWidget) 1297 { 1298 return; 1299 } 1300 1301 if (s->hasRegionSelection()) 1302 { 1303 d->htmlWidget->setSelectionRectangle(s->selectionRectangle); 1304 } 1305 else 1306 { 1307 d->htmlWidget->removeSelectionRectangle(); 1308 } 1309 } 1310 1311 void BackendGoogleMaps::mouseModeChanged() 1312 { 1313 if (!d->htmlWidget) 1314 { 1315 return; 1316 } 1317 1318 /// @todo Does htmlwidget read this value from s->currentMouseMode on its own? 1319 1320 d->htmlWidget->mouseModeChanged(s->currentMouseMode); 1321 } 1322 1323 void BackendGoogleMaps::slotSelectionHasBeenMade(const Digikam::GeoCoordinates::Pair& searchCoordinates) 1324 { 1325 Q_EMIT signalSelectionHasBeenMade(searchCoordinates); 1326 } 1327 1328 void BackendGoogleMaps::setActive(const bool state) 1329 { 1330 const bool oldState = d->activeState; 1331 d->activeState = state; 1332 1333 if (oldState != state) 1334 { 1335 if (!state && d->htmlWidgetWrapper) 1336 { 1337 // we should share our widget in the list of widgets in the global object 1338 1339 GeoIfaceInternalWidgetInfo info; 1340 info.deleteFunction = deleteInfoFunction; 1341 info.widget = d->htmlWidgetWrapper.data(); 1342 info.currentOwner = this; 1343 info.backendName = backendName(); 1344 info.state = d->widgetIsDocked ? GeoIfaceInternalWidgetInfo::InternalWidgetStillDocked 1345 : GeoIfaceInternalWidgetInfo::InternalWidgetUndocked; 1346 1347 GMInternalWidgetInfo intInfo; 1348 intInfo.htmlWidget = d->htmlWidget.data(); 1349 info.backendData.setValue(intInfo); 1350 1351 GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); 1352 go->addMyInternalWidgetToPool(info); 1353 } 1354 1355 if (state && d->htmlWidgetWrapper) 1356 { 1357 // we should remove our widget from the list of widgets in the global object 1358 1359 GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); 1360 go->removeMyInternalWidgetFromPool(this); 1361 1362 /// @todo re-cluster, update markers? 1363 1364 setMapType(d->cacheMapType); 1365 setShowScaleControl(d->cacheShowScaleControl); 1366 setShowMapTypeControl(d->cacheShowMapTypeControl); 1367 setShowNavigationControl(d->cacheShowNavigationControl); 1368 1369 setCenter(d->cacheCenter); 1370 d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetZoom(%1);").arg(d->cacheZoom)); 1371 1372 /// @TODO update tracks more gently 1373 1374 slotTracksChanged(d->trackChangeTracker); 1375 d->trackChangeTracker.clear(); 1376 } 1377 } 1378 } 1379 1380 void BackendGoogleMaps::releaseWidget(GeoIfaceInternalWidgetInfo* const info) 1381 { 1382 // clear all tracks 1383 1384 d->htmlWidget->runScript(QString::fromLatin1("kgeomapClearTracks();")); 1385 1386 disconnect(d->htmlWidget, SIGNAL(signalJavaScriptReady()), 1387 this, SLOT(slotHTMLInitialized())); 1388 1389 disconnect(d->htmlWidget, SIGNAL(signalHTMLEvents(QStringList)), 1390 this, SLOT(slotHTMLEvents(QStringList))); 1391 1392 disconnect(d->htmlWidget, SIGNAL(selectionHasBeenMade(Digikam::GeoCoordinates::Pair)), 1393 this, SLOT(slotSelectionHasBeenMade(Digikam::GeoCoordinates::Pair))); 1394 1395 d->htmlWidget->setSharedGeoIfaceObject(nullptr); 1396 d->htmlWidgetWrapper->removeEventFilter(this); 1397 1398 d->htmlWidget = nullptr; 1399 d->htmlWidgetWrapper = nullptr; 1400 info->currentOwner = nullptr; 1401 info->state = GeoIfaceInternalWidgetInfo::InternalWidgetReleased; 1402 d->isReady = false; 1403 1404 Q_EMIT signalBackendReadyChanged(backendName()); 1405 } 1406 1407 void BackendGoogleMaps::mapWidgetDocked(const bool state) 1408 { 1409 if (d->widgetIsDocked != state) 1410 { 1411 GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); 1412 go->updatePooledWidgetState(d->htmlWidgetWrapper, state ? GeoIfaceInternalWidgetInfo::InternalWidgetStillDocked 1413 : GeoIfaceInternalWidgetInfo::InternalWidgetUndocked); 1414 } 1415 1416 d->widgetIsDocked = state; 1417 } 1418 1419 void BackendGoogleMaps::deleteInfoFunction(GeoIfaceInternalWidgetInfo* const info) 1420 { 1421 if (info->currentOwner) 1422 { 1423 qobject_cast<MapBackend*>(info->currentOwner.data())->releaseWidget(info); 1424 } 1425 1426 const GMInternalWidgetInfo intInfo = info->backendData.value<GMInternalWidgetInfo>(); 1427 1428 delete intInfo.htmlWidget; 1429 delete info->widget.data(); 1430 } 1431 1432 void BackendGoogleMaps::storeTrackChanges(const TrackManager::TrackChanges trackChanges) 1433 { 1434 for (int i = 0 ; i < d->trackChangeTracker.count() ; ++i) 1435 { 1436 if (d->trackChangeTracker.at(i).first == trackChanges.first) 1437 { 1438 d->trackChangeTracker[i].second = TrackManager::ChangeFlag(d->trackChangeTracker.at(i).second | trackChanges.second); 1439 1440 return; 1441 } 1442 } 1443 1444 d->trackChangeTracker << trackChanges; 1445 } 1446 1447 void BackendGoogleMaps::slotTrackManagerChanged() 1448 { 1449 /// @TODO disconnect old track manager 1450 /// @TODO mark all tracks as dirty 1451 1452 if (s->trackManager) 1453 { 1454 connect(s->trackManager, SIGNAL(signalTracksChanged(QList<TrackManager::TrackChanges>)), 1455 this, SLOT(slotTracksChanged(QList<TrackManager::TrackChanges>))); 1456 1457 connect(s->trackManager, SIGNAL(signalVisibilityChanged(bool)), 1458 this, SLOT(slotTrackVisibilityChanged(bool))); 1459 1460 // store all tracks which are already in the manager as changed 1461 1462 const TrackManager::Track::List trackList = s->trackManager->getTrackList(); 1463 1464 Q_FOREACH (const TrackManager::Track& t, trackList) 1465 { 1466 storeTrackChanges(TrackManager::TrackChanges(t.id, TrackManager::ChangeAdd)); 1467 } 1468 } 1469 } 1470 1471 void BackendGoogleMaps::slotTracksChanged(const QList<TrackManager::TrackChanges>& trackChanges) 1472 { 1473 bool needToTrackChanges = !d->activeState; 1474 1475 if (s->trackManager) 1476 { 1477 needToTrackChanges |= !s->trackManager->getVisibility(); 1478 } 1479 1480 if (needToTrackChanges) 1481 { 1482 Q_FOREACH (const TrackManager::TrackChanges& tc, trackChanges) 1483 { 1484 storeTrackChanges(tc); 1485 } 1486 1487 return; 1488 } 1489 1490 /// @TODO We have to re-read the tracks after being inactive. 1491 /// @TODO Tracks have to be cleared in JavaScript every time the 1492 /// htmlwidget is passed to another mapwidget. 1493 /// @TODO Clearing all tracks and re-adding them takes too long. We 1494 /// have to see which track changed, and whether coordinates or only properties changed. 1495 1496 if (!s->trackManager) 1497 { 1498 // no track manager, clear all tracks 1499 1500 const QVariant successClear = d->htmlWidget->runScript(QString::fromLatin1("kgeomapClearTracks();"), false); 1501 1502 return; 1503 } 1504 1505 Q_FOREACH (const TrackManager::TrackChanges& tc, trackChanges) 1506 { 1507 if (tc.second & TrackManager::ChangeRemoved) 1508 { 1509 d->htmlWidget->runScript(QString::fromLatin1("kgeomapRemoveTrack(%1);").arg(tc.first)); 1510 } 1511 else 1512 { 1513 /// @TODO For now, remove the track and re-add it. 1514 1515 d->htmlWidget->runScript(QString::fromLatin1("kgeomapRemoveTrack(%1);").arg(tc.first)); 1516 1517 const TrackManager::Track track = s->trackManager->getTrackById(tc.first); 1518 1519 if (track.points.count() < 2) 1520 { 1521 continue; 1522 } 1523 1524 const QString createTrackScript = QString::fromLatin1("kgeomapCreateTrack(%1,'%2');") 1525 .arg(track.id) 1526 .arg(track.color.name()); // QColor::name() returns #ff00ff 1527 d->htmlWidget->runScript(createTrackScript); 1528 1529 QDateTime t1 = QDateTime::currentDateTime(); 1530 const int numPointsToPassAtOnce = 1000; 1531 1532 for (int coordIdx = 0 ; coordIdx < track.points.count() ; coordIdx += numPointsToPassAtOnce) 1533 { 1534 /// @TODO Even by passing only a few points each time, we can 1535 /// block the UI for a long time. Instead, it may be better 1536 /// to call addPointsToTrack via the eventloop repeatedly 1537 /// to allow processing of other events. 1538 1539 addPointsToTrack(track.id, track.points, coordIdx, numPointsToPassAtOnce); 1540 } 1541 1542 QDateTime t2 = QDateTime::currentDateTime(); 1543 qCDebug(DIGIKAM_GEOIFACE_LOG) << track.url.fileName() << t1.msecsTo(t2); 1544 } 1545 } 1546 } 1547 1548 void BackendGoogleMaps::addPointsToTrack(const quint64 trackId, TrackManager::TrackPoint::List const& track, const int firstPoint, const int nPoints) 1549 { 1550 QString json; 1551 QTextStream jsonBuilder(&json); 1552 jsonBuilder << '['; 1553 int lastPoint = track.count()-1; 1554 1555 if (nPoints > 0) 1556 { 1557 lastPoint = qMin(firstPoint + nPoints - 1, track.count()-1); 1558 } 1559 1560 for (int coordIdx = firstPoint ; coordIdx <= lastPoint ; ++coordIdx) 1561 { 1562 GeoCoordinates const& coordinates = track.at(coordIdx).coordinates; 1563 1564 if (coordIdx > firstPoint) 1565 { 1566 jsonBuilder << ','; 1567 } 1568 1569 /// @TODO This looks like a lot of text to parse. Is there a more compact way? 1570 1571 jsonBuilder << "{\"lat\":" << coordinates.latString() << "," 1572 << "\"lon\":" << coordinates.lonString() << "}"; 1573 } 1574 1575 jsonBuilder << ']'; 1576 const QString addTrackScript = QString::fromLatin1("kgeomapAddToTrack(%1,'%2');").arg(trackId).arg(json); 1577 d->htmlWidget->runScript(addTrackScript); 1578 } 1579 1580 void BackendGoogleMaps::slotTrackVisibilityChanged(const bool newState) 1581 { 1582 /// @TODO Now we remove all tracks and re-add them on visibility change. 1583 /// This is very slow. 1584 1585 if (newState) 1586 { 1587 // store all tracks which are already in the manager as changed 1588 1589 const TrackManager::Track::List trackList = s->trackManager->getTrackList(); 1590 QList<TrackManager::TrackChanges> trackChanges; 1591 1592 Q_FOREACH (const TrackManager::Track& t, trackList) 1593 { 1594 trackChanges << TrackManager::TrackChanges(t.id, TrackManager::ChangeAdd); 1595 } 1596 1597 slotTracksChanged(trackChanges); 1598 } 1599 else if (d->htmlWidget) 1600 { 1601 const QVariant successClear = d->htmlWidget->runScript(QString::fromLatin1("kgeomapClearTracks();"), false); 1602 } 1603 } 1604 1605 void BackendGoogleMaps::slotMessageEvent(const QString& /*message*/) 1606 { 1607 /* 1608 qCDebug(DIGIKAM_GEOIFACE_LOG) << "Javascript Message:" << message; 1609 */ 1610 } 1611 1612 #ifdef HAVE_GEOLOCATION 1613 1614 void BackendGoogleMaps::centerOn(const Marble::GeoDataLatLonBox& latLonBox, const bool useSaneZoomLevel) 1615 { 1616 /// @todo Buffer this call if there is no widget or if inactive! 1617 1618 if (!d->htmlWidget) 1619 { 1620 return; 1621 } 1622 1623 const qreal boxWest = latLonBox.west(Marble::GeoDataCoordinates::Degree); 1624 const qreal boxNorth = latLonBox.north(Marble::GeoDataCoordinates::Degree); 1625 const qreal boxEast = latLonBox.east(Marble::GeoDataCoordinates::Degree); 1626 const qreal boxSouth = latLonBox.south(Marble::GeoDataCoordinates::Degree); 1627 1628 d->htmlWidget->centerOn(boxWest, boxNorth, boxEast, boxSouth, useSaneZoomLevel); 1629 qCDebug(DIGIKAM_GEOIFACE_LOG) << getZoom(); 1630 } 1631 1632 #endif 1633 1634 } // namespace Digikam 1635 1636 #include "moc_backendgooglemaps.cpp"