File indexing completed on 2024-05-05 04:22:10

0001 // SPDX-FileCopyrightText: 2014-2022 Tobias Leupold <tl@stonemx.de>
0002 // SPDX-FileCopyrightText: 2015-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 // SPDX-FileCopyrightText: 2022 Jesper K. Pedersen <jesper.pedersen@kdab.com>
0004 //
0005 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 
0007 // Local includes
0008 
0009 #include "MapView.h"
0010 #include "GeoCluster.h"
0011 #include "Logging.h"
0012 
0013 #include <DB/ImageDB.h>
0014 #include <DB/search/ImageSearchInfo.h>
0015 #include <MainWindow/Window.h>
0016 #include <kpabase/Logging.h>
0017 
0018 #include <KConfigGroup>
0019 #include <KIconLoader>
0020 #include <KLocalizedString>
0021 #include <KMessageBox>
0022 #include <KSharedConfig>
0023 #include <QAction>
0024 #include <QDebug>
0025 #include <QElapsedTimer>
0026 #include <QGuiApplication>
0027 #include <QLabel>
0028 #include <QLoggingCategory>
0029 #include <QMouseEvent>
0030 #include <QPixmap>
0031 #include <QPushButton>
0032 #include <QVBoxLayout>
0033 #include <marble/GeoPainter.h>
0034 #include <marble/MarbleWidget.h>
0035 #include <marble/RenderPlugin.h>
0036 #include <marble/ViewportParams.h>
0037 
0038 namespace
0039 {
0040 // size of the markers in screen coordinates (pixel)
0041 constexpr int MARKER_SIZE_DEFAULT_PX = 40;
0042 constexpr int MARKER_SIZE_MIN_PX = 20;
0043 constexpr int MARKER_SIZE_MAX_PX = 400;
0044 constexpr int MARKER_SIZE_STEP_PX = 10;
0045 const QString MAPVIEW_FLOATER_VISIBLE_CONFIG_PREFIX = QStringLiteral("MarbleFloaterVisible ");
0046 const QStringList MAPVIEW_RENDER_POSITION({ QStringLiteral("HOVERS_ABOVE_SURFACE") });
0047 const QVector<QString> WANTED_FLOATERS { QStringLiteral("compass"),
0048                                          QStringLiteral("scalebar"),
0049                                          QStringLiteral("navigation"),
0050                                          QStringLiteral("overviewmap") };
0051 
0052 // levels of clustering for geo coordinates
0053 constexpr int MAP_CLUSTER_LEVELS = 10;
0054 static_assert(MAP_CLUSTER_LEVELS > 0, "At least one level of clustering is needed for the map.");
0055 static_assert(MAP_CLUSTER_LEVELS < 32, "See coarsenBinAddress to know why this is a bad idea.");
0056 
0057 /**
0058  * @brief computeBinAddress calculates a "bin" for grouping coordinates that are near each other.
0059  * Using a signed 32-bit integer value allows for 2 decimal places of either coordinate part,
0060  * which is roughly equivalent to a spatial resolution of 1 km.
0061  * @param coords (more or less) precise coordinates
0062  * @return imprecise coordinates
0063  */
0064 Map::GeoBinAddress computeBinAddress(const Map::GeoCoordinates &coords)
0065 {
0066     qint64 lat = qRound(coords.lat() * 100);
0067     qint64 lon = qRound(coords.lon() * 100);
0068     return static_cast<quint64>((lat << 32) + lon);
0069 }
0070 
0071 /**
0072  * @brief coarsenBinAddress takes A GeoBinAddress and reduces its precision.
0073  * @param addr the address to be reduced in accuracy
0074  * @param level how many binary digits should be nulled out
0075  * @return
0076  */
0077 Map::GeoBinAddress coarsenBinAddress(Map::GeoBinAddress addr, int level)
0078 {
0079     constexpr quint64 LO = 0x00000000ffffffff;
0080     constexpr quint64 HI = 0xffffffff00000000;
0081     // zero out the rightmost bits
0082     quint64 mask = 0xffffffffffffffff << level;
0083     // duplicate the mask onto the higher 32 bits
0084     mask = (HI & (mask << 32)) | (LO & mask);
0085     // apply mask
0086     return addr & mask;
0087 }
0088 
0089 /**
0090  * @brief buildClusterMap fills the lodMap by putting the GeoBin in GeoClusters
0091  * of decreasing levels of detail.
0092  * @param lodMap a vector containing a <GeoBinAddress, GeoCluster*> map for each level of detail
0093  * @param binAddress the GeoBinAddress for the newly added GeoBin
0094  * @param bin the GeoBin to add to the lodMap
0095  */
0096 void buildClusterMap(QVector<QHash<Map::GeoBinAddress, Map::GeoCluster *>> &lodMap,
0097                      Map::GeoBinAddress binAddress, const Map::GeoBin *bin)
0098 {
0099     const Map::GeoCluster *cluster = bin;
0100     for (int lvl = 1; lvl <= MAP_CLUSTER_LEVELS; lvl++) {
0101         QHash<Map::GeoBinAddress, Map::GeoCluster *> &map = lodMap[lvl - 1];
0102         binAddress = coarsenBinAddress(binAddress, lvl);
0103         qCDebug(MapLog) << "adding GeoCluster with address" << binAddress << "at level" << lvl;
0104         if (map.contains(binAddress)) {
0105             map[binAddress]->addSubCluster(cluster);
0106             break;
0107         } else {
0108             map.insert(binAddress, new Map::GeoCluster(lvl));
0109             map[binAddress]->addSubCluster(cluster);
0110             cluster = map[binAddress];
0111         }
0112     }
0113 }
0114 
0115 }
0116 
0117 namespace
0118 {
0119 inline QPixmap smallIcon(const QString &iconName)
0120 {
0121     return QIcon::fromTheme(iconName).pixmap(KIconLoader::StdSizes::SizeSmall);
0122 }
0123 }
0124 
0125 Map::MapView::MapView(QWidget *parent, UsageType type)
0126     : QWidget(parent)
0127     , m_markerSize(MARKER_SIZE_DEFAULT_PX)
0128 {
0129     if (type == UsageType::MapViewWindow) {
0130         setWindowFlags(Qt::Window);
0131         setAttribute(Qt::WA_DeleteOnClose);
0132     }
0133     setMouseTracking(true);
0134 
0135     QVBoxLayout *layout = new QVBoxLayout(this);
0136 
0137     m_statusLabel = new QLabel;
0138     m_statusLabel->setAlignment(Qt::AlignCenter);
0139     m_statusLabel->hide();
0140     layout->addWidget(m_statusLabel);
0141 
0142     m_mapWidget = new Marble::MarbleWidget;
0143     m_mapWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0144     m_mapWidget->setProjection(Marble::Mercator);
0145     m_mapWidget->setMapThemeId(QStringLiteral("earth/openstreetmap/openstreetmap.dgml"));
0146 #ifdef MARBLE_HAS_regionSelected_NEW
0147     connect(m_mapWidget, &Marble::MarbleWidget::regionSelected,
0148             this, &Map::MapView::updateRegionSelection);
0149 #else
0150     connect(m_mapWidget, &Marble::MarbleWidget::regionSelected,
0151             this, &Map::MapView::updateRegionSelectionOld);
0152 #endif
0153 
0154     m_mapWidget->addLayer(this);
0155 
0156     layout->addWidget(m_mapWidget);
0157     m_mapWidget->show();
0158 
0159     QHBoxLayout *controlLayout = new QHBoxLayout;
0160     layout->addLayout(controlLayout);
0161 
0162     // KPA's control buttons
0163 
0164     m_kpaButtons = new QWidget;
0165     QHBoxLayout *kpaButtonsLayout = new QHBoxLayout(m_kpaButtons);
0166     controlLayout->addWidget(m_kpaButtons);
0167 
0168     QPushButton *saveButton = new QPushButton;
0169     saveButton->setFlat(true);
0170     saveButton->setIcon(QPixmap(smallIcon(QStringLiteral("media-floppy"))));
0171     saveButton->setToolTip(i18n("Save the current map settings"));
0172     kpaButtonsLayout->addWidget(saveButton);
0173     connect(saveButton, &QPushButton::clicked, this, &MapView::saveSettings);
0174 
0175     m_setLastCenterButton = new QPushButton;
0176     m_setLastCenterButton->setFlat(true);
0177     m_setLastCenterButton->setIcon(QPixmap(smallIcon(QStringLiteral("go-first"))));
0178     m_setLastCenterButton->setToolTip(i18n("Go to last map position"));
0179     kpaButtonsLayout->addWidget(m_setLastCenterButton);
0180     connect(m_setLastCenterButton, &QPushButton::clicked, this, &MapView::setLastCenter);
0181 
0182     QPushButton *showThumbnails = new QPushButton;
0183     showThumbnails->setFlat(true);
0184     showThumbnails->setIcon(QPixmap(smallIcon(QStringLiteral("view-preview"))));
0185     showThumbnails->setToolTip(i18n("Show thumbnails"));
0186     kpaButtonsLayout->addWidget(showThumbnails);
0187     showThumbnails->setCheckable(true);
0188     showThumbnails->setChecked(m_showThumbnails);
0189     connect(showThumbnails, &QPushButton::clicked, this, &MapView::setShowThumbnails);
0190 
0191     QPushButton *groupThumbnails = new QPushButton;
0192     groupThumbnails->setFlat(true);
0193     groupThumbnails->setIcon(QPixmap(smallIcon(QStringLiteral("view-group"))));
0194     groupThumbnails->setToolTip(i18nc("@action to group, as in aggregate thumbnails into clusters", "Group adjacent thumbnails into clickable clusters"));
0195     kpaButtonsLayout->addWidget(groupThumbnails);
0196     groupThumbnails->setCheckable(true);
0197     groupThumbnails->setChecked(m_groupThumbnails);
0198     connect(groupThumbnails, &QPushButton::clicked, this, &MapView::setGroupThumbnails);
0199     // groupThumbnails interacts with showThumbnails:
0200     showThumbnails->setEnabled(m_groupThumbnails);
0201     connect(groupThumbnails, &QPushButton::clicked, showThumbnails, &QPushButton::setEnabled);
0202 
0203     QPushButton *decreaseMarkerSize = new QPushButton;
0204     decreaseMarkerSize->setFlat(true);
0205     decreaseMarkerSize->setIcon(QPixmap(smallIcon(QStringLiteral("view-zoom-out-symbolic"))));
0206     decreaseMarkerSize->setToolTip(i18nc("@action Decrease size of the markers (i.e. thumbnails) drawn on the map", "Decrease marker size"));
0207     kpaButtonsLayout->addWidget(decreaseMarkerSize);
0208     connect(decreaseMarkerSize, &QPushButton::clicked, this, &MapView::decreaseMarkerSize);
0209 
0210     QPushButton *increaseMarkerSize = new QPushButton;
0211     increaseMarkerSize->setFlat(true);
0212     increaseMarkerSize->setIcon(QPixmap(smallIcon(QStringLiteral("view-zoom-in-symbolic"))));
0213     increaseMarkerSize->setToolTip(i18nc("@action Increase size of the markers (i.e. thumbnails) drawn on the map", "Increase marker size"));
0214     kpaButtonsLayout->addWidget(increaseMarkerSize);
0215     connect(increaseMarkerSize, &QPushButton::clicked, this, &MapView::increaseMarkerSize);
0216 
0217     // Marble floater control buttons
0218 
0219     m_floaters = new QWidget;
0220     QHBoxLayout *floatersLayout = new QHBoxLayout(m_floaters);
0221     controlLayout->addStretch();
0222     controlLayout->addWidget(m_floaters);
0223 
0224     KSharedConfigPtr config = KSharedConfig::openConfig();
0225     KConfigGroup group = config->group(QStringLiteral("MapView"));
0226 
0227     const auto renderPlugins = m_mapWidget->renderPlugins();
0228     for (const Marble::RenderPlugin *plugin : renderPlugins) {
0229         if (plugin->renderType() != Marble::RenderPlugin::PanelRenderType) {
0230             continue;
0231         }
0232 
0233         const QString name = plugin->nameId();
0234         if (!WANTED_FLOATERS.contains(name)) {
0235             continue;
0236         }
0237 
0238         QPushButton *button = new QPushButton;
0239         button->setCheckable(true);
0240         button->setFlat(true);
0241         button->setChecked(plugin->action()->isChecked());
0242         button->setToolTip(plugin->description());
0243         button->setProperty("floater", name);
0244 
0245         QPixmap icon = plugin->action()->icon().pixmap(QSize(20, 20));
0246         if (icon.isNull()) {
0247             icon = QPixmap(20, 20);
0248             icon.fill(palette().button().color());
0249         }
0250         button->setIcon(icon);
0251 
0252         connect(plugin->action(), &QAction::toggled, button, &QPushButton::setChecked);
0253         connect(button, &QPushButton::toggled, plugin->action(), &QAction::setChecked);
0254         floatersLayout->addWidget(button);
0255         const QVariant checked = group.readEntry(MAPVIEW_FLOATER_VISIBLE_CONFIG_PREFIX + name,
0256                                                  true);
0257         button->setChecked(checked.toBool());
0258     }
0259 
0260     m_pin = QPixmap(QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("pics/pin.png")));
0261 }
0262 
0263 void Map::MapView::clear()
0264 {
0265     m_markersBox.clear();
0266     m_baseBins.clear();
0267     m_geoClusters.clear();
0268     m_regionSelected = false;
0269 }
0270 
0271 bool Map::MapView::addImage(DB::ImageInfoPtr image)
0272 {
0273     if (image->coordinates().hasCoordinates()) {
0274         const GeoBinAddress binAddress = computeBinAddress(image->coordinates());
0275         if (!m_baseBins.contains(binAddress)) {
0276             m_baseBins.insert(binAddress, new GeoBin());
0277         }
0278         m_baseBins[binAddress]->addImage(image);
0279         // Update the viewport for zoomToMarkers()
0280         extendGeoDataLatLonBox(m_markersBox, image->coordinates());
0281         return true;
0282     }
0283     return false;
0284 }
0285 
0286 void Map::MapView::addImages(const DB::ImageSearchInfo &searchInfo)
0287 {
0288     QElapsedTimer timer;
0289     timer.start();
0290     displayStatus(MapStatus::Loading);
0291     const auto images = DB::ImageDB::instance()->search(searchInfo);
0292     int count = 0;
0293     int total = 0;
0294     // put images in bins
0295     for (const auto &imageInfo : images) {
0296         total++;
0297         if (addImage(imageInfo))
0298             count++;
0299     }
0300     buildImageClusters();
0301     displayStatus(MapStatus::SearchCoordinates);
0302     qCInfo(TimingLog) << "MapView::addImages(): added" << count << "of" << total << "images in" << timer.elapsed() << "ms.";
0303 }
0304 
0305 void Map::MapView::buildImageClusters()
0306 {
0307     QElapsedTimer timer;
0308     timer.start();
0309     QVector<QHash<GeoBinAddress, GeoCluster *>> clusters { MAP_CLUSTER_LEVELS };
0310     int count = 0;
0311     // aggregate bins to clusters
0312     for (auto it = m_baseBins.constBegin(); it != m_baseBins.constEnd(); ++it) {
0313         buildClusterMap(clusters, it.key(), it.value());
0314         count++;
0315     }
0316     // alternative proposal:
0317     // 1. sort clusters by size
0318     // 2. take biggest cluster and compute distance to other clusters
0319     // 3. create new aggregate cluster of biggest cluster with all clusters nearer than 1km*2^level
0320     //    remove aggregated clusters from set of eligible clusters
0321     // 4. with remaining clusters, continue at 2.
0322 
0323     Q_ASSERT(clusters[MAP_CLUSTER_LEVELS - 1].size() > 0 || clusters[0].size() == 0);
0324     for (int lvl = 0; lvl < MAP_CLUSTER_LEVELS; lvl++) {
0325         qCInfo(MapLog) << "MapView:" << clusters[lvl].size() << "clusters on level" << lvl;
0326     }
0327     m_geoClusters = clusters[MAP_CLUSTER_LEVELS - 1].values();
0328     qCDebug(TimingLog) << "MapView::addImages(): aggregated" << count << "GeoClusters in" << timer.elapsed() << "ms.";
0329 }
0330 
0331 void Map::MapView::zoomToMarkers()
0332 {
0333     m_mapWidget->centerOn(m_markersBox);
0334 }
0335 
0336 void Map::MapView::setCenter(const DB::ImageInfoPtr image)
0337 {
0338     m_lastCenter = image->coordinates();
0339     setLastCenter();
0340 }
0341 
0342 void Map::MapView::increaseMarkerSize()
0343 {
0344     setMarkerSize(markerSize() + MARKER_SIZE_STEP_PX);
0345 }
0346 
0347 void Map::MapView::decreaseMarkerSize()
0348 {
0349     setMarkerSize(markerSize() - MARKER_SIZE_STEP_PX);
0350 }
0351 
0352 void Map::MapView::saveSettings()
0353 {
0354     KSharedConfigPtr config = KSharedConfig::openConfig();
0355     KConfigGroup group = config->group(QStringLiteral("MapView"));
0356     const auto buttons = m_floaters->findChildren<QPushButton *>();
0357     for (const QPushButton *button : buttons) {
0358         group.writeEntry(MAPVIEW_FLOATER_VISIBLE_CONFIG_PREFIX
0359                              + button->property("floater").toString(),
0360                          button->isChecked());
0361     }
0362     config->sync();
0363     QMessageBox::information(this, i18n("Map view"), i18n("Settings saved!"));
0364 }
0365 
0366 void Map::MapView::setShowThumbnails(bool state)
0367 {
0368     m_showThumbnails = state;
0369     m_mapWidget->update();
0370 }
0371 
0372 void Map::MapView::setGroupThumbnails(bool state)
0373 {
0374     m_groupThumbnails = state;
0375     m_mapWidget->update();
0376 }
0377 
0378 Map::MapStyle Map::MapView::mapStyle() const
0379 {
0380     if (m_groupThumbnails)
0381         return m_showThumbnails ? MapStyle::ShowThumbnails : MapStyle::ShowPins;
0382     else
0383         return MapStyle::ForceShowThumbnails;
0384 }
0385 
0386 void Map::MapView::displayStatus(MapStatus status)
0387 {
0388     switch (status) {
0389     case MapStatus::Loading:
0390         m_statusLabel->setText(i18n("<i>Loading coordinates from the images ...</i>"));
0391         m_statusLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0392         m_statusLabel->show();
0393         m_mapWidget->hide();
0394         m_regionSelected = false;
0395         m_setLastCenterButton->setEnabled(false);
0396         break;
0397     case MapStatus::ImageHasCoordinates:
0398         m_statusLabel->hide();
0399         m_regionSelected = false;
0400         m_statusLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0401         m_mapWidget->show();
0402         m_setLastCenterButton->show();
0403         m_setLastCenterButton->setEnabled(true);
0404         break;
0405     case MapStatus::ImageHasNoCoordinates:
0406         m_statusLabel->setText(i18n("<i>This image does not contain geographic coordinates.</i>"));
0407         m_statusLabel->show();
0408         m_statusLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0409         m_mapWidget->hide();
0410         m_setLastCenterButton->show();
0411         m_setLastCenterButton->setEnabled(false);
0412         break;
0413     case MapStatus::SomeImagesHaveNoCoordinates:
0414         m_statusLabel->setText(i18n("<i>Some of the selected images do not contain geographic "
0415                                     "coordinates.</i>"));
0416         m_statusLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0417         m_statusLabel->show();
0418         m_regionSelected = false;
0419         m_mapWidget->show();
0420         m_setLastCenterButton->show();
0421         m_setLastCenterButton->setEnabled(true);
0422         break;
0423     case MapStatus::SearchCoordinates:
0424         m_statusLabel->setText(i18n("<i>Search for geographic coordinates.</i>"));
0425         m_statusLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0426         m_statusLabel->show();
0427         m_mapWidget->show();
0428         m_mapWidget->centerOn(0.0, 0.0);
0429         m_setLastCenterButton->hide();
0430         break;
0431     case MapStatus::NoImagesHaveNoCoordinates:
0432         m_statusLabel->setText(i18n("<i>None of the selected images contain geographic "
0433                                     "coordinates.</i>"));
0434         m_statusLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0435         m_statusLabel->show();
0436         m_mapWidget->hide();
0437         m_setLastCenterButton->show();
0438         m_setLastCenterButton->setEnabled(false);
0439         break;
0440     }
0441     Q_EMIT displayStatusChanged(status);
0442 }
0443 
0444 void Map::MapView::setLastCenter()
0445 {
0446     m_mapWidget->centerOn(m_lastCenter.lon(), m_lastCenter.lat());
0447 }
0448 
0449 void Map::MapView::updateRegionSelection(const Marble::GeoDataLatLonBox &selection)
0450 {
0451     m_regionSelected = true;
0452     m_regionSelection = selection;
0453     Q_EMIT newRegionSelected(getRegionSelection());
0454 }
0455 
0456 int Map::MapView::markerSize() const
0457 {
0458     return m_markerSize;
0459 }
0460 
0461 void Map::MapView::setMarkerSize(int markerSizePx)
0462 {
0463     m_markerSize = qBound(MARKER_SIZE_MIN_PX, markerSizePx, MARKER_SIZE_MAX_PX);
0464     qCDebug(MapLog) << "Set map marker size to" << m_markerSize;
0465     m_mapWidget->update();
0466 }
0467 
0468 #ifndef MARBLE_HAS_regionSelected_NEW
0469 void Map::MapView::updateRegionSelectionOld(const QList<double> &selection)
0470 {
0471     Q_ASSERT(selection.length() == 4);
0472     // see also: https://commits.kde.org/marble/ec1f7f554e9f6ca248b4a3b01dbf08507870687e
0473     Marble::GeoDataLatLonBox sel { selection.at(1), selection.at(3), selection.at(2), selection.at(0), Marble::GeoDataCoordinates::Degree };
0474     updateRegionSelection(sel);
0475 }
0476 #endif
0477 
0478 Map::GeoCoordinates::LatLonBox Map::MapView::getRegionSelection() const
0479 {
0480     return GeoCoordinates::LatLonBox(m_regionSelection);
0481 }
0482 
0483 bool Map::MapView::regionSelected() const
0484 {
0485     return m_regionSelected;
0486 }
0487 
0488 void Map::MapView::mousePressEvent(QMouseEvent *event)
0489 {
0490     if (m_preselectedCluster && event->button() == Qt::RightButton) {
0491         // cancel geocluster selection if RMB is clicked
0492         m_preselectedCluster = nullptr;
0493         event->accept();
0494         return;
0495     }
0496     if (event->button() == Qt::LeftButton && event->modifiers() == Qt::NoModifier) {
0497         if (m_mapWidget->geometry().contains(event->pos())) {
0498             qCDebug(MapLog) << "Map clicked.";
0499             const auto mapPos = event->pos() - m_mapWidget->pos();
0500 
0501             for (const auto *topLevelCluster : qAsConst(m_geoClusters)) {
0502                 const auto subCluster = topLevelCluster->regionForPoint(mapPos);
0503                 if (subCluster && !subCluster->isEmpty()) {
0504                     qCDebug(MapLog) << "Cluster preselected/clicked.";
0505                     m_preselectedCluster = subCluster;
0506                     event->accept();
0507                     return;
0508                 }
0509             }
0510         }
0511     }
0512     event->ignore();
0513     QWidget::mousePressEvent(event);
0514 }
0515 
0516 void Map::MapView::mouseReleaseEvent(QMouseEvent *event)
0517 {
0518     if (m_preselectedCluster) {
0519         qCDebug(MapLog) << "Cluster selection accepted.";
0520         updateRegionSelection(m_preselectedCluster->boundingRegion());
0521         m_preselectedCluster = nullptr;
0522         event->accept();
0523     } else {
0524         QWidget::mouseReleaseEvent(event);
0525     }
0526 }
0527 
0528 void Map::MapView::mouseMoveEvent(QMouseEvent *event)
0529 {
0530     if (event->button() == Qt::NoButton) {
0531         if (m_mapWidget->geometry().contains(event->pos())) {
0532             const auto mapPos = event->pos() - m_mapWidget->pos();
0533             for (const auto *topLevelCluster : qAsConst(m_geoClusters)) {
0534                 const auto subCluster = topLevelCluster->regionForPoint(mapPos);
0535                 // Note(jzarl) unfortunately we cannot use QWidget::setCursor here
0536                 if (subCluster) {
0537                     QGuiApplication::setOverrideCursor(Qt::ArrowCursor);
0538                 } else {
0539                     QGuiApplication::restoreOverrideCursor();
0540                 }
0541             }
0542         }
0543     }
0544     QWidget::mouseMoveEvent(event);
0545 }
0546 
0547 void Map::MapView::keyPressEvent(QKeyEvent *event)
0548 {
0549     if (m_preselectedCluster && event->matches(QKeySequence::Cancel)) {
0550         // cancel geocluster selection if Escape key is pressed
0551         m_preselectedCluster = nullptr;
0552     } else {
0553         QWidget::keyPressEvent(event);
0554     }
0555 }
0556 
0557 QStringList Map::MapView::renderPosition() const
0558 {
0559     // we only ever paint on the same layer:
0560     return MAPVIEW_RENDER_POSITION;
0561 }
0562 
0563 bool Map::MapView::render(Marble::GeoPainter *painter, Marble::ViewportParams *viewPortParams,
0564                           const QString &renderPos, Marble::GeoSceneLayer *)
0565 {
0566     Q_ASSERT(renderPos == renderPosition().constFirst());
0567     Q_ASSERT(viewPortParams != nullptr);
0568     QElapsedTimer timer;
0569     timer.start();
0570 
0571     painter->setBrush(Qt::transparent);
0572     painter->setPen(palette().color(QPalette::Highlight));
0573     constexpr qreal BOUNDING_BOX_SCALEFACTOR = 1.2;
0574     painter->drawRect(m_markersBox.center(), m_markersBox.width(Marble::GeoDataCoordinates::Degree) * BOUNDING_BOX_SCALEFACTOR, m_markersBox.height(Marble::GeoDataCoordinates::Degree) * BOUNDING_BOX_SCALEFACTOR, true);
0575 
0576     painter->setBrush(palette().brush(QPalette::Dark));
0577     painter->setPen(palette().color(QPalette::Text));
0578     ThumbnailParams thumbs { m_pin, MainWindow::Window::theMainWindow()->thumbnailCache(), m_markerSize };
0579     for (const auto *bin : qAsConst(m_geoClusters)) {
0580         bin->render(painter, *viewPortParams, thumbs, mapStyle());
0581     }
0582     if (m_preselectedCluster) {
0583         painter->setBrush(palette().brush(QPalette::Highlight));
0584         painter->setPen(palette().color(QPalette::HighlightedText));
0585         m_preselectedCluster->render(painter, *viewPortParams, thumbs, mapStyle());
0586     }
0587 
0588     qCDebug(TimingLog) << "Map rendered in" << timer.elapsed() << "ms.";
0589     return true;
0590 }
0591 
0592 // vi:expandtab:tabstop=4 shiftwidth=4:
0593 
0594 #include "moc_MapView.cpp"