File indexing completed on 2024-05-05 16:28:22
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"