File indexing completed on 2025-03-09 03:57:15

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-12-08
0007  * Description : Marble-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 "backendmarble.h"
0018 
0019 // Qt includes
0020 
0021 #include <QMenu>
0022 #include <QMouseEvent>
0023 #include <QPointer>
0024 #include <QAction>
0025 #include <QActionGroup>
0026 #include <QStandardItemModel>
0027 
0028 // KDE includes
0029 
0030 #include <kconfiggroup.h>
0031 #include <klocalizedstring.h>
0032 
0033 // Marble includes
0034 
0035 #include "GeoDataLinearRing.h"
0036 #include "GeoDataLatLonAltBox.h"
0037 #include "GeoPainter.h"
0038 #include "MarbleMap.h"
0039 #include "MarbleDirs.h"
0040 #include "MarbleWidget.h"
0041 #include "ViewportParams.h"
0042 #include "AbstractFloatItem.h"
0043 #include "MarbleWidgetPopupMenu.h"
0044 #include "MapThemeManager.h"
0045 
0046 // Local includes
0047 
0048 #include "digikam_debug.h"
0049 #include "digikam_config.h"
0050 #include "backendmarblelayer.h"
0051 #include "abstractmarkertiler.h"
0052 #include "mapwidget.h"
0053 #include "geomodelhelper.h"
0054 
0055 namespace Digikam
0056 {
0057 
0058 class Q_DECL_HIDDEN BMInternalWidgetInfo
0059 {
0060 public:
0061 
0062     BMInternalWidgetInfo() = default;
0063 
0064     BackendMarbleLayer* bmLayer = nullptr;
0065 };
0066 
0067 } // namespace Digikam
0068 
0069 Q_DECLARE_METATYPE(Digikam::BMInternalWidgetInfo)
0070 
0071 namespace Digikam
0072 {
0073 
0074 class Q_DECL_HIDDEN BackendMarble::Private
0075 {
0076 public:
0077 
0078     explicit Private()
0079       : marbleWidget                    (nullptr),
0080         actionGroupMapTheme             (nullptr),
0081         actionGroupProjection           (nullptr),
0082         actionGroupFloatItems           (nullptr),
0083         actionShowCompass               (nullptr),
0084         actionShowScaleBar              (nullptr),
0085         actionShowNavigation            (nullptr),
0086         actionShowOverviewMap           (nullptr),
0087         cacheMapTheme                   (QLatin1String("earth/openstreetmap/openstreetmap.dgml")),
0088         cacheProjection                 (QLatin1String("spherical")),
0089         cacheShowCompass                (false),
0090         cacheShowScaleBar               (false),
0091         cacheShowNavigation             (true),
0092         cacheShowOverviewMap            (false),
0093         cacheZoom                       (900),
0094         havePotentiallyMouseMovingObject(false),
0095         haveMouseMovingObject           (false),
0096         mouseMoveClusterIndex           (-1),
0097         mouseMoveMarkerIndex            (),
0098         mouseMoveObjectCoordinates      (),
0099         mouseMoveCenterOffset           (0, 0),
0100         dragDropMarkerCount             (0),
0101         dragDropMarkerPos               (),
0102         clustersDirtyCacheProjection    (),
0103         clustersDirtyCacheLat           (),
0104         clustersDirtyCacheLon           (),
0105         displayedRectangle              (),
0106         firstSelectionScreenPoint       (),
0107         firstSelectionPoint             (),
0108         activeState                     (false),
0109         widgetIsDocked                  (false),
0110         blockingZoomWhileChangingTheme  (false),
0111         trackCache                      (),
0112         bmLayer                         (nullptr),
0113         marbleMapThemeManager           (nullptr)
0114     {
0115     }
0116 
0117     QPointer<Marble::MarbleWidget>            marbleWidget;
0118 
0119     QActionGroup*                             actionGroupMapTheme;
0120     QActionGroup*                             actionGroupProjection;
0121     QActionGroup*                             actionGroupFloatItems;
0122     QAction*                                  actionShowCompass;
0123     QAction*                                  actionShowScaleBar;
0124     QAction*                                  actionShowNavigation;
0125     QAction*                                  actionShowOverviewMap;
0126 
0127     QString                                   cacheMapTheme;
0128     QString                                   cacheProjection;
0129     bool                                      cacheShowCompass;
0130     bool                                      cacheShowScaleBar;
0131     bool                                      cacheShowNavigation;
0132     bool                                      cacheShowOverviewMap;
0133     int                                       cacheZoom;
0134     bool                                      havePotentiallyMouseMovingObject;
0135     bool                                      haveMouseMovingObject;
0136     int                                       mouseMoveClusterIndex;
0137     QPersistentModelIndex                     mouseMoveMarkerIndex;
0138     GeoCoordinates                            mouseMoveObjectCoordinates;
0139     QPoint                                    mouseMoveCenterOffset;
0140     int                                       dragDropMarkerCount;
0141     QPoint                                    dragDropMarkerPos;
0142     int                                       clustersDirtyCacheProjection;
0143     qreal                                     clustersDirtyCacheLat;
0144     qreal                                     clustersDirtyCacheLon;
0145     QStringList                               mainMarbleThemes;
0146 
0147     GeoCoordinates::Pair                      displayedRectangle;
0148     QPoint                                    firstSelectionScreenPoint;
0149     QPoint                                    intermediateSelectionScreenPoint;
0150     GeoCoordinates                            firstSelectionPoint;
0151     GeoCoordinates                            intermediateSelectionPoint;
0152     bool                                      activeState;
0153     bool                                      widgetIsDocked;
0154     bool                                      blockingZoomWhileChangingTheme;
0155 
0156     QHash<quint64, Marble::GeoDataLineString> trackCache;
0157 
0158     BackendMarbleLayer*                       bmLayer;
0159     Marble::MapThemeManager*                  marbleMapThemeManager;
0160 };
0161 
0162 BackendMarble::BackendMarble(const QExplicitlySharedDataPointer<GeoIfaceSharedData>& sharedData,
0163                              QObject* const parent)
0164     : MapBackend(sharedData, parent),
0165       d         (new Private())
0166 {
0167     d->marbleMapThemeManager = new Marble::MapThemeManager(this);
0168     this->createActions();
0169 }
0170 
0171 BackendMarble::~BackendMarble()
0172 {
0173     /// @todo Should we leave our widget in this list and not destroy it?
0174 
0175     GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance();
0176     go->removeMyInternalWidgetFromPool(this);
0177 
0178     if (d->marbleWidget)
0179     {
0180         d->marbleWidget->removeLayer(d->bmLayer);
0181 
0182         delete d->marbleWidget;
0183     }
0184 
0185     delete d;
0186 }
0187 
0188 QString BackendMarble::backendName() const
0189 {
0190     return QLatin1String("marble");
0191 }
0192 
0193 QString BackendMarble::backendHumanName() const
0194 {
0195     return i18n("Marble Virtual Globe");
0196 }
0197 
0198 QWidget* BackendMarble::mapWidget()
0199 {
0200     if (!d->marbleWidget)
0201     {
0202         GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance();
0203 
0204         GeoIfaceInternalWidgetInfo info;
0205 
0206         if (go->getInternalWidgetFromPool(this, &info))
0207         {
0208             d->marbleWidget                    = qobject_cast<Marble::MarbleWidget*>(info.widget);
0209             const BMInternalWidgetInfo intInfo = info.backendData.value<BMInternalWidgetInfo>();
0210             d->bmLayer                         = intInfo.bmLayer;
0211 
0212             if (d->bmLayer)
0213             {
0214                 d->bmLayer->setBackend(this);
0215             }
0216             else
0217             {
0218                 qCWarning(DIGIKAM_GEOIFACE_LOG) << "Marble widget instance is null!";
0219             }
0220         }
0221         else
0222         {
0223             d->marbleWidget = new Marble::MarbleWidget();
0224             d->bmLayer      = new BackendMarbleLayer(this);
0225 
0226             d->marbleWidget->addLayer(d->bmLayer);
0227         }
0228 
0229         d->marbleWidget->installEventFilter(this);
0230 
0231         connect(d->marbleWidget, SIGNAL(zoomChanged(int)),
0232                 this, SLOT(slotMarbleZoomChanged()));
0233 
0234         // set a backend first
0235         /// @todo Do this only if we are set active!
0236 
0237         applyCacheToWidget();
0238 
0239         Q_EMIT signalBackendReadyChanged(backendName());
0240     }
0241 
0242     return d->marbleWidget;
0243 }
0244 
0245 void BackendMarble::releaseWidget(GeoIfaceInternalWidgetInfo* const info)
0246 {
0247     info->widget->removeEventFilter(this);
0248 
0249     BMInternalWidgetInfo intInfo = info->backendData.value<BMInternalWidgetInfo>();
0250 
0251     if (intInfo.bmLayer)
0252     {
0253         intInfo.bmLayer->setBackend(nullptr);
0254     }
0255 
0256     disconnect(d->marbleWidget, SIGNAL(zoomChanged(int)),
0257                this, SLOT(slotMarbleZoomChanged()));
0258 
0259     info->currentOwner = nullptr;
0260     info->state        = GeoIfaceInternalWidgetInfo::InternalWidgetReleased;
0261     d->marbleWidget    = nullptr;
0262     d->bmLayer         = nullptr;
0263 
0264     Q_EMIT signalBackendReadyChanged(backendName());
0265 }
0266 
0267 GeoCoordinates BackendMarble::getCenter() const
0268 {
0269     if (!d->marbleWidget)
0270     {
0271         return GeoCoordinates();
0272     }
0273 
0274     return GeoCoordinates(d->marbleWidget->centerLatitude(), d->marbleWidget->centerLongitude());
0275 }
0276 
0277 void BackendMarble::setCenter(const GeoCoordinates& coordinate)
0278 {
0279     if (!d->marbleWidget)
0280     {
0281         return;
0282     }
0283 
0284     d->marbleWidget->setCenterLatitude(coordinate.lat());
0285     d->marbleWidget->setCenterLongitude(coordinate.lon());
0286 }
0287 
0288 bool BackendMarble::isReady() const
0289 {
0290     return (d->marbleWidget != nullptr);
0291 }
0292 
0293 void BackendMarble::zoomIn()
0294 {
0295     if (!d->marbleWidget)
0296     {
0297         return;
0298     }
0299 
0300     d->marbleWidget->zoomIn();
0301     d->marbleWidget->repaint();
0302 }
0303 
0304 void BackendMarble::zoomOut()
0305 {
0306     if (!d->marbleWidget)
0307     {
0308         return;
0309     }
0310 
0311     d->marbleWidget->zoomOut();
0312     d->marbleWidget->repaint();
0313 }
0314 
0315 void BackendMarble::createActions()
0316 {
0317     // map theme:
0318 
0319     d->actionGroupMapTheme = new QActionGroup(this);
0320     d->actionGroupMapTheme->setExclusive(true);
0321 
0322     connect(d->actionGroupMapTheme, &QActionGroup::triggered,
0323             this, &BackendMarble::slotMapThemeActionTriggered);
0324 
0325     QList<QPair<QString, QString>> mainThemes;
0326     mainThemes.append( { i18n("Atlas map"),              QLatin1String("earth/srtm/srtm.dgml")                   } );
0327     mainThemes.append( { i18n("Satellite map"),          QLatin1String("earth/bluemarble/bluemarble.dgml")       } );
0328     mainThemes.append( { i18n("OpenStreetMap"),          QLatin1String("earth/openstreetmap/openstreetmap.dgml") } );
0329     mainThemes.append( { i18n("OpenStreetMap (Vector)"), QLatin1String("earth/vectorosm/vectorosm.dgml")         } );
0330 
0331     for (auto& theme : mainThemes)
0332     {
0333         QAction* const mapAction = new QAction(d->actionGroupMapTheme);
0334         mapAction->setCheckable(true);
0335         mapAction->setText(theme.first);
0336         mapAction->setData(theme.second);
0337         d->mainMarbleThemes.append(theme.second);
0338     }
0339 
0340     QStringList blackListedMarbleThemes
0341     {
0342         QLatin1String("earth/behaim1492/behaim1492.dgml"),
0343         QLatin1String("earth/citylights/citylights.dgml"),
0344         QLatin1String("earth/plain/plain.dgml"),
0345         QLatin1String("earth/political/political.dgml"),
0346         QLatin1String("earth/precip-dec/precip-dec.dgml"),
0347         QLatin1String("earth/precip-july/precip-july.dgml"),
0348         QLatin1String("earth/schagen1689/schagen1689.dgml"),
0349         QLatin1String("earth/openstreetmap/openstreetmap.dgml"),
0350         QLatin1String("earth/temp-dec/temp-dec.dgml"),
0351         QLatin1String("earth/temp-july/temp-july.dgml")
0352     };
0353 
0354     // add all available marble earth themes
0355 
0356     auto* themeModel = d->marbleMapThemeManager->mapThemeModel();
0357 
0358     for (int i = 0 ; i < themeModel->rowCount() ; ++i)
0359     {
0360         auto* item     = themeModel->item(i);
0361         auto themeId   = item->data(Qt::UserRole + 1).toString();
0362         auto themeName = item->data(Qt::DisplayRole).toString();
0363 
0364         if (d->mainMarbleThemes.contains(themeId)     ||
0365             blackListedMarbleThemes.contains(themeId) ||
0366             !themeId.startsWith(QLatin1String("earth/")))
0367         {
0368             continue;
0369         }
0370 
0371         QAction* const mapAction = new QAction(d->actionGroupMapTheme);
0372         mapAction->setCheckable(true);
0373         mapAction->setText(themeName);
0374         mapAction->setData(themeId);
0375     }
0376 
0377     // projection
0378 
0379     d->actionGroupProjection             = new QActionGroup(this);
0380     d->actionGroupProjection->setExclusive(true);
0381 
0382     connect(d->actionGroupProjection, &QActionGroup::triggered,
0383             this, &BackendMarble::slotProjectionActionTriggered);
0384 
0385     QAction* const actionSpherical       = new QAction(d->actionGroupProjection);
0386     actionSpherical->setCheckable(true);
0387     actionSpherical->setText(i18nc("Spherical projection", "Spherical"));
0388     actionSpherical->setData(QLatin1String("spherical"));
0389 
0390     QAction* const actionMercator        = new QAction(d->actionGroupProjection);
0391     actionMercator->setCheckable(true);
0392     actionMercator->setText(i18n("Mercator"));
0393     actionMercator->setData(QLatin1String("mercator"));
0394 
0395     QAction* const actionEquirectangular = new QAction(d->actionGroupProjection);
0396     actionEquirectangular->setCheckable(true);
0397     actionEquirectangular->setText(i18n("Equirectangular"));
0398     actionEquirectangular->setData(QLatin1String("equirectangular"));
0399 
0400     // float items
0401 
0402     d->actionGroupFloatItems = new QActionGroup(this);
0403     d->actionGroupFloatItems->setExclusive(false);
0404 
0405     connect(d->actionGroupFloatItems, &QActionGroup::triggered,
0406             this, &BackendMarble::slotFloatSettingsTriggered);
0407 
0408     d->actionShowCompass     = new QAction(i18n("Show compass"), d->actionGroupFloatItems);
0409     d->actionShowCompass->setData(QLatin1String("showcompass"));
0410     d->actionShowCompass->setCheckable(true);
0411     d->actionGroupFloatItems->addAction(d->actionShowCompass);
0412 
0413     d->actionShowScaleBar    = new QAction(i18n("Show scale bar"), d->actionGroupFloatItems);
0414     d->actionShowScaleBar->setData(QLatin1String("showscalebar"));
0415     d->actionShowScaleBar->setCheckable(true);
0416     d->actionGroupFloatItems->addAction(d->actionShowScaleBar);
0417 
0418     d->actionShowNavigation  = new QAction(i18n("Show navigation"), d->actionGroupFloatItems);
0419     d->actionShowNavigation->setData(QLatin1String("shownavigation"));
0420     d->actionShowNavigation->setCheckable(true);
0421     d->actionGroupFloatItems->addAction(d->actionShowNavigation);
0422 
0423     d->actionShowOverviewMap = new QAction(i18n("Show overview map"), d->actionGroupFloatItems);
0424     d->actionShowOverviewMap->setData(QLatin1String("showoverviewmap"));
0425     d->actionShowOverviewMap->setCheckable(true);
0426     d->actionGroupFloatItems->addAction(d->actionShowOverviewMap);
0427 }
0428 
0429 void BackendMarble::addActionsToConfigurationMenu(QMenu* const configurationMenu)
0430 {
0431     GEOIFACE_ASSERT(configurationMenu != nullptr);
0432 
0433     configurationMenu->addSeparator();
0434 
0435     const QList<QAction*> mapThemeActions = d->actionGroupMapTheme->actions();
0436     QMenu* const extraMarbleMenu          = new QMenu(i18n("Other Marble Themes"), configurationMenu);
0437 
0438     for (auto* action : mapThemeActions)
0439     {
0440         if (d->mainMarbleThemes.contains(action->data().toString()))
0441         {
0442             configurationMenu->addAction(action);
0443         }
0444         else
0445         {
0446             extraMarbleMenu->addAction(action);
0447         }
0448     }
0449 
0450     if (extraMarbleMenu->isEmpty())
0451     {
0452         delete extraMarbleMenu;
0453     }
0454     else
0455     {
0456         configurationMenu->addMenu(extraMarbleMenu);
0457     }
0458 
0459     configurationMenu->addSeparator();
0460 
0461     // TODO: we need a parent for this guy!
0462 
0463     QMenu* const projectionSubMenu          = new QMenu(i18n("Projection"), configurationMenu);
0464     configurationMenu->addMenu(projectionSubMenu);
0465     const QList<QAction*> projectionActions = d->actionGroupProjection->actions();
0466 
0467     for (int i = 0 ; i < projectionActions.count() ; ++i)
0468     {
0469         projectionSubMenu->addAction(projectionActions.at(i));
0470     }
0471 
0472     QMenu* const floatItemsSubMenu     = new QMenu(i18n("Float items"), configurationMenu);
0473     configurationMenu->addMenu(floatItemsSubMenu);
0474     const QList<QAction*> floatActions = d->actionGroupFloatItems->actions();
0475 
0476     for (int i = 0 ; i < floatActions.count() ; ++i)
0477     {
0478         floatItemsSubMenu->addAction(floatActions.at(i));
0479     }
0480 
0481     configurationMenu->addSeparator();
0482 
0483     addCommonOptions(configurationMenu);
0484 
0485     updateActionAvailability();
0486 }
0487 
0488 void BackendMarble::slotMapThemeActionTriggered(QAction* action)
0489 {
0490     setMapTheme(action->data().toString());
0491 }
0492 
0493 QString BackendMarble::getMapTheme() const
0494 {
0495     // TODO: read the theme from the marblewidget!
0496 
0497     return d->cacheMapTheme;
0498 }
0499 
0500 void BackendMarble::setMapTheme(const QString& newMapTheme)
0501 {
0502     // convert old ids to themeIds
0503 
0504     if      (newMapTheme == QLatin1String("atlas"))
0505     {
0506         d->cacheMapTheme = QLatin1String("earth/srtm/srtm.dgml");
0507     }
0508     else if (newMapTheme == QLatin1String("satellite"))
0509     {
0510         d->cacheMapTheme = QLatin1String("earth/bluemarble/bluemarble.dgml");
0511     }
0512     else if (newMapTheme == QLatin1String("openstreetmap"))
0513     {
0514         d->cacheMapTheme = QLatin1String("earth/openstreetmap/openstreetmap.dgml");
0515     }
0516     else if (!d->marbleMapThemeManager->mapThemeIds().contains(newMapTheme))
0517     {
0518         // fall back to atlas
0519         d->cacheMapTheme = QLatin1String("earth/srtm/srtm.dgml");
0520     }
0521     else
0522     {
0523         d->cacheMapTheme = newMapTheme;
0524     }
0525 
0526     if (!d->marbleWidget)
0527     {
0528         return;
0529     }
0530 
0531     // Changing the map theme changes the zoom - we want to try to keep the zoom constant
0532 
0533     d->blockingZoomWhileChangingTheme = true;
0534 
0535     // Remember the zoom from the cache. The zoom of the widget may not have been set yet!
0536 
0537     const int oldMarbleZoom           = d->cacheZoom;
0538 
0539     d->marbleWidget->setMapThemeId(d->cacheMapTheme);
0540 
0541     // the float items are reset when the theme is changed:
0542 
0543     setShowCompass(d->cacheShowCompass);
0544     setShowScaleBar(d->cacheShowScaleBar);
0545     setShowNavigation(d->cacheShowNavigation);
0546     setShowOverviewMap(d->cacheShowOverviewMap);
0547 
0548     // make sure the zoom level is within the allowed range
0549 
0550     int targetZoomLevel = oldMarbleZoom;
0551 
0552     if      (oldMarbleZoom > d->marbleWidget->maximumZoom())
0553     {
0554         targetZoomLevel = d->marbleWidget->maximumZoom();
0555     }
0556     else if (oldMarbleZoom < d->marbleWidget->minimumZoom())
0557     {
0558         targetZoomLevel = d->marbleWidget->minimumZoom();
0559     }
0560 
0561     if (targetZoomLevel != oldMarbleZoom)
0562     {
0563         // our zoom level had to be adjusted, therefore unblock
0564         // the signal now to allow the change to propagate
0565 
0566         d->blockingZoomWhileChangingTheme = false;
0567     }
0568 
0569     d->marbleWidget->zoomView(targetZoomLevel);
0570     d->blockingZoomWhileChangingTheme = false;
0571 
0572     updateActionAvailability();
0573 }
0574 
0575 void BackendMarble::saveSettingsToGroup(KConfigGroup* const group)
0576 {
0577     GEOIFACE_ASSERT(group != nullptr);
0578 
0579     if (!group)
0580     {
0581         return;
0582     }
0583 
0584     group->writeEntry("Marble Map Theme",         d->cacheMapTheme);
0585     group->writeEntry("Marble Projection",        d->cacheProjection);
0586     group->writeEntry("Marble Show Compass",      d->cacheShowCompass);
0587     group->writeEntry("Marble Show Scale Bar",    d->cacheShowScaleBar);
0588     group->writeEntry("Marble Show Navigation",   d->cacheShowNavigation);
0589     group->writeEntry("Marble Show Overview Map", d->cacheShowOverviewMap);
0590 }
0591 
0592 void BackendMarble::readSettingsFromGroup(const KConfigGroup* const group)
0593 {
0594     GEOIFACE_ASSERT(group != nullptr);
0595 
0596     if (!group)
0597     {
0598         return;
0599     }
0600 
0601     setMapTheme(group->readEntry("Marble Map Theme",                d->cacheMapTheme));
0602     setProjection(group->readEntry("Marble Projection",             d->cacheProjection));
0603     setShowCompass(group->readEntry("Marble Show Compass",          d->cacheShowCompass));
0604     setShowScaleBar(group->readEntry("Marble Show Scale Bar",       d->cacheShowScaleBar));
0605     setShowNavigation(group->readEntry("Marble Show Navigation",    d->cacheShowNavigation));
0606     setShowOverviewMap(group->readEntry("Marble Show Overview Map", d->cacheShowOverviewMap));
0607 }
0608 
0609 void BackendMarble::updateMarkers()
0610 {
0611     // just redraw, that's it:
0612 
0613     reload();
0614 }
0615 
0616 void BackendMarble::reload()
0617 {
0618     if (!d->marbleWidget)
0619     {
0620         return;
0621     }
0622 
0623     d->marbleWidget->update();
0624 }
0625 
0626 bool BackendMarble::screenCoordinates(const GeoCoordinates& coordinates, QPoint* const point)
0627 {
0628     if (!d->marbleWidget)
0629     {
0630         return false;
0631     }
0632 
0633     if (!coordinates.hasCoordinates())
0634     {
0635         return false;
0636     }
0637 
0638     qreal x, y;
0639     const bool isVisible = d->marbleWidget->screenCoordinates(coordinates.lon(), coordinates.lat(), x, y);
0640 
0641     if (!isVisible)
0642     {
0643         return false;
0644     }
0645 
0646     if (point)
0647     {
0648         *point = QPoint(x, y);
0649     }
0650 
0651     return true;
0652 }
0653 
0654 bool BackendMarble::geoCoordinates(const QPoint& point, GeoCoordinates* const coordinates) const
0655 {
0656     if (!d->marbleWidget)
0657     {
0658         return false;
0659     }
0660 
0661     // apparently, MarbleWidget::GeoCoordinates can return true even if the object is not on the screen
0662     // check that the point is in the visible range:
0663 
0664     if (!d->marbleWidget->rect().contains(point))
0665     {
0666         return false;
0667     }
0668 
0669     qreal lat, lon;
0670     const bool isVisible = d->marbleWidget->geoCoordinates(point.x(), point.y(),
0671                                                            lon, lat,
0672                                                            Marble::GeoDataCoordinates::Degree);
0673 
0674     if (!isVisible)
0675     {
0676         return false;
0677     }
0678 
0679     if (coordinates)
0680     {
0681         *coordinates = GeoCoordinates(lat, lon);
0682     }
0683 
0684     return true;
0685 }
0686 
0687 /**
0688  * @brief Replacement for Marble::GeoPainter::drawPixmap which takes a pixel offset
0689  *
0690  * @param painter Marble::GeoPainter on which to draw the pixmap
0691  * @param pixmap Pixmap to be drawn
0692  * @param coordinates GeoCoordinates where the image is to be drawn
0693  * @param offsetPoint Point in the @p pixmap which should be at @p coordinates
0694  */
0695 void BackendMarble::GeoPainter_drawPixmapAtCoordinates(Marble::GeoPainter* const painter,
0696                                                        const QPixmap& pixmap,
0697                                                        const GeoCoordinates& coordinates,
0698                                                        const QPoint& offsetPoint)
0699 {
0700     // base point starts at the top left of the pixmap
0701 
0702     // try to convert the coordinates to pixels
0703 
0704     QPoint pointOnScreen;
0705 
0706     if (!screenCoordinates(coordinates, &pointOnScreen))
0707     {
0708         return;
0709     }
0710 
0711     // Marble::GeoPainter::drawPixmap(coordinates, pixmap) draws the pixmap centered on coordinates
0712     // therefore we calculate the pixel position of the center of the image if its offsetPoint
0713     // is to be at pointOnScreen:
0714 
0715     const QSize pixmapSize      = pixmap.size();
0716     const QPoint pixmapHalfSize = QPoint(pixmapSize.width() / 2, pixmapSize.height() / 2);
0717     const QPoint drawPoint      = pointOnScreen + pixmapHalfSize - offsetPoint;
0718 
0719     // now re-calculate the coordinates of the new pixel coordinates:
0720 
0721     GeoCoordinates drawGeoCoordinates;
0722 
0723     if (!geoCoordinates(drawPoint, &drawGeoCoordinates))
0724     {
0725         return;
0726     }
0727 
0728     // convert to Marble datatype and draw:
0729 
0730     const Marble::GeoDataCoordinates mcoord = drawGeoCoordinates.toMarbleCoordinates();
0731     painter->drawPixmap(mcoord, pixmap);
0732 }
0733 
0734 void BackendMarble::marbleCustomPaint(Marble::GeoPainter* painter)
0735 {
0736     if (!d->activeState)
0737     {
0738         return;
0739     }
0740 
0741     // check whether the parameters of the map changed and we may have to update the clusters:
0742 
0743     if ((d->clustersDirtyCacheLat        != d->marbleWidget->centerLatitude())  ||
0744         (d->clustersDirtyCacheLon        != d->marbleWidget->centerLongitude()) ||
0745         (d->clustersDirtyCacheProjection != d->marbleWidget->projection()))
0746     {
0747 /*
0748         qCDebug(DIGIKAM_GEOIFACE_LOG) << d->marbleWidget->centerLatitude()
0749                                          << d->marbleWidget->centerLongitude()
0750                                          << d->marbleWidget->projection();
0751 */
0752         d->clustersDirtyCacheLat        = d->marbleWidget->centerLatitude();
0753         d->clustersDirtyCacheLon        = d->marbleWidget->centerLongitude();
0754         d->clustersDirtyCacheProjection = d->marbleWidget->projection();
0755         s->worldMapWidget->markClustersAsDirty();
0756     }
0757 
0758     painter->save();
0759 
0760     if (s->trackManager)
0761     {
0762         if (s->trackManager->getVisibility())
0763         {
0764             TrackManager::Track::List const& tracks = s->trackManager->getTrackList();
0765 
0766             for (int trackIdx = 0 ; trackIdx < tracks.count() ; ++trackIdx)
0767             {
0768                 TrackManager::Track const& track = tracks.at(trackIdx);
0769 
0770                 if (track.points.count() < 2)
0771                 {
0772                     continue;
0773                 }
0774 
0775                 Marble::GeoDataLineString lineString;
0776 
0777                 if (d->trackCache.contains(track.id))
0778                 {
0779                     lineString = d->trackCache.value(track.id);
0780                 }
0781                 else
0782                 {
0783                     for (int coordIdx = 0 ; coordIdx < track.points.count() ; ++coordIdx)
0784                     {
0785                         GeoCoordinates const& coordinates                  = track.points.at(coordIdx).coordinates;
0786                         const Marble::GeoDataCoordinates marbleCoordinates = coordinates.toMarbleCoordinates();
0787                         lineString << marbleCoordinates;
0788                     }
0789 
0790                     d->trackCache.insert(track.id, lineString);
0791                 }
0792 
0793                 /// @TODO looks a bit too thick IMHO when you zoom out.
0794                 ///       Maybe adjust to zoom level?
0795 
0796                 QColor trackColor = track.color;
0797                 trackColor.setAlpha(180);
0798                 painter->setPen(QPen(QBrush(trackColor),5));
0799                 painter->drawPolyline(lineString);
0800             }
0801         }
0802     }
0803 
0804     for (int i = 0 ; i < s->ungroupedModels.count() ; ++i)
0805     {
0806         GeoModelHelper* const modelHelper = s->ungroupedModels.at(i);
0807 
0808         if (!modelHelper->modelFlags().testFlag(GeoModelHelper::FlagVisible))
0809         {
0810             continue;
0811         }
0812 
0813         QAbstractItemModel* const model   = modelHelper->model();
0814 
0815         // render all visible markers:
0816 
0817         for (int row = 0 ; row < model->rowCount() ; ++row)
0818         {
0819             const QModelIndex currentIndex = model->index(row, 0);
0820             GeoCoordinates markerCoordinates;
0821 
0822             if (!modelHelper->itemCoordinates(currentIndex, &markerCoordinates))
0823             {
0824                 continue;
0825             }
0826 
0827             // is the marker being moved right now?
0828 
0829             if (currentIndex == d->mouseMoveMarkerIndex)
0830             {
0831                 markerCoordinates = d->mouseMoveObjectCoordinates;
0832             }
0833 
0834             QPoint markerPoint;
0835 
0836             if (!screenCoordinates(markerCoordinates, &markerPoint))
0837             {
0838                 /// @todo This check does not work properly in all cases!
0839                 // the marker is not visible
0840 
0841                 continue;
0842             }
0843 
0844             QPoint markerOffsetPoint;
0845             QPixmap markerPixmap;
0846             const bool haveMarkerPixmap = modelHelper->itemIcon(currentIndex,
0847                                                                 &markerOffsetPoint, nullptr,
0848                                                                 &markerPixmap, nullptr);
0849 
0850             if (!haveMarkerPixmap || markerPixmap.isNull())
0851             {
0852                 markerPixmap      = GeoIfaceGlobalObject::instance()->getStandardMarkerPixmap();
0853                 markerOffsetPoint = QPoint(markerPixmap.width() / 2, markerPixmap.height() - 1);
0854             }
0855 
0856             GeoPainter_drawPixmapAtCoordinates(painter, markerPixmap, markerCoordinates, markerOffsetPoint);
0857         }
0858     }
0859 
0860     int markersInMovingCluster = 0;
0861 
0862     if (s->markerModel)
0863     {
0864         // now for the clusters:
0865 
0866         s->worldMapWidget->updateClusters();
0867 
0868         for (int i = 0 ; i < s->clusterList.size() ; ++i)
0869         {
0870             const GeoIfaceCluster& cluster       = s->clusterList.at(i);
0871             GeoCoordinates clusterCoordinates    = cluster.coordinates;
0872             int markerCountOverride              = cluster.markerCount;
0873             GeoGroupState selectionStateOverride = cluster.groupState;
0874 
0875             if (d->haveMouseMovingObject && (d->mouseMoveClusterIndex >= 0))
0876             {
0877                 bool movingSelectedMarkers = s->clusterList.at(d->mouseMoveClusterIndex).groupState != SelectedNone;
0878 
0879                 if      (movingSelectedMarkers)
0880                 {
0881                     markersInMovingCluster += cluster.markerSelectedCount;
0882                     markerCountOverride    -= cluster.markerSelectedCount;
0883                     selectionStateOverride  = SelectedNone;
0884                 }
0885                 else if (d->mouseMoveClusterIndex == i)
0886                 {
0887                     markerCountOverride = 0;
0888                 }
0889 
0890                 if (markerCountOverride == 0)
0891                 {
0892                     continue;
0893                 }
0894             }
0895 
0896             QPoint clusterPoint;
0897 
0898             if (!screenCoordinates(clusterCoordinates, &clusterPoint))
0899             {
0900                 /// @todo This check does not work properly in all cases!
0901                 // cluster is not visible
0902 
0903                 continue;
0904             }
0905 
0906             QPoint clusterOffsetPoint;
0907             const QPixmap clusterPixmap = s->worldMapWidget->getDecoratedPixmapForCluster(i,
0908                                                 &selectionStateOverride,
0909                                                 &markerCountOverride,
0910                                                 &clusterOffsetPoint);
0911 
0912             GeoPainter_drawPixmapAtCoordinates(painter,
0913                                                clusterPixmap,
0914                                                clusterCoordinates,
0915                                                clusterOffsetPoint);
0916         }
0917     }
0918 
0919     // now render the mouse-moving cluster, if there is one:
0920 
0921     if (d->haveMouseMovingObject && (d->mouseMoveClusterIndex >= 0))
0922     {
0923         const GeoIfaceCluster& cluster       = s->clusterList.at(d->mouseMoveClusterIndex);
0924         GeoCoordinates clusterCoordinates    = d->mouseMoveObjectCoordinates;
0925         int markerCountOverride              = (markersInMovingCluster>0)?markersInMovingCluster:cluster.markerCount;
0926         GeoGroupState selectionStateOverride = cluster.groupState;
0927         QPoint clusterPoint;
0928 
0929         if (screenCoordinates(clusterCoordinates, &clusterPoint))
0930         {
0931             // determine the colors:
0932 
0933             QColor       fillColor;
0934             QColor       strokeColor;
0935             Qt::PenStyle strokeStyle;
0936             QColor       labelColor;
0937             QString      labelText;
0938             s->worldMapWidget->getColorInfos(d->mouseMoveClusterIndex,
0939                                              &fillColor, &strokeColor,
0940                                              &strokeStyle, &labelText,
0941                                              &labelColor, &selectionStateOverride,
0942                                              &markerCountOverride);
0943 
0944             QString pixmapName = fillColor.name().mid(1);
0945 
0946             if (cluster.groupState == SelectedAll)
0947             {
0948                 pixmapName += QLatin1String("-selected");
0949             }
0950 
0951             if (cluster.groupState == SelectedSome)
0952             {
0953                 pixmapName += QLatin1String("-someselected");
0954             }
0955 
0956             const QPixmap& markerPixmap = GeoIfaceGlobalObject::instance()->getMarkerPixmap(pixmapName);
0957             painter->drawPixmap(clusterPoint.x() - markerPixmap.width()  / 2,
0958                                 clusterPoint.y() - markerPixmap.height() - 1,
0959                                 markerPixmap);
0960         }
0961     }
0962 
0963     // now render the drag-and-drop marker, if there is one:
0964 
0965     if (d->dragDropMarkerCount>0)
0966     {
0967         // determine the colors:
0968 
0969         QColor       fillColor;
0970         QColor       strokeColor;
0971         Qt::PenStyle strokeStyle;
0972         QColor       labelColor;
0973         QString      labelText;
0974         s->worldMapWidget->getColorInfos(SelectedAll, d->dragDropMarkerCount,
0975                                          &fillColor, &strokeColor,
0976                                          &strokeStyle, &labelText, &labelColor);
0977 
0978         QString pixmapName          = fillColor.name().mid(1);
0979         pixmapName                 += QLatin1String("-selected");
0980         const QPixmap& markerPixmap = GeoIfaceGlobalObject::instance()->getMarkerPixmap(pixmapName);
0981 
0982         painter->drawPixmap(d->dragDropMarkerPos.x() - markerPixmap.width()  / 2,
0983                             d->dragDropMarkerPos.y() - markerPixmap.height() - 1,
0984                             markerPixmap);
0985     }
0986 
0987     // here we draw the selection rectangle which is being made by the user right now
0988     /// @todo merge drawing of the rectangles into one function
0989 
0990     if (d->displayedRectangle.first.hasCoordinates())
0991     {
0992         drawSearchRectangle(painter, d->displayedRectangle, false);
0993     }
0994 
0995     // draw the current or old search rectangle
0996 
0997     if (s->selectionRectangle.first.hasCoordinates())
0998     {
0999         drawSearchRectangle(painter, s->selectionRectangle,
1000                             d->intermediateSelectionPoint.hasCoordinates());
1001     }
1002 
1003     painter->restore();
1004 }
1005 
1006 QString BackendMarble::getProjection() const
1007 {
1008     /// @todo Do we actually need to read out the projection from the widget???
1009 
1010     if (d->marbleWidget)
1011     {
1012         const Marble::Projection currentProjection = d->marbleWidget->projection();
1013 
1014         switch (currentProjection)
1015         {
1016             case Marble::Equirectangular:
1017                 d->cacheProjection = QLatin1String("equirectangular");
1018                 break;
1019 
1020             case Marble::Mercator:
1021                 d->cacheProjection = QLatin1String("mercator");
1022                 break;
1023 
1024             default:
1025             case Marble::Spherical:
1026                 d->cacheProjection = QLatin1String("spherical");
1027                 break;
1028         }
1029     }
1030 
1031     return d->cacheProjection;
1032 }
1033 
1034 void BackendMarble::setProjection(const QString& newProjection)
1035 {
1036     d->cacheProjection = newProjection;
1037 
1038     if (d->marbleWidget)
1039     {
1040         if      (newProjection == QLatin1String("equirectangular"))
1041         {
1042             d->marbleWidget->setProjection(Marble::Equirectangular);
1043         }
1044         else if (newProjection == QLatin1String("mercator"))
1045         {
1046             d->marbleWidget->setProjection(Marble::Mercator);
1047         }
1048         else // "spherical"
1049         {
1050             d->marbleWidget->setProjection(Marble::Spherical);
1051         }
1052     }
1053 
1054     updateActionAvailability();
1055 }
1056 
1057 void BackendMarble::slotProjectionActionTriggered(QAction* action)
1058 {
1059     setProjection(action->data().toString());
1060 }
1061 
1062 void BackendMarble::setShowCompass(const bool state)
1063 {
1064     d->cacheShowCompass = state;
1065     updateActionAvailability();
1066 
1067     if (d->marbleWidget)
1068     {
1069         Marble::AbstractFloatItem* const item = d->marbleWidget->floatItem(QLatin1String("compass"));
1070 
1071         if (item)
1072         {
1073             item->setVisible(state);
1074         }
1075     }
1076 }
1077 
1078 void BackendMarble::setShowScaleBar(const bool state)
1079 {
1080     d->cacheShowScaleBar = state;
1081     updateActionAvailability();
1082 
1083     if (d->marbleWidget)
1084     {
1085         Marble::AbstractFloatItem* const item = d->marbleWidget->floatItem(QLatin1String("scalebar"));
1086 
1087         if (item)
1088         {
1089             item->setVisible(state);
1090         }
1091     }
1092 }
1093 
1094 void BackendMarble::setShowNavigation(const bool state)
1095 {
1096     d->cacheShowNavigation = state;
1097     updateActionAvailability();
1098 
1099     if (d->marbleWidget)
1100     {
1101         Marble::AbstractFloatItem* const item = d->marbleWidget->floatItem(QLatin1String("navigation"));
1102 
1103         if (item)
1104         {
1105             item->setVisible(state);
1106         }
1107     }
1108 }
1109 
1110 void BackendMarble::setShowOverviewMap(const bool state)
1111 {
1112     d->cacheShowOverviewMap = state;
1113     updateActionAvailability();
1114 
1115     if (d->marbleWidget)
1116     {
1117         Marble::AbstractFloatItem* const item = d->marbleWidget->floatItem(QLatin1String("overviewmap"));
1118 
1119         if (item)
1120         {
1121             item->setVisible(state);
1122         }
1123     }
1124 }
1125 
1126 void BackendMarble::slotFloatSettingsTriggered(QAction* action)
1127 {
1128     const QString actionIdString = action->data().toString();
1129     const bool actionState       = action->isChecked();
1130 
1131     if      (actionIdString == QLatin1String("showcompass"))
1132     {
1133         setShowCompass(actionState);
1134     }
1135     else if (actionIdString == QLatin1String("showscalebar"))
1136     {
1137         setShowScaleBar(actionState);
1138     }
1139     else if (actionIdString == QLatin1String("shownavigation"))
1140     {
1141         setShowNavigation(actionState);
1142     }
1143     else if (actionIdString == QLatin1String("showoverviewmap"))
1144     {
1145         setShowOverviewMap(actionState);
1146     }
1147 }
1148 
1149 void BackendMarble::slotClustersNeedUpdating()
1150 {
1151     if (!d->marbleWidget)
1152     {
1153         return;
1154     }
1155 
1156     // tell the widget to redraw:
1157 
1158     d->marbleWidget->update();
1159 }
1160 
1161 void BackendMarble::updateClusters()
1162 {
1163     // clusters are only needed during redraw
1164 }
1165 
1166 QSize BackendMarble::mapSize() const
1167 {
1168     return d->marbleWidget->size();
1169 }
1170 
1171 void BackendMarble::slotMarbleZoomChanged()
1172 {
1173     // ignore the zoom change while changing the map theme
1174 
1175     if (d->blockingZoomWhileChangingTheme)
1176     {
1177         return;
1178     }
1179 
1180     const QString newZoomString = getZoom();
1181     s->worldMapWidget->markClustersAsDirty();
1182     updateActionAvailability();
1183 
1184     Q_EMIT signalZoomChanged(newZoomString);
1185 }
1186 
1187 void BackendMarble::setZoom(const QString& newZoom)
1188 {
1189     const QString myZoomString = s->worldMapWidget->convertZoomToBackendZoom(newZoom, QLatin1String("marble"));
1190 
1191     GEOIFACE_ASSERT(myZoomString.startsWith(QLatin1String("marble:")));
1192 
1193     const int myZoom           = myZoomString.mid(QString::fromLatin1("marble:").length()).toInt();
1194     d->cacheZoom               = myZoom;
1195     d->marbleWidget->setZoom(myZoom);
1196 }
1197 
1198 QString BackendMarble::getZoom() const
1199 {
1200     if (d->marbleWidget)
1201     {
1202         d->cacheZoom = d->marbleWidget->zoom();
1203     }
1204 
1205     return QString::fromLatin1("marble:%1").arg(d->cacheZoom);
1206 }
1207 
1208 int BackendMarble::getMarkerModelLevel()
1209 {
1210 /*
1211     return AbstractMarkerTiler::TileIndex::MaxLevel-1;
1212 */
1213     GEOIFACE_ASSERT(isReady());
1214 
1215     if (!isReady())
1216     {
1217         return 0;
1218     }
1219 
1220     const int currentZoom                      = d->marbleWidget->zoom();
1221     int tileLevel                              = 0;
1222     const Marble::Projection currentProjection = d->marbleWidget->projection();
1223 
1224     switch (currentProjection)
1225     {
1226         case Marble::Equirectangular:
1227 
1228             if      (currentZoom < 1000) { tileLevel = 4; }
1229             else if (currentZoom < 1400) { tileLevel = 5; }
1230             else if (currentZoom < 1900) { tileLevel = 6; }
1231             else if (currentZoom < 2300) { tileLevel = 7; }
1232             else if (currentZoom < 2800) { tileLevel = 8; }
1233             else                         { tileLevel = 9; }
1234 
1235             // note: level 9 is not enough starting at zoom level 3200
1236             break;
1237 
1238         case Marble::Mercator:
1239 
1240             if      (currentZoom < 1000) { tileLevel = 4; }
1241             else if (currentZoom < 1500) { tileLevel = 5; }
1242             else if (currentZoom < 1900) { tileLevel = 6; }
1243             else if (currentZoom < 2300) { tileLevel = 7; }
1244             else if (currentZoom < 2800) { tileLevel = 8; }
1245             else                         { tileLevel = 9; }
1246 
1247             // note: level 9 is not enough starting at zoom level 3200
1248             break;
1249 
1250         default:
1251         case Marble::Spherical:
1252 
1253             if      (currentZoom < 1300) { tileLevel = 5; }
1254             else if (currentZoom < 1800) { tileLevel = 6; }
1255             else if (currentZoom < 2200) { tileLevel = 7; }
1256             else if (currentZoom < 2800) { tileLevel = 8; }
1257             else                         { tileLevel = 9; }
1258 
1259             // note: level 9 is not enough starting at zoom level 3200
1260             break;
1261     }
1262 
1263     // TODO: verify that this assertion was too strict
1264 /*
1265     GEOIFACE_ASSERT(tileLevel <= AbstractMarkerTiler::TileIndex::MaxLevel-1);
1266 */
1267     return tileLevel;
1268 }
1269 
1270 GeoCoordinates::PairList BackendMarble::getNormalizedBounds()
1271 {
1272     if (!d->marbleWidget)
1273     {
1274         return GeoCoordinates::PairList();
1275     }
1276 
1277     const Marble::GeoDataLatLonAltBox marbleBounds = d->marbleWidget->viewport()->viewLatLonAltBox();
1278 /*
1279     qCDebug(DIGIKAM_GEOIFACE_LOG) << marbleBounds.toString(GeoDataCoordinates::Degree);
1280 */
1281     const GeoCoordinates::Pair boundsPair = GeoCoordinates::makePair(
1282             marbleBounds.south(Marble::GeoDataCoordinates::Degree),
1283             marbleBounds.west(Marble::GeoDataCoordinates::Degree),
1284             marbleBounds.north(Marble::GeoDataCoordinates::Degree),
1285             marbleBounds.east(Marble::GeoDataCoordinates::Degree));
1286 /*
1287     qCDebug(DIGIKAM_GEOIFACE_LOG) << boundsPair.first<<boundsPair.second;
1288     qCDebug(DIGIKAM_GEOIFACE_LOG) << GeoIfaceHelperNormalizeBounds(boundsPair);
1289 */
1290     return GeoIfaceHelperNormalizeBounds(boundsPair);
1291 }
1292 
1293 bool BackendMarble::eventFilter(QObject* object, QEvent* event)
1294 {
1295     if (object != d->marbleWidget)
1296     {
1297         // event not filtered, because it is not for our object
1298 
1299         return QObject::eventFilter(object, event);
1300     }
1301 
1302     // we only handle mouse events:
1303 
1304     if ((event->type() != QEvent::MouseButtonPress) &&
1305         (event->type() != QEvent::MouseMove)        &&
1306         (event->type() != QEvent::MouseButtonRelease))
1307     {
1308         return QObject::eventFilter(object, event);
1309     }
1310 
1311     // no filtering in pan mode
1312 
1313     if (s->currentMouseMode == MouseModePan)
1314     {
1315         return QObject::eventFilter(object, event);
1316     }
1317 
1318     QMouseEvent* const mouseEvent = static_cast<QMouseEvent*>(event);
1319     bool doFilterEvent            = false;
1320 
1321     if (s->currentMouseMode == MouseModeRegionSelection)
1322     {
1323         if      ((event->type()        == QEvent::MouseButtonPress) &&
1324                  (mouseEvent->button() == Qt::LeftButton))
1325         {
1326             // we need to filter this event because otherwise Marble displays
1327             // a left click context menu
1328 
1329             doFilterEvent = true;
1330         }
1331         else if (event->type() == QEvent::MouseMove)
1332         {
1333             if (d->firstSelectionPoint.hasCoordinates())
1334             {
1335                 d->intermediateSelectionPoint.clear();
1336                 geoCoordinates(mouseEvent->pos(), &d->intermediateSelectionPoint);
1337                 d->intermediateSelectionScreenPoint = mouseEvent->pos();
1338 
1339                 qCDebug(DIGIKAM_GEOIFACE_LOG) << d->firstSelectionScreenPoint
1340                                               << " "
1341                                               << d->intermediateSelectionScreenPoint;
1342 
1343                 qreal lonWest, latNorth, lonEast, latSouth;
1344 
1345                 if (d->firstSelectionScreenPoint.x() < d->intermediateSelectionScreenPoint.x())
1346                 {
1347                     lonWest = d->firstSelectionPoint.lon();
1348                     lonEast = d->intermediateSelectionPoint.lon();
1349                 }
1350                 else
1351                 {
1352                     lonWest = d->intermediateSelectionPoint.lon();
1353                     lonEast = d->firstSelectionPoint.lon();
1354                 }
1355 
1356                 if (d->firstSelectionScreenPoint.y() < d->intermediateSelectionScreenPoint.y())
1357                 {
1358                     latNorth = d->firstSelectionPoint.lat();
1359                     latSouth = d->intermediateSelectionPoint.lat();
1360                 }
1361                 else
1362                 {
1363                     latNorth = d->intermediateSelectionPoint.lat();
1364                     latSouth = d->firstSelectionPoint.lat();
1365                 }
1366 
1367                 const GeoCoordinates::Pair selectionCoordinates(
1368                         GeoCoordinates(latNorth, lonWest),
1369                         GeoCoordinates(latSouth, lonEast));
1370 
1371                 //setSelectionRectangle(selectionCoordinates, SelectionRectangle);
1372 
1373                 d->displayedRectangle = selectionCoordinates;
1374                 d->marbleWidget->update();
1375             }
1376 
1377             doFilterEvent = true;
1378         }
1379         else if ((event->type()        == QEvent::MouseButtonRelease) &&
1380                  (mouseEvent->button() == Qt::LeftButton))
1381         {
1382             if (!d->firstSelectionPoint.hasCoordinates())
1383             {
1384                 geoCoordinates(mouseEvent->pos(), &d->firstSelectionPoint);
1385                 d->firstSelectionScreenPoint = mouseEvent->pos();
1386             }
1387             else
1388             {
1389                 d->intermediateSelectionPoint.clear();
1390 
1391                 GeoCoordinates secondSelectionPoint;
1392                 geoCoordinates(mouseEvent->pos(), &secondSelectionPoint);
1393                 QPoint secondSelectionScreenPoint = mouseEvent->pos();
1394 
1395                 qreal lonWest, latNorth, lonEast, latSouth;
1396 
1397                 if (d->firstSelectionScreenPoint.x() < secondSelectionScreenPoint.x())
1398                 {
1399                     lonWest = d->firstSelectionPoint.lon();
1400                     lonEast = secondSelectionPoint.lon();
1401                 }
1402                 else
1403                 {
1404                     lonWest = secondSelectionPoint.lon();
1405                     lonEast = d->firstSelectionPoint.lon();
1406                 }
1407 
1408                 if (d->firstSelectionScreenPoint.y() < secondSelectionScreenPoint.y())
1409                 {
1410                     latNorth = d->firstSelectionPoint.lat();
1411                     latSouth = secondSelectionPoint.lat();
1412                 }
1413                 else
1414                 {
1415                     latNorth = secondSelectionPoint.lat();
1416                     latSouth = d->firstSelectionPoint.lat();
1417                 }
1418 
1419                 const GeoCoordinates::Pair selectionCoordinates(
1420                         GeoCoordinates(latNorth, lonWest),
1421                         GeoCoordinates(latSouth, lonEast));
1422 
1423                 d->firstSelectionPoint.clear();
1424                 d->displayedRectangle.first.clear();
1425 
1426                 Q_EMIT signalSelectionHasBeenMade(selectionCoordinates);
1427             }
1428 
1429             doFilterEvent = true;
1430         }
1431     }
1432     else
1433     {
1434         if ((event->type()        == QEvent::MouseButtonPress) &&
1435             (mouseEvent->button() == Qt::LeftButton))
1436         {
1437             // check whether the user clicked on one of our items:
1438             // scan in reverse order, because the user would expect
1439             // the topmost marker to be picked up and not the
1440             // one below
1441 /*
1442             if (s->specialMarkersModel)
1443             {
1444                 for (int row = s->specialMarkersModel->rowCount()-1 ; row >= 0 ; --row)
1445                 {
1446                     const QModelIndex currentIndex          = s->specialMarkersModel->index(row, 0);
1447                     const GeoCoordinates currentCoordinates = s->specialMarkersModel->data(currentIndex, s->specialMarkersCoordinatesRole).value<GeoCoordinates>();
1448 
1449                     QPoint markerPoint;
1450 
1451                     if (!screenCoordinates(currentCoordinates, &markerPoint))
1452                     {
1453                         continue;
1454                     }
1455 
1456                     const int markerPixmapHeight = s->markerPixmap.height();
1457                     const int markerPixmapWidth  = s->markerPixmap.width();
1458                     const QRect markerRect(markerPoint.x()-markerPixmapWidth/2, markerPoint.y()-markerPixmapHeight, markerPixmapWidth, markerPixmapHeight);
1459 
1460                     if (!markerRect.contains(mouseEvent->pos()))
1461                     {
1462                         continue;
1463                     }
1464 
1465                     // the user clicked on a marker:
1466 
1467                     d->mouseMoveMarkerIndex             = QPersistentModelIndex(currentIndex);
1468                     d->mouseMoveCenterOffset            = mouseEvent->pos() - markerPoint;
1469                     d->mouseMoveObjectCoordinates       = currentCoordinates;
1470                     doFilterEvent                       = true;
1471                     d->havePotentiallyMouseMovingObject = true;
1472 
1473                     break;
1474                 }
1475             }
1476 */
1477             if (
1478 /*
1479                 s->inEditMode &&
1480 */
1481                 // cppcheck-suppress knownConditionTrueFalse
1482                 !doFilterEvent
1483                )
1484             {
1485                 // scan in reverse order of painting!
1486 
1487                 for (int clusterIndex = s->clusterList.count()-1 ;
1488                      clusterIndex >= 0 ;
1489                      --clusterIndex)
1490                 {
1491                     const GeoIfaceCluster& cluster          = s->clusterList.at(clusterIndex);
1492                     const GeoCoordinates currentCoordinates = cluster.coordinates;
1493                     QPoint clusterPoint;
1494 
1495                     if (!screenCoordinates(currentCoordinates, &clusterPoint))
1496                     {
1497                         continue;
1498                     }
1499 
1500                     QRect markerRect;
1501                     markerRect.setSize(cluster.pixmapSize);
1502                     markerRect.moveTopLeft(clusterPoint);
1503                     markerRect.translate(-cluster.pixmapOffset);
1504 
1505                     if (!markerRect.contains(mouseEvent->pos()))
1506                     {
1507                         continue;
1508                     }
1509 
1510                     /// @todo For circles, make sure the mouse is really above the circle and not just in the rectangle!
1511 
1512                     // the user clicked on a cluster:
1513 
1514                     d->mouseMoveClusterIndex            = clusterIndex;
1515                     d->mouseMoveCenterOffset            = mouseEvent->pos() - clusterPoint;
1516                     d->mouseMoveObjectCoordinates       = currentCoordinates;
1517                     doFilterEvent                       = true;
1518                     d->havePotentiallyMouseMovingObject = true;
1519                     s->haveMovingCluster                = true;
1520 
1521                     break;
1522                 }
1523             }
1524         }
1525         else if ((event->type() == QEvent::MouseMove) &&
1526                  (d->havePotentiallyMouseMovingObject || d->haveMouseMovingObject))
1527         {
1528             if ((!s->modificationsAllowed)                                                 ||
1529                 (!s->markerModel->tilerFlags().testFlag(AbstractMarkerTiler::FlagMovable)) ||
1530                 ((d->mouseMoveClusterIndex >= 0) && s->showThumbnails))
1531             {
1532                 // clusters only move in edit mode and when edit mode is enabled
1533                 /// @todo This blocks moving of the map in non-edit mode
1534 
1535                 d->havePotentiallyMouseMovingObject = false;
1536                 d->mouseMoveClusterIndex            = -1;
1537                 d->mouseMoveMarkerIndex             = QPersistentModelIndex();
1538                 s->haveMovingCluster                = false;
1539             }
1540             else
1541             {
1542 
1543                 // mark the object as really moving:
1544 
1545                 d->havePotentiallyMouseMovingObject = false;
1546                 d->haveMouseMovingObject            = true;
1547 
1548                 // a cluster or marker is being moved. update its position:
1549 
1550                 QPoint newMarkerPoint               = mouseEvent->pos() - d->mouseMoveCenterOffset;
1551                 QPoint snapPoint;
1552 
1553                 if (findSnapPoint(newMarkerPoint, &snapPoint, nullptr, nullptr))
1554                 {
1555                     newMarkerPoint = snapPoint;
1556                 }
1557 
1558                 GeoCoordinates newCoordinates;
1559 
1560                 if (geoCoordinates(newMarkerPoint, &newCoordinates))
1561                 {
1562                     d->mouseMoveObjectCoordinates = newCoordinates;
1563                     d->marbleWidget->update();
1564                 }
1565             }
1566         }
1567         else if ((event->type() == QEvent::MouseButtonRelease) &&
1568                  (d->havePotentiallyMouseMovingObject))
1569         {
1570             // the object was not moved, but just clicked once
1571 
1572             if (d->mouseMoveClusterIndex >= 0)
1573             {
1574                 const int mouseMoveClusterIndex     = d->mouseMoveClusterIndex;
1575 
1576                 // we are done with the clicked object
1577                 // reset these before sending the signal
1578 
1579                 d->havePotentiallyMouseMovingObject = false;
1580                 d->mouseMoveClusterIndex            = -1;
1581                 d->mouseMoveMarkerIndex             = QPersistentModelIndex();
1582                 s->haveMovingCluster                = false;
1583                 doFilterEvent                       = true;
1584 
1585                 Q_EMIT signalClustersClicked(QIntList() << mouseMoveClusterIndex);
1586             }
1587             else
1588             {
1589                 // we are done with the clicked object:
1590 
1591                 d->havePotentiallyMouseMovingObject = false;
1592                 d->mouseMoveClusterIndex            = -1;
1593                 d->mouseMoveMarkerIndex             = QPersistentModelIndex();
1594                 s->haveMovingCluster                = false;
1595             }
1596         }
1597         else if ((event->type() == QEvent::MouseButtonRelease) &&
1598                  (d->haveMouseMovingObject))
1599         {
1600             // the object was dropped, apply the coordinates if it is on screen:
1601 
1602             const QPoint dropMarkerPoint = mouseEvent->pos() - d->mouseMoveCenterOffset;
1603 
1604             QPair<int, QModelIndex> snapTargetIndex(-1, QModelIndex());
1605             GeoCoordinates newCoordinates;
1606             bool haveValidPoint          = findSnapPoint(dropMarkerPoint, nullptr,
1607                                                          &newCoordinates,
1608                                                          &snapTargetIndex);
1609 
1610             if (!haveValidPoint)
1611             {
1612                 haveValidPoint = geoCoordinates(dropMarkerPoint, &newCoordinates);
1613             }
1614 
1615             if (haveValidPoint)
1616             {
1617                 if (d->mouseMoveMarkerIndex.isValid())
1618                 {
1619 /*
1620                     // the marker was dropped to valid coordinates
1621 
1622                     s->specialMarkersModel->setData(d->mouseMoveMarkerIndex, QVariant::fromValue(newCoordinates), s->specialMarkersCoordinatesRole);
1623 
1624                     QList<QPersistentModelIndex> markerIndices;
1625                     markerIndices << d->mouseMoveMarkerIndex;
1626 
1627                     // also Q_EMIT a signal that the marker was moved:
1628 
1629                     Q_EMIT signalSpecialMarkersMoved(markerIndices);
1630 */
1631                 }
1632                 else
1633                 {
1634                     // a cluster is being moved
1635 
1636                     s->clusterList[d->mouseMoveClusterIndex].coordinates = newCoordinates;
1637                     Q_EMIT signalClustersMoved(QIntList() << d->mouseMoveClusterIndex, snapTargetIndex);
1638                 }
1639             }
1640 
1641             d->haveMouseMovingObject = false;
1642             d->mouseMoveClusterIndex = -1;
1643             d->mouseMoveMarkerIndex  = QPersistentModelIndex();
1644             d->marbleWidget->update();
1645             s->haveMovingCluster     = false;
1646         }
1647     }
1648 
1649     if (doFilterEvent)
1650     {
1651         return true;
1652     }
1653 
1654     return QObject::eventFilter(object, event);
1655 }
1656 
1657 /*
1658 void BackendMarble::updateDragDropMarker(const QPoint& pos, const GeoIfaceDragData* const dragData)
1659 {
1660     if (!dragData)
1661     {
1662         d->dragDropMarkerCount = 0;
1663     }
1664     else
1665     {
1666         d->dragDropMarkerPos   = pos;
1667         d->dragDropMarkerCount = dragData->itemCount;
1668     }
1669 
1670     d->marbleWidget->update();
1671 
1672     // TODO: hide dragged markers on the map
1673 }
1674 
1675 void BackendMarble::updateDragDropMarkerPosition(const QPoint& pos)
1676 {
1677     d->dragDropMarkerPos = pos;
1678     d->marbleWidget->update();
1679 }
1680 */
1681 
1682 void BackendMarble::updateActionAvailability()
1683 {
1684     if ((!d->activeState) || (!d->marbleWidget))
1685     {
1686         return;
1687     }
1688 
1689     qCDebug(DIGIKAM_GEOIFACE_LOG) << d->cacheZoom
1690                                   << d->marbleWidget->maximumZoom()
1691                                   << d->marbleWidget->minimumZoom();
1692 
1693     s->worldMapWidget->getControlAction(QLatin1String("zoomin"))->setEnabled(d->cacheZoom<d->marbleWidget->maximumZoom());
1694     s->worldMapWidget->getControlAction(QLatin1String("zoomout"))->setEnabled(d->cacheZoom>d->marbleWidget->minimumZoom());
1695     const QList<QAction*> mapThemeActions = d->actionGroupMapTheme->actions();
1696 
1697     for (int i = 0 ; i < mapThemeActions.size() ; ++i)
1698     {
1699         mapThemeActions.at(i)->setChecked(mapThemeActions.at(i)->data().toString() == getMapTheme());
1700     }
1701 
1702     const QList<QAction*> projectionActions = d->actionGroupProjection->actions();
1703 
1704     for (int i = 0 ; i < projectionActions.size() ; ++i)
1705     {
1706         projectionActions.at(i)->setChecked(projectionActions.at(i)->data().toString() == d->cacheProjection);
1707     }
1708 
1709     d->actionShowCompass->setChecked(d->cacheShowCompass);
1710     d->actionShowScaleBar->setChecked(d->cacheShowScaleBar);
1711     d->actionShowNavigation->setChecked(d->cacheShowNavigation);
1712     d->actionShowOverviewMap->setChecked(d->cacheShowOverviewMap);
1713 }
1714 
1715 void BackendMarble::slotThumbnailAvailableForIndex(const QVariant& index, const QPixmap& pixmap)
1716 {
1717     if (!d->marbleWidget)
1718     {
1719         return;
1720     }
1721 
1722     qCDebug(DIGIKAM_GEOIFACE_LOG) << index << pixmap.size();
1723 
1724     if (pixmap.isNull() || !s->showThumbnails)
1725     {
1726         return;
1727     }
1728 
1729     // TODO: properly reject pixmaps with the wrong size
1730 
1731     const int expectedThumbnailSize = s->worldMapWidget->getUndecoratedThumbnailSize();
1732 
1733     if ((pixmap.size().height() > expectedThumbnailSize) ||
1734         (pixmap.size().width() > expectedThumbnailSize))
1735     {
1736         return;
1737     }
1738 
1739     // re-paint the map
1740 
1741     d->marbleWidget->update();
1742 }
1743 
1744 void BackendMarble::slotUngroupedModelChanged(const int index)
1745 {
1746     Q_UNUSED(index)
1747 
1748     if (!d->marbleWidget)
1749     {
1750         return;
1751     }
1752 
1753     d->marbleWidget->update();
1754 }
1755 
1756 void BackendMarble::slotTrackManagerChanged()
1757 {
1758     d->trackCache.clear();
1759 
1760     if (s->trackManager)
1761     {
1762         connect(s->trackManager, SIGNAL(signalTracksChanged(QList<TrackManager::TrackChanges>)),
1763                 this, SLOT(slotTracksChanged(QList<TrackManager::TrackChanges>)));
1764 
1765         // when the visibility of the tracks is changed, we simple schedule a redraw
1766 
1767         connect(s->trackManager, SIGNAL(signalVisibilityChanged(bool)),
1768                 this, SLOT(slotScheduleUpdate()));
1769     }
1770 
1771     slotScheduleUpdate();
1772 }
1773 
1774 bool BackendMarble::findSnapPoint(const QPoint& actualPoint,
1775                                   QPoint* const snapPoint,
1776                                   GeoCoordinates* const snapCoordinates,
1777                                   QPair<int, QModelIndex>* const snapTargetIndex)
1778 {
1779     QPoint bestSnapPoint;
1780     GeoCoordinates bestSnapCoordinates;
1781     int bestSnapDistanceSquared = -1;
1782     QModelIndex bestSnapIndex;
1783     int bestSnapUngroupedModel  = -1;
1784 
1785     // now handle snapping: is there any object close by?
1786 
1787     for (int im = 0 ; im < s->ungroupedModels.count() ; ++im)
1788     {
1789         GeoModelHelper* const modelHelper = s->ungroupedModels.at(im);
1790 
1791         // TODO: test for active snapping
1792 
1793         if ((!modelHelper->modelFlags().testFlag(GeoModelHelper::FlagVisible)) ||
1794             (!modelHelper->modelFlags().testFlag(GeoModelHelper::FlagSnaps)))
1795         {
1796             continue;
1797         }
1798 
1799         // TODO: configurable snapping radius
1800 
1801         const int snapRadiusSquared         = 10*10;
1802         QAbstractItemModel* const itemModel = modelHelper->model();
1803 
1804         for (int row = 0 ; row < itemModel->rowCount() ; ++row)
1805         {
1806             const QModelIndex currentIndex = itemModel->index(row, 0);
1807             GeoCoordinates currentCoordinates;
1808 
1809             if (!modelHelper->itemCoordinates(currentIndex, &currentCoordinates))
1810             {
1811                 continue;
1812             }
1813 
1814             QPoint snapMarkerPoint;
1815 
1816             if (!screenCoordinates(currentCoordinates, &snapMarkerPoint))
1817             {
1818                 continue;
1819             }
1820 
1821             const QPoint distancePoint    = snapMarkerPoint - actualPoint;
1822             const int snapDistanceSquared = (distancePoint.x()*distancePoint.x()+distancePoint.y()*distancePoint.y());
1823 
1824             if ((snapDistanceSquared <= snapRadiusSquared) &&
1825                 ((bestSnapDistanceSquared == -1) || (bestSnapDistanceSquared > snapDistanceSquared)))
1826             {
1827                 bestSnapDistanceSquared = snapDistanceSquared;
1828                 bestSnapPoint           = snapMarkerPoint;
1829                 bestSnapCoordinates     = currentCoordinates;
1830                 bestSnapIndex           = currentIndex;
1831                 bestSnapUngroupedModel  = im;
1832             }
1833         }
1834     }
1835 
1836     const bool foundSnapPoint = (bestSnapDistanceSquared >= 0);
1837 
1838     if (foundSnapPoint)
1839     {
1840         if (snapPoint)
1841         {
1842             *snapPoint = bestSnapPoint;
1843         }
1844 
1845         if (snapCoordinates)
1846         {
1847             *snapCoordinates = bestSnapCoordinates;
1848         }
1849 
1850         if (snapTargetIndex)
1851         {
1852             *snapTargetIndex = QPair<int, QModelIndex>(bestSnapUngroupedModel, bestSnapIndex);
1853         }
1854     }
1855 
1856     return foundSnapPoint;
1857 }
1858 
1859 void BackendMarble::regionSelectionChanged()
1860 {
1861     if (d->marbleWidget && d->activeState)
1862     {
1863         d->marbleWidget->update();
1864     }
1865 }
1866 
1867 void BackendMarble::mouseModeChanged()
1868 {
1869     if (s->currentMouseMode != MouseModeRegionSelection)
1870     {
1871         d->firstSelectionPoint.clear();
1872         d->intermediateSelectionPoint.clear();
1873 
1874         if (d->marbleWidget && d->activeState)
1875         {
1876             d->marbleWidget->update();
1877         }
1878     }
1879 }
1880 
1881 void BackendMarble::centerOn(const Marble::GeoDataLatLonBox& box, const bool useSaneZoomLevel)
1882 {
1883     if (!d->marbleWidget)
1884     {
1885         return;
1886     }
1887 
1888     /**
1889      * @todo Boxes with very small width or height (<1e-6 or so) cause a deadlock in Marble
1890      * in spherical projection.
1891      * So instead, we just center on the center of the box and go to maximum zoom.
1892      * This does not yet handle the case of only width or height being too small though.
1893      */
1894 
1895     const bool boxTooSmall = qMin(box.width(), box.height()) < 0.000001;
1896 
1897     if (boxTooSmall)
1898     {
1899         d->marbleWidget->centerOn(box.center());
1900         d->marbleWidget->zoomView(useSaneZoomLevel ? qMin(3400, d->marbleWidget->maximumZoom())
1901                                                    : d->marbleWidget->maximumZoom());
1902     }
1903     else
1904     {
1905         d->marbleWidget->centerOn(box, false);
1906     }
1907 
1908     // simple check to see whether the zoom level is now too high
1909     /// @todo for very small boxes, Marbles zoom becomes -2billion. Catch this case here.
1910 
1911     int maxZoomLevel = d->marbleWidget->maximumZoom();
1912 
1913     if (useSaneZoomLevel)
1914     {
1915         maxZoomLevel = qMin(maxZoomLevel, 3400);
1916     }
1917 
1918     if ((d->marbleWidget->zoom()>maxZoomLevel) ||
1919         (d->marbleWidget->zoom()<d->marbleWidget->minimumZoom()))
1920     {
1921         d->marbleWidget->zoomView(maxZoomLevel);
1922     }
1923 }
1924 
1925 void BackendMarble::setActive(const bool state)
1926 {
1927     const bool oldState = d->activeState;
1928     d->activeState      = state;
1929 
1930     if (oldState != state)
1931     {
1932         if ((!state) && d->marbleWidget)
1933         {
1934             // we should share our widget in the list of widgets in the global object
1935 
1936             GeoIfaceInternalWidgetInfo info;
1937             info.deleteFunction            = deleteInfoFunction;
1938             info.widget                    = d->marbleWidget;
1939             info.currentOwner              = this;
1940             info.backendName               = backendName();
1941             info.state                     = d->widgetIsDocked ? GeoIfaceInternalWidgetInfo::InternalWidgetStillDocked
1942                                                                : GeoIfaceInternalWidgetInfo::InternalWidgetUndocked;
1943 
1944             BMInternalWidgetInfo intInfo;
1945             intInfo.bmLayer                = d->bmLayer;
1946             info.backendData.setValue(intInfo);
1947 
1948             GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance();
1949             go->addMyInternalWidgetToPool(info);
1950         }
1951 
1952         if (state && d->marbleWidget)
1953         {
1954             // we should remove our widget from the list of widgets in the global object
1955 
1956             GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance();
1957             go->removeMyInternalWidgetFromPool(this);
1958         }
1959     }
1960 }
1961 
1962 void BackendMarble::mapWidgetDocked(const bool state)
1963 {
1964     if (d->widgetIsDocked!=state)
1965     {
1966         GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance();
1967         go->updatePooledWidgetState(d->marbleWidget,
1968                                     state ? GeoIfaceInternalWidgetInfo::InternalWidgetStillDocked
1969                                           : GeoIfaceInternalWidgetInfo::InternalWidgetUndocked);
1970     }
1971 
1972     d->widgetIsDocked = state;
1973 }
1974 
1975 void BackendMarble::drawSearchRectangle(Marble::GeoPainter* const painter,
1976                                         const GeoCoordinates::Pair& searchRectangle,
1977                                         const bool isOldRectangle)
1978 {
1979     const GeoCoordinates& topLeft     = searchRectangle.first;
1980     const GeoCoordinates& bottomRight = searchRectangle.second;
1981     const qreal lonWest               = topLeft.lon();
1982     const qreal latNorth              = topLeft.lat();
1983     const qreal lonEast               = bottomRight.lon();
1984     const qreal latSouth              = bottomRight.lat();
1985 
1986     Marble::GeoDataCoordinates coordTopLeft(lonWest,     latNorth, 0, Marble::GeoDataCoordinates::Degree);
1987     Marble::GeoDataCoordinates coordTopRight(lonEast,    latNorth, 0, Marble::GeoDataCoordinates::Degree);
1988     Marble::GeoDataCoordinates coordBottomLeft(lonWest,  latSouth, 0, Marble::GeoDataCoordinates::Degree);
1989     Marble::GeoDataCoordinates coordBottomRight(lonEast, latSouth, 0, Marble::GeoDataCoordinates::Degree);
1990     Marble::GeoDataLinearRing polyRing;
1991 
1992     polyRing << coordTopLeft << coordTopRight << coordBottomRight << coordBottomLeft;
1993 
1994     QPen selectionPen;
1995 
1996     if (isOldRectangle)
1997     {
1998         // there is a new selection in progress,
1999         // therefore display the current search rectangle in red
2000 
2001         selectionPen.setColor(Qt::red);
2002     }
2003     else
2004     {
2005         selectionPen.setColor(Qt::blue);
2006     }
2007 
2008     selectionPen.setStyle(Qt::SolidLine);
2009     selectionPen.setWidth(1);
2010     painter->setPen(selectionPen);
2011     painter->setBrush(Qt::NoBrush);
2012     painter->drawPolygon(polyRing);
2013 }
2014 
2015 void BackendMarble::deleteInfoFunction(GeoIfaceInternalWidgetInfo* const info)
2016 {
2017     if (info->currentOwner)
2018     {
2019         qobject_cast<MapBackend*>(info->currentOwner.data())->releaseWidget(info);
2020     }
2021 
2022     BMInternalWidgetInfo intInfo = info->backendData.value<BMInternalWidgetInfo>();
2023 
2024     if (intInfo.bmLayer)
2025     {
2026         delete intInfo.bmLayer;
2027     }
2028 
2029     delete info->widget.data();
2030 }
2031 
2032 void BackendMarble::applyCacheToWidget()
2033 {
2034     /// @todo Do this only when the widget is active!
2035 
2036     if (!d->marbleWidget)
2037     {
2038         return;
2039     }
2040 
2041     setMapTheme(d->cacheMapTheme);
2042     setProjection(d->cacheProjection);
2043     setShowCompass(d->cacheShowCompass);
2044     setShowScaleBar(d->cacheShowScaleBar);
2045     setShowNavigation(d->cacheShowNavigation);
2046     setShowOverviewMap(d->cacheShowOverviewMap);
2047 }
2048 
2049 void BackendMarble::slotTracksChanged(const QList<TrackManager::TrackChanges>& trackChanges)
2050 {
2051     // invalidate the cache for all changed tracks
2052 
2053     Q_FOREACH (const TrackManager::TrackChanges& tc, trackChanges)
2054     {
2055         if (tc.second & (TrackManager::ChangeTrackPoints | TrackManager::ChangeRemoved))
2056         {
2057             d->trackCache.remove(tc.first);
2058         }
2059     }
2060 
2061     slotScheduleUpdate();
2062 }
2063 
2064 void BackendMarble::slotScheduleUpdate()
2065 {
2066     if (d->marbleWidget && d->activeState)
2067     {
2068         /// @TODO Put this onto the eventloop to collect update calls into one.
2069 
2070         d->marbleWidget->update();
2071     }
2072 }
2073 
2074 } // namespace Digikam
2075 
2076 #include "moc_backendmarble.cpp"